diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md
index 43e586897..09a933f81 100644
--- a/docs/keybindings/Keybindings_en.md
+++ b/docs/keybindings/Keybindings_en.md
@@ -123,30 +123,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Commits Panel (Commits)
- ctrl+l: open log menu
- s: squash down
- r: reword commit
- R: reword commit with editor
- g: reset to this commit
- f: fixup commit
- F: create fixup commit for this commit
- S: squash all 'fixup!' commits above selected commit (autosquash)
- d: delete commit
- ctrl+j: move commit down one
- ctrl+k: move commit up one
- e: edit commit
- A: amend commit with staged changes
- p: pick commit (when mid-rebase)
- t: revert commit
c: copy commit (cherry-pick)
ctrl+o: copy commit SHA to clipboard
C: copy commit range (cherry-pick)
v: paste commits (cherry-pick)
+ n: create new branch off of commit
+ ctrl+r: reset cherry-picked (copied) commits selection
+ s: squash down
+ f: fixup commit
+ r: reword commit
+ R: reword commit with editor
+ d: delete commit
+ e: edit commit
+ p: pick commit (when mid-rebase)
+ F: create fixup commit for this commit
+ S: squash all 'fixup!' commits above selected commit (autosquash)
+ ctrl+j: move commit down one
+ ctrl+k: move commit up one
+ A: amend commit with staged changes
+ t: revert commit
+ ctrl+l: open log menu
+ g: reset to this commit
enter: view commit's files
space: checkout commit
- n: create new branch off of commit
T: tag commit
- ctrl+r: reset cherry-picked (copied) commits selection
ctrl+y: copy commit message to clipboard
o: open commit in browser
b: view bisect options
@@ -183,7 +183,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
w: commit changes without pre-commit hook
A: amend last commit
C: commit changes using git editor
- space: toggle staged
d: view 'discard changes' options
e: edit file
o: open file
@@ -200,6 +199,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
`: toggle file tree view
M: open external merge tool (git mergetool)
ctrl+w: Toggle whether or not whitespace changes are shown in the diff view
+ space: toggle staged
## Files Panel (Submodules)
diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md
index 7d5b8f78c..5e0d62fac 100644
--- a/docs/keybindings/Keybindings_nl.md
+++ b/docs/keybindings/Keybindings_nl.md
@@ -123,30 +123,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Commits Paneel (Commits)
- ctrl+l: open log menu
- s: squash beneden
- r: hernoem commit
- R: hernoem commit met editor
- g: reset naar deze commit
- f: Fixup commit
- F: creëer fixup commit voor deze commit
- S: squash bovenstaande commits
- d: verwijder commit
- ctrl+j: verplaats commit 1 naar beneden
- ctrl+k: verplaats commit 1 naar boven
- e: wijzig commit
- A: wijzig commit met staged veranderingen
- p: kies commit (wanneer midden in rebase)
- t: commit ongedaan maken
c: kopieer commit (cherry-pick)
ctrl+o: kopieer commit SHA naar klembord
C: kopieer commit reeks (cherry-pick)
v: plak commits (cherry-pick)
+ n: creëer nieuwe branch van commit
+ ctrl+r: reset cherry-picked (gekopieerde) commits selectie
+ s: squash beneden
+ f: Fixup commit
+ r: hernoem commit
+ R: hernoem commit met editor
+ d: verwijder commit
+ e: wijzig commit
+ p: kies commit (wanneer midden in rebase)
+ F: creëer fixup commit voor deze commit
+ S: squash bovenstaande commits
+ ctrl+j: verplaats commit 1 naar beneden
+ ctrl+k: verplaats commit 1 naar boven
+ A: wijzig commit met staged veranderingen
+ t: commit ongedaan maken
+ ctrl+l: open log menu
+ g: reset naar deze commit
enter: bekijk gecommite bestanden
space: checkout commit
- n: creëer nieuwe branch van commit
T: tag commit
- ctrl+r: reset cherry-picked (gekopieerde) commits selectie
ctrl+y: kopieer commit bericht naar klembord
o: open commit in browser
b: view bisect options
@@ -183,7 +183,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
w: commit veranderingen zonder pre-commit hook
A: wijzig laatste commit
C: commit veranderingen met de git editor
- space: toggle staged
d: bekijk 'veranderingen ongedaan maken' opties
e: verander bestand
o: open bestand
@@ -200,6 +199,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
`: toggle bestandsboom weergave
M: open external merge tool (git mergetool)
ctrl+w: Toggle whether or not whitespace changes are shown in the diff view
+ space: toggle staged
## Bestanden Paneel (Submodules)
diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md
index 95e713826..afdcdebcd 100644
--- a/docs/keybindings/Keybindings_pl.md
+++ b/docs/keybindings/Keybindings_pl.md
@@ -123,30 +123,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Commity Panel (Commity)
- ctrl+l: open log menu
- s: ściśnij
- r: zmień nazwę commita
- R: zmień nazwę commita w edytorze
- g: zresetuj do tego commita
- f: napraw commit
- F: utwórz commit naprawczy dla tego commita
- S: spłaszcz wszystkie commity naprawcze powyżej zaznaczonych commitów (autosquash)
- d: usuń commit
- ctrl+j: przenieś commit 1 w dół
- ctrl+k: przenieś commit 1 w górę
- e: edytuj commit
- A: popraw commit zmianami z poczekalni
- p: wybierz commit (podczas zmiany bazy)
- t: odwróć commit
c: kopiuj commit (przebieranie)
ctrl+o: copy commit SHA to clipboard
C: kopiuj zakres commitów (przebieranie)
v: wklej commity (przebieranie)
+ n: create new branch off of commit
+ ctrl+r: reset cherry-picked (copied) commits selection
+ s: ściśnij
+ f: napraw commit
+ r: zmień nazwę commita
+ R: zmień nazwę commita w edytorze
+ d: usuń commit
+ e: edytuj commit
+ p: wybierz commit (podczas zmiany bazy)
+ F: utwórz commit naprawczy dla tego commita
+ S: spłaszcz wszystkie commity naprawcze powyżej zaznaczonych commitów (autosquash)
+ ctrl+j: przenieś commit 1 w dół
+ ctrl+k: przenieś commit 1 w górę
+ A: popraw commit zmianami z poczekalni
+ t: odwróć commit
+ ctrl+l: open log menu
+ g: zresetuj do tego commita
enter: przeglądaj pliki commita
space: checkout commit
- n: create new branch off of commit
T: tag commit
- ctrl+r: reset cherry-picked (copied) commits selection
ctrl+y: copy commit message to clipboard
o: open commit in browser
b: view bisect options
@@ -183,7 +183,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
w: zatwierdź zmiany bez skryptu pre-commit
A: Zmień ostatni commit
C: Zatwierdź zmiany używając edytora
- space: przełącz stan poczekalni
d: pokaż opcje porzucania zmian
e: edytuj plik
o: otwórz plik
@@ -200,6 +199,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
`: toggle file tree view
M: open external merge tool (git mergetool)
ctrl+w: Toggle whether or not whitespace changes are shown in the diff view
+ space: przełącz stan poczekalni
## Pliki Panel (Submodules)
diff --git a/docs/keybindings/Keybindings_zh.md b/docs/keybindings/Keybindings_zh.md
index 42a4fd1c7..61b2c287f 100644
--- a/docs/keybindings/Keybindings_zh.md
+++ b/docs/keybindings/Keybindings_zh.md
@@ -123,30 +123,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 提交 面板 (提交)
- ctrl+l: open log menu
- s: 向下压缩
- r: 改写提交
- R: 使用编辑器重命名提交
- g: 重置为此提交
- f: 修正提交(fixup)
- F: 为此提交创建修正
- S: 压缩在所选提交之上的所有“fixup!”提交(自动压缩)
- d: 删除提交
- ctrl+j: 下移提交
- ctrl+k: 上移提交
- e: 编辑提交
- A: 用已暂存的更改来修补提交
- p: 选择提交(变基过程中)
- t: 还原提交
c: 复制提交(拣选)
ctrl+o: 将提交的 SHA 复制到剪贴板
C: 复制提交范围(拣选)
v: 粘贴提交(拣选)
+ n: 从提交创建新分支
+ ctrl+r: 重置已拣选(复制)的提交
+ s: 向下压缩
+ f: 修正提交(fixup)
+ r: 改写提交
+ R: 使用编辑器重命名提交
+ d: 删除提交
+ e: 编辑提交
+ p: 选择提交(变基过程中)
+ F: 为此提交创建修正
+ S: 压缩在所选提交之上的所有“fixup!”提交(自动压缩)
+ ctrl+j: 下移提交
+ ctrl+k: 上移提交
+ A: 用已暂存的更改来修补提交
+ t: 还原提交
+ ctrl+l: open log menu
+ g: 重置为此提交
enter: 查看提交的文件
space: 检出提交
- n: 从提交创建新分支
T: 标签提交
- ctrl+r: 重置已拣选(复制)的提交
ctrl+y: 将提交消息复制到剪贴板
o: open commit in browser
b: view bisect options
@@ -183,7 +183,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
w: 提交更改而无需预先提交钩子
A: 修补最后一次提交
C: 提交更改(使用编辑器编辑提交信息)
- space: 切换暂存状态
d: 查看'放弃更改‘选项
e: 编辑文件
o: 打开文件
@@ -200,6 +199,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
`: 切换文件树视图
M: 打开合并工具
ctrl+w: 切换是否在差异视图中显示空白更改
+ space: 切换暂存状态
## 文件 面板 (子模块)
diff --git a/pkg/cheatsheet/generate.go b/pkg/cheatsheet/generate.go
index 546239df5..3d1b5efcf 100644
--- a/pkg/cheatsheet/generate.go
+++ b/pkg/cheatsheet/generate.go
@@ -17,13 +17,14 @@ import (
"github.com/jesseduffield/lazygit/pkg/app"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/integration"
)
type bindingSection struct {
title string
- bindings []*gui.Binding
+ bindings []*types.Binding
}
func CommandToRun() string {
@@ -113,7 +114,7 @@ func formatTitle(title string) string {
return fmt.Sprintf("\n## %s\n\n", title)
}
-func formatBinding(binding *gui.Binding) string {
+func formatBinding(binding *types.Binding) string {
if binding.Alternative != "" {
return fmt.Sprintf(" %s: %s (%s)\n", gui.GetKeyDisplay(binding.Key), binding.Description, binding.Alternative)
}
@@ -130,7 +131,7 @@ func getBindingSections(mApp *app.App) []*bindingSection {
title string
}
- contextAndViewBindingMap := map[contextAndViewType][]*gui.Binding{}
+ contextAndViewBindingMap := map[contextAndViewType][]*types.Binding{}
outer:
for _, binding := range bindings {
@@ -138,7 +139,7 @@ outer:
key := contextAndViewType{subtitle: "", title: "navigation"}
existing := contextAndViewBindingMap[key]
if existing == nil {
- contextAndViewBindingMap[key] = []*gui.Binding{binding}
+ contextAndViewBindingMap[key] = []*types.Binding{binding}
} else {
for _, navBinding := range contextAndViewBindingMap[key] {
if navBinding.Description == binding.Description {
@@ -162,7 +163,7 @@ outer:
key := contextAndViewType{subtitle: context, title: binding.ViewName}
existing := contextAndViewBindingMap[key]
if existing == nil {
- contextAndViewBindingMap[key] = []*gui.Binding{binding}
+ contextAndViewBindingMap[key] = []*types.Binding{binding}
} else {
contextAndViewBindingMap[key] = append(contextAndViewBindingMap[key], binding)
}
@@ -171,7 +172,7 @@ outer:
type groupedBindingsType struct {
contextAndView contextAndViewType
- bindings []*gui.Binding
+ bindings []*types.Binding
}
groupedBindings := make([]groupedBindingsType, len(contextAndViewBindingMap))
@@ -227,7 +228,7 @@ outer:
return bindingSections
}
-func addBinding(title string, bindingSections []*bindingSection, binding *gui.Binding) []*bindingSection {
+func addBinding(title string, bindingSections []*bindingSection, binding *types.Binding) []*bindingSection {
if binding.Description == "" && binding.Alternative == "" {
return bindingSections
}
@@ -241,7 +242,7 @@ func addBinding(title string, bindingSections []*bindingSection, binding *gui.Bi
section := &bindingSection{
title: title,
- bindings: []*gui.Binding{binding},
+ bindings: []*types.Binding{binding},
}
return append(bindingSections, section)
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index f6812e254..3880e0dfc 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strings"
+ "sync"
"github.com/go-errors/errors"
@@ -56,6 +57,7 @@ func NewGitCommand(
cmn *common.Common,
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
+ syncMutex *sync.Mutex,
) (*GitCommand, error) {
if err := navigateToRepoRootDirectory(os.Stat, os.Chdir); err != nil {
return nil, err
@@ -77,6 +79,7 @@ func NewGitCommand(
gitConfig,
dotGitDir,
repo,
+ syncMutex,
), nil
}
@@ -86,6 +89,7 @@ func NewGitCommandAux(
gitConfig git_config.IGitConfig,
dotGitDir string,
repo *gogit.Repository,
+ syncMutex *sync.Mutex,
) *GitCommand {
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
@@ -95,7 +99,7 @@ func NewGitCommandAux(
// on the one struct.
// common ones are: cmn, osCommand, dotGitDir, configCommands
configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo)
- gitCommon := git_commands.NewGitCommon(cmn, cmd, osCommand, dotGitDir, repo, configCommands)
+ gitCommon := git_commands.NewGitCommon(cmn, cmd, osCommand, dotGitDir, repo, configCommands, syncMutex)
statusCommands := git_commands.NewStatusCommands(gitCommon)
fileLoader := loaders.NewFileLoader(cmn, cmd, configCommands)
diff --git a/pkg/commands/git_commands/common.go b/pkg/commands/git_commands/common.go
index a045be75a..85f0d2118 100644
--- a/pkg/commands/git_commands/common.go
+++ b/pkg/commands/git_commands/common.go
@@ -1,6 +1,8 @@
package git_commands
import (
+ "sync"
+
gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
@@ -13,6 +15,8 @@ type GitCommon struct {
dotGitDir string
repo *gogit.Repository
config *ConfigCommands
+ // mutex for doing things like push/pull/fetch
+ syncMutex *sync.Mutex
}
func NewGitCommon(
@@ -22,6 +26,7 @@ func NewGitCommon(
dotGitDir string,
repo *gogit.Repository,
config *ConfigCommands,
+ syncMutex *sync.Mutex,
) *GitCommon {
return &GitCommon{
Common: cmn,
@@ -30,5 +35,6 @@ func NewGitCommon(
dotGitDir: dotGitDir,
repo: repo,
config: config,
+ syncMutex: syncMutex,
}
}
diff --git a/pkg/commands/git_commands/remote.go b/pkg/commands/git_commands/remote.go
index 3116c764a..1245a8cf0 100644
--- a/pkg/commands/git_commands/remote.go
+++ b/pkg/commands/git_commands/remote.go
@@ -40,7 +40,7 @@ func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string
func (self *RemoteCommands) DeleteRemoteBranch(remoteName string, branchName string) error {
command := fmt.Sprintf("git push %s --delete %s", self.cmd.Quote(remoteName), self.cmd.Quote(branchName))
- return self.cmd.New(command).PromptOnCredentialRequest().Run()
+ return self.cmd.New(command).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
}
// CheckRemoteBranchExists Returns remote branch
diff --git a/pkg/commands/git_commands/sync.go b/pkg/commands/git_commands/sync.go
index 8a6933522..fb1aa9648 100644
--- a/pkg/commands/git_commands/sync.go
+++ b/pkg/commands/git_commands/sync.go
@@ -47,7 +47,7 @@ func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error)
cmdStr += " " + self.cmd.Quote(opts.UpstreamBranch)
}
- cmdObj := self.cmd.New(cmdStr).PromptOnCredentialRequest()
+ cmdObj := self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex)
return cmdObj, nil
}
@@ -83,7 +83,7 @@ func (self *SyncCommands) Fetch(opts FetchOptions) error {
} else {
cmdObj.PromptOnCredentialRequest()
}
- return cmdObj.Run()
+ return cmdObj.WithMutex(self.syncMutex).Run()
}
type PullOptions struct {
@@ -108,15 +108,15 @@ func (self *SyncCommands) Pull(opts PullOptions) error {
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
// has 'pull.rebase = interactive' configured.
- return self.cmd.New(cmdStr).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest().Run()
+ return self.cmd.New(cmdStr).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
}
func (self *SyncCommands) FastForward(branchName string, remoteName string, remoteBranchName string) error {
cmdStr := fmt.Sprintf("git fetch %s %s:%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName), self.cmd.Quote(branchName))
- return self.cmd.New(cmdStr).PromptOnCredentialRequest().Run()
+ return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
}
func (self *SyncCommands) FetchRemote(remoteName string) error {
cmdStr := fmt.Sprintf("git fetch %s", self.cmd.Quote(remoteName))
- return self.cmd.New(cmdStr).PromptOnCredentialRequest().Run()
+ return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
}
diff --git a/pkg/commands/git_commands/tag.go b/pkg/commands/git_commands/tag.go
index 94b0d8ac1..5abad0dc5 100644
--- a/pkg/commands/git_commands/tag.go
+++ b/pkg/commands/git_commands/tag.go
@@ -27,5 +27,5 @@ func (self *TagCommands) Delete(tagName string) error {
}
func (self *TagCommands) Push(remoteName string, tagName string) error {
- return self.cmd.New(fmt.Sprintf("git push %s %s", self.cmd.Quote(remoteName), self.cmd.Quote(tagName))).PromptOnCredentialRequest().Run()
+ return self.cmd.New(fmt.Sprintf("git push %s %s", self.cmd.Quote(remoteName), self.cmd.Quote(tagName))).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
}
diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go
index 684696a8c..77436130d 100644
--- a/pkg/commands/git_test.go
+++ b/pkg/commands/git_test.go
@@ -3,6 +3,7 @@ package commands
import (
"fmt"
"os"
+ "sync"
"testing"
"time"
@@ -211,7 +212,12 @@ func TestNewGitCommand(t *testing.T) {
s := s
t.Run(s.testName, func(t *testing.T) {
s.setup()
- s.test(NewGitCommand(utils.NewDummyCommon(), oscommands.NewDummyOSCommand(), git_config.NewFakeGitConfig(nil)))
+ s.test(
+ NewGitCommand(utils.NewDummyCommon(),
+ oscommands.NewDummyOSCommand(),
+ git_config.NewFakeGitConfig(nil),
+ &sync.Mutex{},
+ ))
})
}
}
diff --git a/pkg/commands/oscommands/cmd_obj.go b/pkg/commands/oscommands/cmd_obj.go
index 3e55359de..7960bfa99 100644
--- a/pkg/commands/oscommands/cmd_obj.go
+++ b/pkg/commands/oscommands/cmd_obj.go
@@ -2,6 +2,7 @@ package oscommands
import (
"os/exec"
+ "sync"
)
// A command object is a general way to represent a command to be run on the
@@ -50,6 +51,9 @@ type ICmdObj interface {
PromptOnCredentialRequest() ICmdObj
FailOnCredentialRequest() ICmdObj
+ WithMutex(mutex *sync.Mutex) ICmdObj
+ Mutex() *sync.Mutex
+
GetCredentialStrategy() CredentialStrategy
}
@@ -70,6 +74,9 @@ type CmdObj struct {
// if set to true, it means we might be asked to enter a username/password by this command.
credentialStrategy CredentialStrategy
+
+ // can be set so that we don't run certain commands simultaneously
+ mutex *sync.Mutex
}
type CredentialStrategy int
@@ -132,6 +139,16 @@ func (self *CmdObj) IgnoreEmptyError() ICmdObj {
return self
}
+func (self *CmdObj) Mutex() *sync.Mutex {
+ return self.mutex
+}
+
+func (self *CmdObj) WithMutex(mutex *sync.Mutex) ICmdObj {
+ self.mutex = mutex
+
+ return self
+}
+
func (self *CmdObj) ShouldIgnoreEmptyError() bool {
return self.ignoreEmptyError
}
diff --git a/pkg/commands/oscommands/cmd_obj_runner.go b/pkg/commands/oscommands/cmd_obj_runner.go
index e1a38d80f..9522bc627 100644
--- a/pkg/commands/oscommands/cmd_obj_runner.go
+++ b/pkg/commands/oscommands/cmd_obj_runner.go
@@ -34,6 +34,11 @@ type cmdObjRunner struct {
var _ ICmdObjRunner = &cmdObjRunner{}
func (self *cmdObjRunner) Run(cmdObj ICmdObj) error {
+ if cmdObj.Mutex() != nil {
+ cmdObj.Mutex().Lock()
+ defer cmdObj.Mutex().Unlock()
+ }
+
if cmdObj.GetCredentialStrategy() != NONE {
return self.runWithCredentialHandling(cmdObj)
}
@@ -42,17 +47,14 @@ func (self *cmdObjRunner) Run(cmdObj ICmdObj) error {
return self.runAndStream(cmdObj)
}
- _, err := self.RunWithOutput(cmdObj)
+ _, err := self.RunWithOutputAux(cmdObj)
return err
}
func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
- if cmdObj.ShouldStreamOutput() {
- err := self.runAndStream(cmdObj)
- // for now we're not capturing output, just because it would take a little more
- // effort and there's currently no use case for it. Some commands call RunWithOutput
- // but ignore the output, hence why we've got this check here.
- return "", err
+ if cmdObj.Mutex() != nil {
+ cmdObj.Mutex().Lock()
+ defer cmdObj.Mutex().Unlock()
}
if cmdObj.GetCredentialStrategy() != NONE {
@@ -63,6 +65,18 @@ func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
return "", err
}
+ if cmdObj.ShouldStreamOutput() {
+ err := self.runAndStream(cmdObj)
+ // for now we're not capturing output, just because it would take a little more
+ // effort and there's currently no use case for it. Some commands call RunWithOutput
+ // but ignore the output, hence why we've got this check here.
+ return "", err
+ }
+
+ return self.RunWithOutputAux(cmdObj)
+}
+
+func (self *cmdObjRunner) RunWithOutputAux(cmdObj ICmdObj) (string, error) {
self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand")
if cmdObj.ShouldLog() {
@@ -77,6 +91,11 @@ func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
}
func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
+ if cmdObj.Mutex() != nil {
+ cmdObj.Mutex().Lock()
+ defer cmdObj.Mutex().Unlock()
+ }
+
if cmdObj.GetCredentialStrategy() != NONE {
return errors.New("cannot call RunAndProcessLines with credential strategy. If you're seeing this then a contributor to Lazygit has accidentally called this method! Please raise an issue")
}
diff --git a/pkg/gui/app_status_manager.go b/pkg/gui/app_status_manager.go
index 825bb8801..4c32f79b5 100644
--- a/pkg/gui/app_status_manager.go
+++ b/pkg/gui/app_status_manager.go
@@ -83,7 +83,7 @@ func (m *statusManager) getStatusString() string {
return topStatus.message
}
-func (gui *Gui) raiseToast(message string) {
+func (gui *Gui) toast(message string) {
gui.statusManager.addToastStatus(message)
gui.renderAppStatus()
@@ -119,7 +119,7 @@ func (gui *Gui) withWaitingStatus(message string, f func() error) error {
if err := f(); err != nil {
gui.OnUIThread(func() error {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
})
}
})
diff --git a/pkg/gui/arrangement.go b/pkg/gui/arrangement.go
index fa2e7f29d..944391d75 100644
--- a/pkg/gui/arrangement.go
+++ b/pkg/gui/arrangement.go
@@ -2,6 +2,7 @@ package gui
import (
"github.com/jesseduffield/lazygit/pkg/gui/boxlayout"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -44,7 +45,7 @@ func (gui *Gui) getMidSectionWeights() (int, int) {
currentWindow := gui.currentWindow()
// we originally specified this as a ratio i.e. .20 would correspond to a weight of 1 against 4
- sidePanelWidthRatio := gui.UserConfig.Gui.SidePanelWidth
+ sidePanelWidthRatio := gui.c.UserConfig.Gui.SidePanelWidth
// we could make this better by creating ratios like 2:3 rather than always 1:something
mainSectionWeight := int(1/sidePanelWidthRatio) - 1
sideSectionWeight := 1
@@ -115,7 +116,7 @@ func (gui *Gui) splitMainPanelSideBySide() bool {
return false
}
- mainPanelSplitMode := gui.UserConfig.Gui.MainPanelSplitMode
+ mainPanelSplitMode := gui.c.UserConfig.Gui.MainPanelSplitMode
width, height := gui.g.Size()
switch mainPanelSplitMode {
@@ -143,7 +144,7 @@ func (gui *Gui) getExtrasWindowSize(screenHeight int) int {
} else if screenHeight < 40 {
baseSize = 1
} else {
- baseSize = gui.UserConfig.Gui.CommandLogSize
+ baseSize = gui.c.UserConfig.Gui.CommandLogSize
}
frameSize := 2
@@ -259,7 +260,7 @@ func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box {
fullHeightBox("stash"),
}
} else if height >= 28 {
- accordionMode := gui.UserConfig.Gui.ExpandFocusedSidePanel
+ accordionMode := gui.c.UserConfig.Gui.ExpandFocusedSidePanel
accordionBox := func(defaultBox *boxlayout.Box) *boxlayout.Box {
if accordionMode && defaultBox.Window == currentWindow {
return &boxlayout.Box{
@@ -320,7 +321,7 @@ func (gui *Gui) currentSideWindowName() string {
reversedIdx := len(gui.State.ContextManager.ContextStack) - 1 - idx
context := gui.State.ContextManager.ContextStack[reversedIdx]
- if context.GetKind() == SIDE_CONTEXT {
+ if context.GetKind() == types.SIDE_CONTEXT {
return context.GetWindowName()
}
}
diff --git a/pkg/gui/basic_context.go b/pkg/gui/basic_context.go
index 1db80ee4a..1043cca89 100644
--- a/pkg/gui/basic_context.go
+++ b/pkg/gui/basic_context.go
@@ -1,22 +1,28 @@
package gui
+import (
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+)
+
type BasicContext struct {
- OnFocus func(opts ...OnFocusOpts) error
+ OnFocus func(opts ...types.OnFocusOpts) error
OnFocusLost func() error
OnRender func() error
// this is for pushing some content to the main view
- OnRenderToMain func(opts ...OnFocusOpts) error
- Kind ContextKind
- Key ContextKey
+ OnRenderToMain func(opts ...types.OnFocusOpts) error
+ Kind types.ContextKind
+ Key types.ContextKey
ViewName string
WindowName string
OnGetOptionsMap func() map[string]string
- ParentContext Context
+ ParentContext types.Context
// we can't know on the calling end whether a Context is actually a nil value without reflection, so we're storing this flag here to tell us. There has got to be a better way around this
hasParent bool
}
+var _ types.Context = &BasicContext{}
+
func (self *BasicContext) GetOptionsMap() map[string]string {
if self.OnGetOptionsMap != nil {
return self.OnGetOptionsMap()
@@ -24,12 +30,12 @@ func (self *BasicContext) GetOptionsMap() map[string]string {
return nil
}
-func (self *BasicContext) SetParentContext(context Context) {
+func (self *BasicContext) SetParentContext(context types.Context) {
self.ParentContext = context
self.hasParent = true
}
-func (self *BasicContext) GetParentContext() (Context, bool) {
+func (self *BasicContext) GetParentContext() (types.Context, bool) {
return self.ParentContext, self.hasParent
}
@@ -59,7 +65,7 @@ func (self *BasicContext) GetViewName() string {
return self.ViewName
}
-func (self *BasicContext) HandleFocus(opts ...OnFocusOpts) error {
+func (self *BasicContext) HandleFocus(opts ...types.OnFocusOpts) error {
if self.OnFocus != nil {
if err := self.OnFocus(opts...); err != nil {
return err
@@ -90,10 +96,10 @@ func (self *BasicContext) HandleRenderToMain() error {
return nil
}
-func (self *BasicContext) GetKind() ContextKind {
+func (self *BasicContext) GetKind() types.ContextKind {
return self.Kind
}
-func (self *BasicContext) GetKey() ContextKey {
+func (self *BasicContext) GetKey() types.ContextKey {
return self.Key
}
diff --git a/pkg/gui/bisect.go b/pkg/gui/bisect.go
deleted file mode 100644
index 5c46460ac..000000000
--- a/pkg/gui/bisect.go
+++ /dev/null
@@ -1,219 +0,0 @@
-package gui
-
-import (
- "fmt"
- "strings"
-
- "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
- "github.com/jesseduffield/lazygit/pkg/commands/models"
-)
-
-func (gui *Gui) handleOpenBisectMenu() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
- // no shame in getting this directly rather than using the cached value
- // given how cheap it is to obtain
- info := gui.Git.Bisect.GetInfo()
- commit := gui.getSelectedLocalCommit()
- if info.Started() {
- return gui.openMidBisectMenu(info, commit)
- } else {
- return gui.openStartBisectMenu(info, commit)
- }
-}
-
-func (gui *Gui) openMidBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error {
- // if there is not yet a 'current' bisect commit, or if we have
- // selected the current commit, we need to jump to the next 'current' commit
- // after we perform a bisect action. The reason we don't unconditionally jump
- // is that sometimes the user will want to go and mark a few commits as skipped
- // in a row and they wouldn't want to be jumped back to the current bisect
- // commit each time.
- // Originally we were allowing the user to, from the bisect menu, select whether
- // they were talking about the selected commit or the current bisect commit,
- // and that was a bit confusing (and required extra keypresses).
- selectCurrentAfter := info.GetCurrentSha() == "" || info.GetCurrentSha() == commit.Sha
- // we need to wait to reselect if our bisect commits aren't ancestors of our 'start'
- // ref, because we'll be reloading our commits in that case.
- waitToReselect := selectCurrentAfter && !gui.Git.Bisect.ReachableFromStart(info)
-
- menuItems := []*menuItem{
- {
- displayString: fmt.Sprintf(gui.Tr.Bisect.Mark, commit.ShortSha(), info.NewTerm()),
- onPress: func() error {
- gui.logAction(gui.Tr.Actions.BisectMark)
- if err := gui.Git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
- return gui.surfaceError(err)
- }
-
- return gui.afterMark(selectCurrentAfter, waitToReselect)
- },
- },
- {
- displayString: fmt.Sprintf(gui.Tr.Bisect.Mark, commit.ShortSha(), info.OldTerm()),
- onPress: func() error {
- gui.logAction(gui.Tr.Actions.BisectMark)
- if err := gui.Git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
- return gui.surfaceError(err)
- }
-
- return gui.afterMark(selectCurrentAfter, waitToReselect)
- },
- },
- {
- displayString: fmt.Sprintf(gui.Tr.Bisect.Skip, commit.ShortSha()),
- onPress: func() error {
- gui.logAction(gui.Tr.Actions.BisectSkip)
- if err := gui.Git.Bisect.Skip(commit.Sha); err != nil {
- return gui.surfaceError(err)
- }
-
- return gui.afterMark(selectCurrentAfter, waitToReselect)
- },
- },
- {
- displayString: gui.Tr.Bisect.ResetOption,
- onPress: func() error {
- return gui.resetBisect()
- },
- },
- }
-
- return gui.createMenu(
- gui.Tr.Bisect.BisectMenuTitle,
- menuItems,
- createMenuOptions{showCancel: true},
- )
-}
-
-func (gui *Gui) openStartBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error {
- return gui.createMenu(
- gui.Tr.Bisect.BisectMenuTitle,
- []*menuItem{
- {
- displayString: fmt.Sprintf(gui.Tr.Bisect.MarkStart, commit.ShortSha(), info.NewTerm()),
- onPress: func() error {
- gui.logAction(gui.Tr.Actions.StartBisect)
- if err := gui.Git.Bisect.Start(); err != nil {
- return gui.surfaceError(err)
- }
-
- if err := gui.Git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
- return gui.surfaceError(err)
- }
-
- return gui.postBisectCommandRefresh()
- },
- },
- {
- displayString: fmt.Sprintf(gui.Tr.Bisect.MarkStart, commit.ShortSha(), info.OldTerm()),
- onPress: func() error {
- gui.logAction(gui.Tr.Actions.StartBisect)
- if err := gui.Git.Bisect.Start(); err != nil {
- return gui.surfaceError(err)
- }
-
- if err := gui.Git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
- return gui.surfaceError(err)
- }
-
- return gui.postBisectCommandRefresh()
- },
- },
- },
- createMenuOptions{showCancel: true},
- )
-}
-
-func (gui *Gui) resetBisect() error {
- return gui.ask(askOpts{
- title: gui.Tr.Bisect.ResetTitle,
- prompt: gui.Tr.Bisect.ResetPrompt,
- handleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.ResetBisect)
- if err := gui.Git.Bisect.Reset(); err != nil {
- return gui.surfaceError(err)
- }
-
- return gui.postBisectCommandRefresh()
- },
- })
-}
-
-func (gui *Gui) showBisectCompleteMessage(candidateShas []string) error {
- prompt := gui.Tr.Bisect.CompletePrompt
- if len(candidateShas) > 1 {
- prompt = gui.Tr.Bisect.CompletePromptIndeterminate
- }
-
- formattedCommits, err := gui.Git.Commit.GetCommitsOneline(candidateShas)
- if err != nil {
- return gui.surfaceError(err)
- }
-
- return gui.ask(askOpts{
- title: gui.Tr.Bisect.CompleteTitle,
- prompt: fmt.Sprintf(prompt, strings.TrimSpace(formattedCommits)),
- handleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.ResetBisect)
- if err := gui.Git.Bisect.Reset(); err != nil {
- return gui.surfaceError(err)
- }
-
- return gui.postBisectCommandRefresh()
- },
- })
-}
-
-func (gui *Gui) afterMark(selectCurrent bool, waitToReselect bool) error {
- done, candidateShas, err := gui.Git.Bisect.IsDone()
- if err != nil {
- return gui.surfaceError(err)
- }
-
- if err := gui.afterBisectMarkRefresh(selectCurrent, waitToReselect); err != nil {
- return gui.surfaceError(err)
- }
-
- if done {
- return gui.showBisectCompleteMessage(candidateShas)
- }
-
- return nil
-}
-
-func (gui *Gui) postBisectCommandRefresh() error {
- return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{}})
-}
-
-func (gui *Gui) afterBisectMarkRefresh(selectCurrent bool, waitToReselect bool) error {
- selectFn := func() {
- if selectCurrent {
- gui.selectCurrentBisectCommit()
- }
- }
-
- if waitToReselect {
- return gui.refreshSidePanels(refreshOptions{mode: SYNC, scope: []RefreshableView{}, then: selectFn})
- } else {
- selectFn()
-
- return gui.postBisectCommandRefresh()
- }
-}
-
-func (gui *Gui) selectCurrentBisectCommit() {
- info := gui.Git.Bisect.GetInfo()
- if info.GetCurrentSha() != "" {
- // find index of commit with that sha, move cursor to that.
- for i, commit := range gui.State.Commits {
- if commit.Sha == info.GetCurrentSha() {
- gui.State.Contexts.BranchCommits.GetPanelState().SetSelectedLineIdx(i)
- _ = gui.State.Contexts.BranchCommits.HandleFocus()
- break
- }
- }
- }
-}
diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go
index dca0dc8f0..18094b297 100644
--- a/pkg/gui/branches_panel.go
+++ b/pkg/gui/branches_panel.go
@@ -7,6 +7,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/gui/controllers"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -31,9 +32,9 @@ func (gui *Gui) branchesRenderToMain() error {
var task updateTask
branch := gui.getSelectedBranch()
if branch == nil {
- task = NewRenderStringTask(gui.Tr.NoBranchesThisRepo)
+ task = NewRenderStringTask(gui.c.Tr.NoBranchesThisRepo)
} else {
- cmdObj := gui.Git.Branch.GetGraphCmdObj(branch.Name)
+ cmdObj := gui.git.Branch.GetGraphCmdObj(branch.Name)
task = NewRunPtyTask(cmdObj.GetCmd())
}
@@ -56,21 +57,21 @@ func (gui *Gui) refreshBranches() {
// which allows us to order them correctly. So if we're filtering we'll just
// manually load all the reflog commits here
var err error
- reflogCommits, _, err = gui.Git.Loaders.ReflogCommits.GetReflogCommits(nil, "")
+ reflogCommits, _, err = gui.git.Loaders.ReflogCommits.GetReflogCommits(nil, "")
if err != nil {
- gui.Log.Error(err)
+ gui.c.Log.Error(err)
}
}
- branches, err := gui.Git.Loaders.Branches.Load(reflogCommits)
+ branches, err := gui.git.Loaders.Branches.Load(reflogCommits)
if err != nil {
- _ = gui.PopupHandler.Error(err)
+ _ = gui.c.Error(err)
}
gui.State.Branches = branches
- if err := gui.postRefreshUpdate(gui.State.Contexts.Branches); err != nil {
- gui.Log.Error(err)
+ if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Branches); err != nil {
+ gui.c.Log.Error(err)
}
gui.refreshStatus()
@@ -83,11 +84,11 @@ func (gui *Gui) handleBranchPress() error {
return nil
}
if gui.State.Panels.Branches.SelectedLineIdx == 0 {
- return gui.PopupHandler.ErrorMsg(gui.Tr.AlreadyCheckedOutBranch)
+ return gui.c.ErrorMsg(gui.c.Tr.AlreadyCheckedOutBranch)
}
branch := gui.getSelectedBranch()
- gui.logAction(gui.Tr.Actions.CheckoutBranch)
- return gui.handleCheckoutRef(branch.Name, handleCheckoutRefOptions{})
+ gui.c.LogAction(gui.c.Tr.Actions.CheckoutBranch)
+ return gui.refHelper.CheckoutRef(branch.Name, types.CheckoutRefOptions{})
}
func (gui *Gui) handleCreatePullRequestPress() error {
@@ -110,129 +111,64 @@ func (gui *Gui) handleCopyPullRequestURLPress() error {
branch := gui.getSelectedBranch()
- branchExistsOnRemote := gui.Git.Remote.CheckRemoteBranchExists(branch.Name)
+ branchExistsOnRemote := gui.git.Remote.CheckRemoteBranchExists(branch.Name)
if !branchExistsOnRemote {
- return gui.PopupHandler.Error(errors.New(gui.Tr.NoBranchOnRemote))
+ return gui.c.Error(errors.New(gui.c.Tr.NoBranchOnRemote))
}
url, err := hostingServiceMgr.GetPullRequestURL(branch.Name, "")
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
- gui.logAction(gui.Tr.Actions.CopyPullRequestURL)
+ gui.c.LogAction(gui.c.Tr.Actions.CopyPullRequestURL)
if err := gui.OSCommand.CopyToClipboard(url); err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
- gui.raiseToast(gui.Tr.PullRequestURLCopiedToClipboard)
+ gui.c.Toast(gui.c.Tr.PullRequestURLCopiedToClipboard)
return nil
}
func (gui *Gui) handleGitFetch() error {
- return gui.PopupHandler.WithLoaderPanel(gui.Tr.FetchWait, func() error {
+ return gui.c.WithLoaderPanel(gui.c.Tr.FetchWait, func() error {
if err := gui.fetch(); err != nil {
- _ = gui.PopupHandler.Error(err)
+ _ = gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
})
}
func (gui *Gui) handleForceCheckout() error {
branch := gui.getSelectedBranch()
- message := gui.Tr.SureForceCheckout
- title := gui.Tr.ForceCheckoutBranch
+ message := gui.c.Tr.SureForceCheckout
+ title := gui.c.Tr.ForceCheckoutBranch
- return gui.PopupHandler.Ask(popup.AskOpts{
+ return gui.c.Ask(popup.AskOpts{
Title: title,
Prompt: message,
HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.ForceCheckoutBranch)
- if err := gui.Git.Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
- _ = gui.PopupHandler.Error(err)
+ gui.c.LogAction(gui.c.Tr.Actions.ForceCheckoutBranch)
+ if err := gui.git.Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
+ _ = gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
},
})
}
-type handleCheckoutRefOptions struct {
- WaitingStatus string
- EnvVars []string
- onRefNotFound func(ref string) error
-}
-
-func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions) error {
- waitingStatus := options.WaitingStatus
- if waitingStatus == "" {
- waitingStatus = gui.Tr.CheckingOutStatus
- }
-
- cmdOptions := git_commands.CheckoutOptions{Force: false, EnvVars: options.EnvVars}
-
- onSuccess := func() {
- gui.State.Panels.Branches.SelectedLineIdx = 0
- gui.State.Panels.Commits.SelectedLineIdx = 0
- // loading a heap of commits is slow so we limit them whenever doing a reset
- gui.State.Panels.Commits.LimitCommits = true
- }
-
- return gui.PopupHandler.WithWaitingStatus(waitingStatus, func() error {
- if err := gui.Git.Branch.Checkout(ref, cmdOptions); err != nil {
- // note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option
-
- if options.onRefNotFound != nil && strings.Contains(err.Error(), "did not match any file(s) known to git") {
- return options.onRefNotFound(ref)
- }
-
- if strings.Contains(err.Error(), "Please commit your changes or stash them before you switch branch") {
- // offer to autostash changes
- return gui.PopupHandler.Ask(popup.AskOpts{
-
- Title: gui.Tr.AutoStashTitle,
- Prompt: gui.Tr.AutoStashPrompt,
- HandleConfirm: func() error {
- if err := gui.Git.Stash.Save(gui.Tr.StashPrefix + ref); err != nil {
- return gui.PopupHandler.Error(err)
- }
- if err := gui.Git.Branch.Checkout(ref, cmdOptions); err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- onSuccess()
- if err := gui.Git.Stash.Pop(0); err != nil {
- if err := gui.refreshSidePanels(types.RefreshOptions{Mode: types.BLOCK_UI}); err != nil {
- return err
- }
- return gui.PopupHandler.Error(err)
- }
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.BLOCK_UI})
- },
- })
- }
-
- if err := gui.PopupHandler.Error(err); err != nil {
- return err
- }
- }
- onSuccess()
-
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.BLOCK_UI})
- })
-}
-
func (gui *Gui) handleCheckoutByName() error {
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- Title: gui.Tr.BranchName + ":",
- FindSuggestionsFunc: gui.getRefsSuggestionsFunc(),
+ return gui.c.Prompt(popup.PromptOpts{
+ Title: gui.c.Tr.BranchName + ":",
+ FindSuggestionsFunc: gui.suggestionsHelper.GetRefsSuggestionsFunc(),
HandleConfirm: func(response string) error {
- gui.logAction("Checkout branch")
- return gui.handleCheckoutRef(response, handleCheckoutRefOptions{
- onRefNotFound: func(ref string) error {
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.BranchNotFoundTitle,
- Prompt: fmt.Sprintf("%s %s%s", gui.Tr.BranchNotFoundPrompt, ref, "?"),
+ gui.c.LogAction("Checkout branch")
+ return gui.refHelper.CheckoutRef(response, types.CheckoutRefOptions{
+ OnRefNotFound: func(ref string) error {
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.BranchNotFoundTitle,
+ Prompt: fmt.Sprintf("%s %s%s", gui.c.Tr.BranchNotFoundPrompt, ref, "?"),
HandleConfirm: func() error {
return gui.createNewBranchWithName(ref)
},
@@ -257,12 +193,12 @@ func (gui *Gui) createNewBranchWithName(newBranchName string) error {
return nil
}
- if err := gui.Git.Branch.New(newBranchName, branch.Name); err != nil {
- return gui.PopupHandler.Error(err)
+ if err := gui.git.Branch.New(newBranchName, branch.Name); err != nil {
+ return gui.c.Error(err)
}
gui.State.Panels.Branches.SelectedLineIdx = 0
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
}
func (gui *Gui) handleDeleteBranch() error {
@@ -276,18 +212,18 @@ func (gui *Gui) deleteBranch(force bool) error {
}
checkedOutBranch := gui.getCheckedOutBranch()
if checkedOutBranch.Name == selectedBranch.Name {
- return gui.PopupHandler.ErrorMsg(gui.Tr.CantDeleteCheckOutBranch)
+ return gui.c.ErrorMsg(gui.c.Tr.CantDeleteCheckOutBranch)
}
return gui.deleteNamedBranch(selectedBranch, force)
}
func (gui *Gui) deleteNamedBranch(selectedBranch *models.Branch, force bool) error {
- title := gui.Tr.DeleteBranch
+ title := gui.c.Tr.DeleteBranch
var templateStr string
if force {
- templateStr = gui.Tr.ForceDeleteBranchMessage
+ templateStr = gui.c.Tr.ForceDeleteBranchMessage
} else {
- templateStr = gui.Tr.DeleteBranchMessage
+ templateStr = gui.c.Tr.DeleteBranchMessage
}
message := utils.ResolvePlaceholderString(
templateStr,
@@ -296,59 +232,51 @@ func (gui *Gui) deleteNamedBranch(selectedBranch *models.Branch, force bool) err
},
)
- return gui.PopupHandler.Ask(popup.AskOpts{
+ return gui.c.Ask(popup.AskOpts{
Title: title,
Prompt: message,
HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.DeleteBranch)
- if err := gui.Git.Branch.Delete(selectedBranch.Name, force); err != nil {
+ gui.c.LogAction(gui.c.Tr.Actions.DeleteBranch)
+ if err := gui.git.Branch.Delete(selectedBranch.Name, force); err != nil {
errMessage := err.Error()
if !force && strings.Contains(errMessage, "git branch -D ") {
return gui.deleteNamedBranch(selectedBranch, true)
}
- return gui.PopupHandler.ErrorMsg(errMessage)
+ return gui.c.ErrorMsg(errMessage)
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
},
})
}
func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
- if gui.Git.Branch.IsHeadDetached() {
- return gui.PopupHandler.ErrorMsg("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")
+ if gui.git.Branch.IsHeadDetached() {
+ return gui.c.ErrorMsg("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")
}
checkedOutBranchName := gui.getCheckedOutBranch().Name
if checkedOutBranchName == branchName {
- return gui.PopupHandler.ErrorMsg(gui.Tr.CantMergeBranchIntoItself)
+ return gui.c.ErrorMsg(gui.c.Tr.CantMergeBranchIntoItself)
}
prompt := utils.ResolvePlaceholderString(
- gui.Tr.ConfirmMerge,
+ gui.c.Tr.ConfirmMerge,
map[string]string{
"checkedOutBranch": checkedOutBranchName,
"selectedBranch": branchName,
},
)
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.MergingTitle,
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.MergingTitle,
Prompt: prompt,
HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.Merge)
- err := gui.Git.Branch.Merge(branchName, git_commands.MergeOpts{})
- return gui.handleGenericMergeCommandResult(err)
+ gui.c.LogAction(gui.c.Tr.Actions.Merge)
+ err := gui.git.Branch.Merge(branchName, git_commands.MergeOpts{})
+ return gui.checkMergeOrRebase(err)
},
})
}
func (gui *Gui) handleMerge() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
selectedBranchName := gui.getSelectedBranch().Name
return gui.mergeBranchIntoCheckedOutBranch(selectedBranchName)
}
@@ -359,29 +287,25 @@ func (gui *Gui) handleRebaseOntoLocalBranch() error {
}
func (gui *Gui) handleRebaseOntoBranch(selectedBranchName string) error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
checkedOutBranch := gui.getCheckedOutBranch().Name
if selectedBranchName == checkedOutBranch {
- return gui.PopupHandler.ErrorMsg(gui.Tr.CantRebaseOntoSelf)
+ return gui.c.ErrorMsg(gui.c.Tr.CantRebaseOntoSelf)
}
prompt := utils.ResolvePlaceholderString(
- gui.Tr.ConfirmRebase,
+ gui.c.Tr.ConfirmRebase,
map[string]string{
"checkedOutBranch": checkedOutBranch,
"selectedBranch": selectedBranchName,
},
)
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.RebasingTitle,
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.RebasingTitle,
Prompt: prompt,
HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.RebaseBranch)
- err := gui.Git.Rebase.RebaseBranch(selectedBranchName)
- return gui.handleGenericMergeCommandResult(err)
+ gui.c.LogAction(gui.c.Tr.Actions.RebaseBranch)
+ err := gui.git.Rebase.RebaseBranch(selectedBranchName)
+ return gui.checkMergeOrRebase(err)
},
})
}
@@ -393,35 +317,35 @@ func (gui *Gui) handleFastForward() error {
}
if !branch.IsTrackingRemote() {
- return gui.PopupHandler.ErrorMsg(gui.Tr.FwdNoUpstream)
+ return gui.c.ErrorMsg(gui.c.Tr.FwdNoUpstream)
}
if !branch.RemoteBranchStoredLocally() {
- return gui.PopupHandler.ErrorMsg(gui.Tr.FwdNoLocalUpstream)
+ return gui.c.ErrorMsg(gui.c.Tr.FwdNoLocalUpstream)
}
if branch.HasCommitsToPush() {
- return gui.PopupHandler.ErrorMsg(gui.Tr.FwdCommitsToPush)
+ return gui.c.ErrorMsg(gui.c.Tr.FwdCommitsToPush)
}
- action := gui.Tr.Actions.FastForwardBranch
+ action := gui.c.Tr.Actions.FastForwardBranch
message := utils.ResolvePlaceholderString(
- gui.Tr.Fetching,
+ gui.c.Tr.Fetching,
map[string]string{
"from": fmt.Sprintf("%s/%s", branch.UpstreamRemote, branch.UpstreamBranch),
"to": branch.Name,
},
)
- return gui.PopupHandler.WithLoaderPanel(message, func() error {
+ return gui.c.WithLoaderPanel(message, func() error {
if gui.State.Panels.Branches.SelectedLineIdx == 0 {
- _ = gui.pullWithLock(PullFilesOptions{action: action, FastForwardOnly: true})
+ _ = gui.Controllers.Sync.PullAux(controllers.PullFilesOptions{Action: action, FastForwardOnly: true})
} else {
- gui.logAction(action)
- err := gui.Git.Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
+ gui.c.LogAction(action)
+ err := gui.git.Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
if err != nil {
- _ = gui.PopupHandler.Error(err)
+ _ = gui.c.Error(err)
}
- _ = gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
+ _ = gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
}
return nil
@@ -434,7 +358,7 @@ func (gui *Gui) handleCreateResetToBranchMenu() error {
return nil
}
- return gui.createResetMenu(branch.Name)
+ return gui.refHelper.CreateGitResetMenu(branch.Name)
}
func (gui *Gui) handleRenameBranch() error {
@@ -444,13 +368,13 @@ func (gui *Gui) handleRenameBranch() error {
}
promptForNewName := func() error {
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- Title: gui.Tr.NewBranchNamePrompt + " " + branch.Name + ":",
+ return gui.c.Prompt(popup.PromptOpts{
+ Title: gui.c.Tr.NewBranchNamePrompt + " " + branch.Name + ":",
InitialContent: branch.Name,
HandleConfirm: func(newBranchName string) error {
- gui.logAction(gui.Tr.Actions.RenameBranch)
- if err := gui.Git.Branch.Rename(branch.Name, newBranchName); err != nil {
- return gui.PopupHandler.Error(err)
+ gui.c.LogAction(gui.c.Tr.Actions.RenameBranch)
+ if err := gui.git.Branch.Rename(branch.Name, newBranchName); err != nil {
+ return gui.c.Error(err)
}
// need to find where the branch is now so that we can re-select it. That means we need to refetch the branches synchronously and then find our branch
@@ -478,20 +402,13 @@ func (gui *Gui) handleRenameBranch() error {
return promptForNewName()
}
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.LcRenameBranch,
- Prompt: gui.Tr.RenameBranchWarning,
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.LcRenameBranch,
+ Prompt: gui.c.Tr.RenameBranchWarning,
HandleConfirm: promptForNewName,
})
}
-func (gui *Gui) currentBranch() *models.Branch {
- if len(gui.State.Branches) == 0 {
- return nil
- }
- return gui.State.Branches[0]
-}
-
func (gui *Gui) handleNewBranchOffCurrentItem() error {
context := gui.currentSideListContext()
@@ -501,7 +418,7 @@ func (gui *Gui) handleNewBranchOffCurrentItem() error {
}
message := utils.ResolvePlaceholderString(
- gui.Tr.NewBranchNameBranchOff,
+ gui.c.Tr.NewBranchNameBranchOff,
map[string]string{
"branchName": item.Description(),
},
@@ -513,12 +430,12 @@ func (gui *Gui) handleNewBranchOffCurrentItem() error {
prefilledName = strings.SplitAfterN(item.ID(), "/", 2)[1]
}
- return gui.PopupHandler.Prompt(popup.PromptOpts{
+ return gui.c.Prompt(popup.PromptOpts{
Title: message,
InitialContent: prefilledName,
HandleConfirm: func(response string) error {
- gui.logAction(gui.Tr.Actions.CreateBranch)
- if err := gui.Git.Branch.New(sanitizedBranchName(response), item.ID()); err != nil {
+ gui.c.LogAction(gui.c.Tr.Actions.CreateBranch)
+ if err := gui.git.Branch.New(sanitizedBranchName(response), item.ID()); err != nil {
return err
}
@@ -529,14 +446,14 @@ func (gui *Gui) handleNewBranchOffCurrentItem() error {
}
if context.GetKey() != gui.State.Contexts.Branches.GetKey() {
- if err := gui.pushContext(gui.State.Contexts.Branches); err != nil {
+ if err := gui.c.PushContext(gui.State.Contexts.Branches); err != nil {
return err
}
}
gui.State.Panels.Branches.SelectedLineIdx = 0
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
},
})
}
diff --git a/pkg/gui/cherry_picking.go b/pkg/gui/cherry_picking.go
index 225fc3811..28554edce 100644
--- a/pkg/gui/cherry_picking.go
+++ b/pkg/gui/cherry_picking.go
@@ -3,12 +3,13 @@ package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
)
// you can only copy from one context at a time, because the order and position of commits matter
-func (gui *Gui) resetCherryPickingIfNecessary(context Context) error {
- oldContextKey := ContextKey(gui.State.Modes.CherryPicking.ContextKey)
+func (gui *Gui) resetCherryPickingIfNecessary(context types.Context) error {
+ oldContextKey := types.ContextKey(gui.State.Modes.CherryPicking.ContextKey)
if oldContextKey != context.GetKey() {
// need to reset the cherry picking mode
@@ -22,10 +23,6 @@ func (gui *Gui) resetCherryPickingIfNecessary(context Context) error {
}
func (gui *Gui) handleCopyCommit() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
// get currently selected commit, add the sha to state.
context := gui.currentSideListContext()
if context == nil {
@@ -80,7 +77,7 @@ func (gui *Gui) commitsListForContext() []*models.Commit {
case SUB_COMMITS_CONTEXT_KEY:
return gui.State.SubCommits
default:
- gui.Log.Errorf("no commit list for context %s", context.GetKey())
+ gui.c.Log.Errorf("no commit list for context %s", context.GetKey())
return nil
}
}
@@ -102,10 +99,6 @@ func (gui *Gui) addCommitToCherryPickedCommits(index int) {
}
func (gui *Gui) handleCopyCommitRange() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
// get currently selected commit, add the sha to state.
context := gui.currentSideListContext()
if context == nil {
@@ -142,38 +135,34 @@ func (gui *Gui) handleCopyCommitRange() error {
// HandlePasteCommits begins a cherry-pick rebase with the commits the user has copied
func (gui *Gui) HandlePasteCommits() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.CherryPick,
- Prompt: gui.Tr.SureCherryPick,
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.CherryPick,
+ Prompt: gui.c.Tr.SureCherryPick,
HandleConfirm: func() error {
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.CherryPickingStatus, func() error {
- gui.logAction(gui.Tr.Actions.CherryPick)
- err := gui.Git.Rebase.CherryPickCommits(gui.State.Modes.CherryPicking.CherryPickedCommits)
- return gui.handleGenericMergeCommandResult(err)
+ return gui.c.WithWaitingStatus(gui.c.Tr.CherryPickingStatus, func() error {
+ gui.c.LogAction(gui.c.Tr.Actions.CherryPick)
+ err := gui.git.Rebase.CherryPickCommits(gui.State.Modes.CherryPicking.CherryPickedCommits)
+ return gui.checkMergeOrRebase(err)
})
},
})
}
func (gui *Gui) exitCherryPickingMode() error {
- contextKey := ContextKey(gui.State.Modes.CherryPicking.ContextKey)
+ contextKey := types.ContextKey(gui.State.Modes.CherryPicking.ContextKey)
gui.State.Modes.CherryPicking.ContextKey = ""
gui.State.Modes.CherryPicking.CherryPickedCommits = nil
if contextKey == "" {
- gui.Log.Warn("context key blank when trying to exit cherry picking mode")
+ gui.c.Log.Warn("context key blank when trying to exit cherry picking mode")
return nil
}
return gui.rerenderContextViewIfPresent(contextKey)
}
-func (gui *Gui) rerenderContextViewIfPresent(contextKey ContextKey) error {
+func (gui *Gui) rerenderContextViewIfPresent(contextKey types.ContextKey) error {
if contextKey == "" {
return nil
}
@@ -184,11 +173,11 @@ func (gui *Gui) rerenderContextViewIfPresent(contextKey ContextKey) error {
view, err := gui.g.View(viewName)
if err != nil {
- gui.Log.Error(err)
+ gui.c.Log.Error(err)
return nil
}
- if ContextKey(view.Context) == contextKey {
+ if types.ContextKey(view.Context) == contextKey {
if err := context.HandleRender(); err != nil {
return err
}
diff --git a/pkg/gui/command_log_panel.go b/pkg/gui/command_log_panel.go
index aa46a4d18..409fa0023 100644
--- a/pkg/gui/command_log_panel.go
+++ b/pkg/gui/command_log_panel.go
@@ -22,7 +22,7 @@ import (
// So we call logAction to log the 'Stage File' part and then we call logCommand to log the command itself.
// We pass logCommand to our OSCommand struct so that it can handle logging commands
// for us.
-func (gui *Gui) logAction(action string) {
+func (gui *Gui) LogAction(action string) {
if gui.Views.Extras == nil {
return
}
@@ -32,7 +32,7 @@ func (gui *Gui) logAction(action string) {
fmt.Fprint(gui.Views.Extras, "\n"+style.FgYellow.Sprint(action))
}
-func (gui *Gui) logCommand(cmdStr string, commandLine bool) {
+func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
if gui.Views.Extras == nil {
return
}
@@ -52,23 +52,23 @@ func (gui *Gui) logCommand(cmdStr string, commandLine bool) {
func (gui *Gui) printCommandLogHeader() {
introStr := fmt.Sprintf(
- gui.Tr.CommandLogHeader,
- gui.getKeyDisplay(gui.UserConfig.Keybinding.Universal.ExtrasMenu),
+ gui.c.Tr.CommandLogHeader,
+ gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.ExtrasMenu),
)
fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr))
- if gui.UserConfig.Gui.ShowRandomTip {
+ if gui.c.UserConfig.Gui.ShowRandomTip {
fmt.Fprintf(
gui.Views.Extras,
"%s: %s",
- style.FgYellow.Sprint(gui.Tr.RandomTip),
+ style.FgYellow.Sprint(gui.c.Tr.RandomTip),
style.FgGreen.Sprint(gui.getRandomTip()),
)
}
}
func (gui *Gui) getRandomTip() string {
- config := gui.UserConfig.Keybinding
+ config := gui.c.UserConfig.Keybinding
formattedKey := func(key string) string {
return gui.getKeyDisplay(key)
diff --git a/pkg/gui/commit_files_panel.go b/pkg/gui/commit_files_panel.go
index 1941802c2..abae8196f 100644
--- a/pkg/gui/commit_files_panel.go
+++ b/pkg/gui/commit_files_panel.go
@@ -3,6 +3,7 @@ package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
+ "github.com/jesseduffield/lazygit/pkg/gui/controllers"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -47,7 +48,7 @@ func (gui *Gui) commitFilesRenderToMain() error {
to := gui.State.CommitFileTreeViewModel.GetParent()
from, reverse := gui.getFromAndReverseArgsForDiff(to)
- cmdObj := gui.Git.WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false)
+ cmdObj := gui.git.WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false)
task := NewRunPtyTask(cmdObj.GetCmd())
return gui.refreshMainViews(refreshMainOpts{
@@ -65,12 +66,12 @@ func (gui *Gui) handleCheckoutCommitFile() error {
return nil
}
- gui.logAction(gui.Tr.Actions.CheckoutFile)
- if err := gui.Git.WorkingTree.CheckoutFile(gui.State.CommitFileTreeViewModel.GetParent(), node.GetPath()); err != nil {
- return gui.PopupHandler.Error(err)
+ gui.c.LogAction(gui.c.Tr.Actions.CheckoutFile)
+ if err := gui.git.WorkingTree.CheckoutFile(gui.State.CommitFileTreeViewModel.GetParent(), node.GetPath()); err != nil {
+ return gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
}
func (gui *Gui) handleDiscardOldFileChange() error {
@@ -80,19 +81,19 @@ func (gui *Gui) handleDiscardOldFileChange() error {
fileName := gui.getSelectedCommitFileName()
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.DiscardFileChangesTitle,
- Prompt: gui.Tr.DiscardFileChangesPrompt,
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.DiscardFileChangesTitle,
+ Prompt: gui.c.Tr.DiscardFileChangesPrompt,
HandleConfirm: func() error {
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
- gui.logAction(gui.Tr.Actions.DiscardOldFileChange)
- if err := gui.Git.Rebase.DiscardOldFileChanges(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, fileName); err != nil {
- if err := gui.handleGenericMergeCommandResult(err); err != nil {
+ return gui.c.WithWaitingStatus(gui.c.Tr.RebasingStatus, func() error {
+ gui.c.LogAction(gui.c.Tr.Actions.DiscardOldFileChange)
+ if err := gui.git.Rebase.DiscardOldFileChanges(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, fileName); err != nil {
+ if err := gui.checkMergeOrRebase(err); err != nil {
return err
}
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.BLOCK_UI})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI})
})
},
})
@@ -109,14 +110,14 @@ func (gui *Gui) refreshCommitFilesView() error {
to := gui.State.Panels.CommitFiles.refName
from, reverse := gui.getFromAndReverseArgsForDiff(to)
- files, err := gui.Git.Loaders.CommitFiles.GetFilesInDiff(from, to, reverse)
+ files, err := gui.git.Loaders.CommitFiles.GetFilesInDiff(from, to, reverse)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
gui.State.CommitFileTreeViewModel.SetParent(to)
gui.State.CommitFileTreeViewModel.SetFiles(files)
- return gui.postRefreshUpdate(gui.State.Contexts.CommitFiles)
+ return gui.c.PostRefreshUpdate(gui.State.Contexts.CommitFiles)
}
func (gui *Gui) handleOpenOldCommitFile() error {
@@ -125,7 +126,7 @@ func (gui *Gui) handleOpenOldCommitFile() error {
return nil
}
- return gui.openFile(node.GetPath())
+ return gui.fileHelper.OpenFile(node.GetPath())
}
func (gui *Gui) handleEditCommitFile() error {
@@ -135,10 +136,10 @@ func (gui *Gui) handleEditCommitFile() error {
}
if node.File == nil {
- return gui.PopupHandler.ErrorMsg(gui.Tr.ErrCannotEditDirectory)
+ return gui.c.ErrorMsg(gui.c.Tr.ErrCannotEditDirectory)
}
- return gui.editFile(node.GetPath())
+ return gui.fileHelper.EditFile(node.GetPath())
}
func (gui *Gui) handleToggleFileForPatch() error {
@@ -148,7 +149,7 @@ func (gui *Gui) handleToggleFileForPatch() error {
}
toggleTheFile := func() error {
- if !gui.Git.Patch.PatchManager.Active() {
+ if !gui.git.Patch.PatchManager.Active() {
if err := gui.startPatchManager(); err != nil {
return err
}
@@ -157,34 +158,34 @@ func (gui *Gui) handleToggleFileForPatch() error {
// if there is any file that hasn't been fully added we'll fully add everything,
// otherwise we'll remove everything
adding := node.AnyFile(func(file *models.CommitFile) bool {
- return gui.Git.Patch.PatchManager.GetFileStatus(file.Name, gui.State.CommitFileTreeViewModel.GetParent()) != patch.WHOLE
+ return gui.git.Patch.PatchManager.GetFileStatus(file.Name, gui.State.CommitFileTreeViewModel.GetParent()) != patch.WHOLE
})
err := node.ForEachFile(func(file *models.CommitFile) error {
if adding {
- return gui.Git.Patch.PatchManager.AddFileWhole(file.Name)
+ return gui.git.Patch.PatchManager.AddFileWhole(file.Name)
} else {
- return gui.Git.Patch.PatchManager.RemoveFile(file.Name)
+ return gui.git.Patch.PatchManager.RemoveFile(file.Name)
}
})
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
- if gui.Git.Patch.PatchManager.IsEmpty() {
- gui.Git.Patch.PatchManager.Reset()
+ if gui.git.Patch.PatchManager.IsEmpty() {
+ gui.git.Patch.PatchManager.Reset()
}
- return gui.postRefreshUpdate(gui.State.Contexts.CommitFiles)
+ return gui.c.PostRefreshUpdate(gui.State.Contexts.CommitFiles)
}
- if gui.Git.Patch.PatchManager.Active() && gui.Git.Patch.PatchManager.To != gui.State.CommitFileTreeViewModel.GetParent() {
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.DiscardPatch,
- Prompt: gui.Tr.DiscardPatchConfirm,
+ if gui.git.Patch.PatchManager.Active() && gui.git.Patch.PatchManager.To != gui.State.CommitFileTreeViewModel.GetParent() {
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.DiscardPatch,
+ Prompt: gui.c.Tr.DiscardPatchConfirm,
HandleConfirm: func() error {
- gui.Git.Patch.PatchManager.Reset()
+ gui.git.Patch.PatchManager.Reset()
return toggleTheFile()
},
})
@@ -199,15 +200,15 @@ func (gui *Gui) startPatchManager() error {
to := gui.State.Panels.CommitFiles.refName
from, reverse := gui.getFromAndReverseArgsForDiff(to)
- gui.Git.Patch.PatchManager.Start(from, to, reverse, canRebase)
+ gui.git.Patch.PatchManager.Start(from, to, reverse, canRebase)
return nil
}
func (gui *Gui) handleEnterCommitFile() error {
- return gui.enterCommitFile(OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1})
+ return gui.enterCommitFile(types.OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1})
}
-func (gui *Gui) enterCommitFile(opts OnFocusOpts) error {
+func (gui *Gui) enterCommitFile(opts types.OnFocusOpts) error {
node := gui.getSelectedCommitFileNode()
if node == nil {
return nil
@@ -218,21 +219,21 @@ func (gui *Gui) enterCommitFile(opts OnFocusOpts) error {
}
enterTheFile := func() error {
- if !gui.Git.Patch.PatchManager.Active() {
+ if !gui.git.Patch.PatchManager.Active() {
if err := gui.startPatchManager(); err != nil {
return err
}
}
- return gui.pushContext(gui.State.Contexts.PatchBuilding, opts)
+ return gui.c.PushContext(gui.State.Contexts.PatchBuilding, opts)
}
- if gui.Git.Patch.PatchManager.Active() && gui.Git.Patch.PatchManager.To != gui.State.CommitFileTreeViewModel.GetParent() {
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.DiscardPatch,
- Prompt: gui.Tr.DiscardPatchConfirm,
+ if gui.git.Patch.PatchManager.Active() && gui.git.Patch.PatchManager.To != gui.State.CommitFileTreeViewModel.GetParent() {
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.DiscardPatch,
+ Prompt: gui.c.Tr.DiscardPatchConfirm,
HandleConfirm: func() error {
- gui.Git.Patch.PatchManager.Reset()
+ gui.git.Patch.PatchManager.Reset()
return enterTheFile()
},
})
@@ -249,29 +250,29 @@ func (gui *Gui) handleToggleCommitFileDirCollapsed() error {
gui.State.CommitFileTreeViewModel.ToggleCollapsed(node.GetPath())
- if err := gui.postRefreshUpdate(gui.State.Contexts.CommitFiles); err != nil {
- gui.Log.Error(err)
+ if err := gui.c.PostRefreshUpdate(gui.State.Contexts.CommitFiles); err != nil {
+ gui.c.Log.Error(err)
}
return nil
}
-func (gui *Gui) switchToCommitFilesContext(refName string, canRebase bool, context Context, windowName string) error {
+func (gui *Gui) SwitchToCommitFilesContext(opts controllers.SwitchToCommitFilesContextOpts) error {
// sometimes the commitFiles view is already shown in another window, so we need to ensure that window
// no longer considers the commitFiles view as its main view.
gui.resetWindowForView(gui.Views.CommitFiles)
gui.State.Panels.CommitFiles.SelectedLineIdx = 0
- gui.State.Panels.CommitFiles.refName = refName
- gui.State.Panels.CommitFiles.canRebase = canRebase
- gui.State.Contexts.CommitFiles.SetParentContext(context)
- gui.State.Contexts.CommitFiles.SetWindowName(windowName)
+ gui.State.Panels.CommitFiles.refName = opts.RefName
+ gui.State.Panels.CommitFiles.canRebase = opts.CanRebase
+ gui.State.Contexts.CommitFiles.SetParentContext(opts.Context)
+ gui.State.Contexts.CommitFiles.SetWindowName(opts.WindowName)
if err := gui.refreshCommitFilesView(); err != nil {
return err
}
- return gui.pushContext(gui.State.Contexts.CommitFiles)
+ return gui.c.PushContext(gui.State.Contexts.CommitFiles)
}
// NOTE: this is very similar to handleToggleFileTreeView, could be DRY'd with generics
@@ -289,12 +290,5 @@ func (gui *Gui) handleToggleCommitFileTreeView() error {
}
}
- if err := gui.State.Contexts.CommitFiles.HandleRender(); err != nil {
- return err
- }
- if err := gui.State.Contexts.CommitFiles.HandleFocus(); err != nil {
- return err
- }
-
- return nil
+ return gui.c.PostRefreshUpdate(gui.State.Contexts.CommitFiles)
}
diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go
index feed1aecc..b59111fe2 100644
--- a/pkg/gui/commit_message_panel.go
+++ b/pkg/gui/commit_message_panel.go
@@ -12,14 +12,14 @@ func (gui *Gui) handleCommitConfirm() error {
message := strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent())
gui.State.failedCommitMessage = message
if message == "" {
- return gui.PopupHandler.ErrorMsg(gui.Tr.CommitWithoutMessageErr)
+ return gui.c.ErrorMsg(gui.c.Tr.CommitWithoutMessageErr)
}
- cmdObj := gui.Git.Commit.CommitCmdObj(message)
- gui.logAction(gui.Tr.Actions.Commit)
+ cmdObj := gui.git.Commit.CommitCmdObj(message)
+ gui.c.LogAction(gui.c.Tr.Actions.Commit)
_ = gui.returnFromContext()
- return gui.withGpgHandling(cmdObj, gui.Tr.CommittingStatus, func() error {
+ return gui.withGpgHandling(cmdObj, gui.c.Tr.CommittingStatus, func() error {
gui.Views.CommitMessage.ClearTextArea()
gui.State.failedCommitMessage = ""
return nil
@@ -32,14 +32,16 @@ func (gui *Gui) handleCommitClose() error {
func (gui *Gui) handleCommitMessageFocused() error {
message := utils.ResolvePlaceholderString(
- gui.Tr.CommitMessageConfirm,
+ gui.c.Tr.CommitMessageConfirm,
map[string]string{
- "keyBindClose": gui.getKeyDisplay(gui.UserConfig.Keybinding.Universal.Return),
- "keyBindConfirm": gui.getKeyDisplay(gui.UserConfig.Keybinding.Universal.Confirm),
- "keyBindNewLine": gui.getKeyDisplay(gui.UserConfig.Keybinding.Universal.AppendNewline),
+ "keyBindClose": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.Return),
+ "keyBindConfirm": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.Confirm),
+ "keyBindNewLine": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.AppendNewline),
},
)
+ gui.RenderCommitLength()
+
return gui.renderString(gui.Views.Options, message)
}
@@ -49,7 +51,7 @@ func (gui *Gui) getBufferLength(view *gocui.View) string {
// RenderCommitLength is a function.
func (gui *Gui) RenderCommitLength() {
- if !gui.UserConfig.Gui.CommitLength.Show {
+ if !gui.c.UserConfig.Gui.CommitLength.Show {
return
}
diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go
index 342596964..6f80ee5c3 100644
--- a/pkg/gui/commits_panel.go
+++ b/pkg/gui/commits_panel.go
@@ -1,13 +1,10 @@
package gui
import (
- "fmt"
"sync"
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/models"
- "github.com/jesseduffield/lazygit/pkg/gui/popup"
- "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -31,7 +28,7 @@ func (gui *Gui) onCommitFocus() error {
state.LimitCommits = false
go utils.Safe(func() {
if err := gui.refreshCommitsWithLimit(); err != nil {
- _ = gui.PopupHandler.Error(err)
+ _ = gui.c.Error(err)
}
})
}
@@ -45,9 +42,9 @@ func (gui *Gui) branchCommitsRenderToMain() error {
var task updateTask
commit := gui.getSelectedLocalCommit()
if commit == nil {
- task = NewRenderStringTask(gui.Tr.NoCommitsThisBranch)
+ task = NewRenderStringTask(gui.c.Tr.NoCommitsThisBranch)
} else {
- cmdObj := gui.Git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
+ cmdObj := gui.git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
task = NewRunPtyTask(cmdObj.GetCmd())
}
@@ -118,7 +115,7 @@ func (gui *Gui) refreshCommitsWithLimit() error {
gui.Mutexes.BranchCommitsMutex.Lock()
defer gui.Mutexes.BranchCommitsMutex.Unlock()
- commits, err := gui.Git.Loaders.Commits.GetCommits(
+ commits, err := gui.git.Loaders.Commits.GetCommits(
loaders.GetCommitsOptions{
Limit: gui.State.Panels.Commits.LimitCommits,
FilterPath: gui.State.Modes.Filtering.GetPath(),
@@ -132,11 +129,11 @@ func (gui *Gui) refreshCommitsWithLimit() error {
}
gui.State.Commits = commits
- return gui.postRefreshUpdate(gui.State.Contexts.BranchCommits)
+ return gui.c.PostRefreshUpdate(gui.State.Contexts.BranchCommits)
}
func (gui *Gui) refForLog() string {
- bisectInfo := gui.Git.Bisect.GetInfo()
+ bisectInfo := gui.git.Bisect.GetInfo()
gui.State.BisectInfo = bisectInfo
if !bisectInfo.Started() {
@@ -144,7 +141,7 @@ func (gui *Gui) refForLog() string {
}
// need to see if our bisect's current commit is reachable from our 'new' ref.
- if bisectInfo.Bisecting() && !gui.Git.Bisect.ReachableFromStart(bisectInfo) {
+ if bisectInfo.Bisecting() && !gui.git.Bisect.ReachableFromStart(bisectInfo) {
return bisectInfo.GetNewSha()
}
@@ -155,691 +152,11 @@ func (gui *Gui) refreshRebaseCommits() error {
gui.Mutexes.BranchCommitsMutex.Lock()
defer gui.Mutexes.BranchCommitsMutex.Unlock()
- updatedCommits, err := gui.Git.Loaders.Commits.MergeRebasingCommits(gui.State.Commits)
+ updatedCommits, err := gui.git.Loaders.Commits.MergeRebasingCommits(gui.State.Commits)
if err != nil {
return err
}
gui.State.Commits = updatedCommits
- return gui.postRefreshUpdate(gui.State.Contexts.BranchCommits)
-}
-
-// specific functions
-
-func (gui *Gui) handleCommitSquashDown() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
- if len(gui.State.Commits) <= 1 {
- return gui.PopupHandler.ErrorMsg(gui.Tr.YouNoCommitsToSquash)
- }
-
- applied, err := gui.handleMidRebaseCommand("squash")
- if err != nil {
- return err
- }
- if applied {
- return nil
- }
-
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.Squash,
- Prompt: gui.Tr.SureSquashThisCommit,
- HandleConfirm: func() error {
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.SquashingStatus, func() error {
- gui.logAction(gui.Tr.Actions.SquashCommitDown)
- err := gui.Git.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "squash")
- return gui.handleGenericMergeCommandResult(err)
- })
- },
- })
-}
-
-func (gui *Gui) handleCommitFixup() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
- if len(gui.State.Commits) <= 1 {
- return gui.PopupHandler.ErrorMsg(gui.Tr.YouNoCommitsToSquash)
- }
-
- applied, err := gui.handleMidRebaseCommand("fixup")
- if err != nil {
- return err
- }
- if applied {
- return nil
- }
-
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.Fixup,
- Prompt: gui.Tr.SureFixupThisCommit,
- HandleConfirm: func() error {
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.FixingStatus, func() error {
- gui.logAction(gui.Tr.Actions.FixupCommit)
- err := gui.Git.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "fixup")
- return gui.handleGenericMergeCommandResult(err)
- })
- },
- })
-}
-
-func (gui *Gui) handleRewordCommit() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
- applied, err := gui.handleMidRebaseCommand("reword")
- if err != nil {
- return err
- }
- if applied {
- return nil
- }
-
- commit := gui.getSelectedLocalCommit()
- if commit == nil {
- return nil
- }
-
- message, err := gui.Git.Commit.GetCommitMessage(commit.Sha)
- if err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- // TODO: use the commit message panel here
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- Title: gui.Tr.LcRewordCommit,
- InitialContent: message,
- HandleConfirm: func(response string) error {
- gui.logAction(gui.Tr.Actions.RewordCommit)
- if err := gui.Git.Rebase.RewordCommit(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, response); err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
- },
- })
-}
-
-func (gui *Gui) handleRewordCommitEditor() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
- applied, err := gui.handleMidRebaseCommand("reword")
- if err != nil {
- return err
- }
- if applied {
- return nil
- }
-
- gui.logAction(gui.Tr.Actions.RewordCommit)
- subProcess, err := gui.Git.Rebase.RewordCommitInEditor(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx)
- if err != nil {
- return gui.PopupHandler.Error(err)
- }
- if subProcess != nil {
- return gui.runSubprocessWithSuspenseAndRefresh(subProcess)
- }
-
- return nil
-}
-
-// handleMidRebaseCommand sees if the selected commit is in fact a rebasing
-// commit meaning you are trying to edit the todo file rather than actually
-// begin a rebase. It then updates the todo file with that action
-func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) {
- selectedCommit := gui.State.Commits[gui.State.Panels.Commits.SelectedLineIdx]
- if selectedCommit.Status != "rebasing" {
- return false, nil
- }
-
- // for now we do not support setting 'reword' because it requires an editor
- // and that means we either unconditionally wait around for the subprocess to ask for
- // our input or we set a lazygit client as the EDITOR env variable and have it
- // request us to edit the commit message when prompted.
- if action == "reword" {
- return true, gui.PopupHandler.ErrorMsg(gui.Tr.LcRewordNotSupported)
- }
-
- gui.logAction("Update rebase TODO")
- gui.logCommand(
- fmt.Sprintf("Updating rebase action of commit %s to '%s'", selectedCommit.ShortSha(), action),
- false,
- )
-
- if err := gui.Git.Rebase.EditRebaseTodo(gui.State.Panels.Commits.SelectedLineIdx, action); err != nil {
- return false, gui.PopupHandler.Error(err)
- }
-
- return true, gui.refreshRebaseCommits()
-}
-
-func (gui *Gui) handleCommitDelete() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
- applied, err := gui.handleMidRebaseCommand("drop")
- if err != nil {
- return err
- }
- if applied {
- return nil
- }
-
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.DeleteCommitTitle,
- Prompt: gui.Tr.DeleteCommitPrompt,
- HandleConfirm: func() error {
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.DeletingStatus, func() error {
- gui.logAction(gui.Tr.Actions.DropCommit)
- err := gui.Git.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "drop")
- return gui.handleGenericMergeCommandResult(err)
- })
- },
- })
-}
-
-func (gui *Gui) handleCommitMoveDown() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
- index := gui.State.Panels.Commits.SelectedLineIdx
- selectedCommit := gui.State.Commits[index]
- if selectedCommit.Status == "rebasing" {
- if gui.State.Commits[index+1].Status != "rebasing" {
- return nil
- }
-
- // logging directly here because MoveTodoDown doesn't have enough information
- // to provide a useful log
- gui.logAction(gui.Tr.Actions.MoveCommitDown)
- gui.logCommand(fmt.Sprintf("Moving commit %s down", selectedCommit.ShortSha()), false)
-
- if err := gui.Git.Rebase.MoveTodoDown(index); err != nil {
- return gui.PopupHandler.Error(err)
- }
- gui.State.Panels.Commits.SelectedLineIdx++
- return gui.refreshRebaseCommits()
- }
-
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.MovingStatus, func() error {
- gui.logAction(gui.Tr.Actions.MoveCommitDown)
- err := gui.Git.Rebase.MoveCommitDown(gui.State.Commits, index)
- if err == nil {
- gui.State.Panels.Commits.SelectedLineIdx++
- }
- return gui.handleGenericMergeCommandResult(err)
- })
-}
-
-func (gui *Gui) handleCommitMoveUp() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
- index := gui.State.Panels.Commits.SelectedLineIdx
- if index == 0 {
- return nil
- }
-
- selectedCommit := gui.State.Commits[index]
- if selectedCommit.Status == "rebasing" {
- // logging directly here because MoveTodoDown doesn't have enough information
- // to provide a useful log
- gui.logAction(gui.Tr.Actions.MoveCommitUp)
- gui.logCommand(
- fmt.Sprintf("Moving commit %s up", selectedCommit.ShortSha()),
- false,
- )
-
- if err := gui.Git.Rebase.MoveTodoDown(index - 1); err != nil {
- return gui.PopupHandler.Error(err)
- }
- gui.State.Panels.Commits.SelectedLineIdx--
- return gui.refreshRebaseCommits()
- }
-
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.MovingStatus, func() error {
- gui.logAction(gui.Tr.Actions.MoveCommitUp)
- err := gui.Git.Rebase.MoveCommitDown(gui.State.Commits, index-1)
- if err == nil {
- gui.State.Panels.Commits.SelectedLineIdx--
- }
- return gui.handleGenericMergeCommandResult(err)
- })
-}
-
-func (gui *Gui) handleCommitEdit() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
- applied, err := gui.handleMidRebaseCommand("edit")
- if err != nil {
- return err
- }
- if applied {
- return nil
- }
-
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
- gui.logAction(gui.Tr.Actions.EditCommit)
- err = gui.Git.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "edit")
- return gui.handleGenericMergeCommandResult(err)
- })
-}
-
-func (gui *Gui) handleCommitAmendTo() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.AmendCommitTitle,
- Prompt: gui.Tr.AmendCommitPrompt,
- HandleConfirm: func() error {
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.AmendingStatus, func() error {
- gui.logAction(gui.Tr.Actions.AmendCommit)
- err := gui.Git.Rebase.AmendTo(gui.State.Commits[gui.State.Panels.Commits.SelectedLineIdx].Sha)
- return gui.handleGenericMergeCommandResult(err)
- })
- },
- })
-}
-
-func (gui *Gui) handleCommitPick() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
- applied, err := gui.handleMidRebaseCommand("pick")
- if err != nil {
- return err
- }
- if applied {
- return nil
- }
-
- // at this point we aren't actually rebasing so we will interpret this as an
- // attempt to pull. We might revoke this later after enabling configurable keybindings
- return gui.handlePullFiles()
-}
-
-func (gui *Gui) handleCommitRevert() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
- commit := gui.getSelectedLocalCommit()
- if commit.IsMerge() {
- return gui.createRevertMergeCommitMenu(commit)
- } else {
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.Actions.RevertCommit,
- Prompt: utils.ResolvePlaceholderString(
- gui.Tr.ConfirmRevertCommit,
- map[string]string{
- "selectedCommit": commit.ShortSha(),
- }),
- HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.RevertCommit)
- if err := gui.Git.Commit.Revert(commit.Sha); err != nil {
- return gui.PopupHandler.Error(err)
- }
- return gui.afterRevertCommit()
- },
- })
- }
-}
-
-func (gui *Gui) createRevertMergeCommitMenu(commit *models.Commit) error {
- menuItems := make([]*popup.MenuItem, len(commit.Parents))
- for i, parentSha := range commit.Parents {
- i := i
- message, err := gui.Git.Commit.GetCommitMessageFirstLine(parentSha)
- if err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- menuItems[i] = &popup.MenuItem{
- DisplayString: fmt.Sprintf("%s: %s", utils.SafeTruncate(parentSha, 8), message),
- OnPress: func() error {
- parentNumber := i + 1
- gui.logAction(gui.Tr.Actions.RevertCommit)
- if err := gui.Git.Commit.RevertMerge(commit.Sha, parentNumber); err != nil {
- return gui.PopupHandler.Error(err)
- }
- return gui.afterRevertCommit()
- },
- }
- }
-
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: gui.Tr.SelectParentCommitForMerge, Items: menuItems})
-}
-
-func (gui *Gui) afterRevertCommit() error {
- gui.State.Panels.Commits.SelectedLineIdx++
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.BLOCK_UI, Scope: []types.RefreshableView{types.COMMITS, types.BRANCHES}})
-}
-
-func (gui *Gui) handleViewCommitFiles() error {
- commit := gui.getSelectedLocalCommit()
- if commit == nil {
- return nil
- }
-
- return gui.switchToCommitFilesContext(commit.Sha, true, gui.State.Contexts.BranchCommits, "commits")
-}
-
-func (gui *Gui) handleCreateFixupCommit() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
- commit := gui.getSelectedLocalCommit()
- if commit == nil {
- return nil
- }
-
- prompt := utils.ResolvePlaceholderString(
- gui.Tr.SureCreateFixupCommit,
- map[string]string{
- "commit": commit.Sha,
- },
- )
-
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.CreateFixupCommit,
- Prompt: prompt,
- HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.CreateFixupCommit)
- if err := gui.Git.Commit.CreateFixupCommit(commit.Sha); err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
- },
- })
-}
-
-func (gui *Gui) handleSquashAllAboveFixupCommits() error {
- if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
- return err
- }
-
- commit := gui.getSelectedLocalCommit()
- if commit == nil {
- return nil
- }
-
- prompt := utils.ResolvePlaceholderString(
- gui.Tr.SureSquashAboveCommits,
- map[string]string{
- "commit": commit.Sha,
- },
- )
-
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.SquashAboveCommits,
- Prompt: prompt,
- HandleConfirm: func() error {
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.SquashingStatus, func() error {
- gui.logAction(gui.Tr.Actions.SquashAllAboveFixupCommits)
- err := gui.Git.Rebase.SquashAllAboveFixupCommits(commit.Sha)
- return gui.handleGenericMergeCommandResult(err)
- })
- },
- })
-}
-
-func (gui *Gui) handleTagCommit() error {
- commit := gui.getSelectedLocalCommit()
- if commit == nil {
- return nil
- }
-
- return gui.createTagMenu(commit.Sha)
-}
-
-func (gui *Gui) createTagMenu(commitSha string) error {
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{
- Title: gui.Tr.TagMenuTitle,
- Items: []*popup.MenuItem{
- {
- DisplayString: gui.Tr.LcLightweightTag,
- OnPress: func() error {
- return gui.handleCreateLightweightTag(commitSha)
- },
- },
- {
- DisplayString: gui.Tr.LcAnnotatedTag,
- OnPress: func() error {
- return gui.handleCreateAnnotatedTag(commitSha)
- },
- },
- },
- })
-}
-
-func (gui *Gui) afterTagCreate() error {
- gui.State.Panels.Tags.SelectedLineIdx = 0 // Set to the top
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
-}
-
-func (gui *Gui) handleCreateAnnotatedTag(commitSha string) error {
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- Title: gui.Tr.TagNameTitle,
- HandleConfirm: func(tagName string) error {
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- Title: gui.Tr.TagMessageTitle,
- HandleConfirm: func(msg string) error {
- gui.logAction(gui.Tr.Actions.CreateAnnotatedTag)
- if err := gui.Git.Tag.CreateAnnotated(tagName, commitSha, msg); err != nil {
- return gui.PopupHandler.Error(err)
- }
- return gui.afterTagCreate()
- },
- })
- },
- })
-}
-
-func (gui *Gui) handleCreateLightweightTag(commitSha string) error {
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- Title: gui.Tr.TagNameTitle,
- HandleConfirm: func(tagName string) error {
- gui.logAction(gui.Tr.Actions.CreateLightweightTag)
- if err := gui.Git.Tag.CreateLightweight(tagName, commitSha); err != nil {
- return gui.PopupHandler.Error(err)
- }
- return gui.afterTagCreate()
- },
- })
-}
-
-func (gui *Gui) handleCheckoutCommit() error {
- commit := gui.getSelectedLocalCommit()
- if commit == nil {
- return nil
- }
-
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.LcCheckoutCommit,
- Prompt: gui.Tr.SureCheckoutThisCommit,
- HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.CheckoutCommit)
- return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{})
- },
- })
-}
-
-func (gui *Gui) handleCreateCommitResetMenu() error {
- commit := gui.getSelectedLocalCommit()
- if commit == nil {
- return gui.PopupHandler.ErrorMsg(gui.Tr.NoCommitsThisBranch)
- }
-
- return gui.createResetMenu(commit.Sha)
-}
-
-func (gui *Gui) handleOpenSearchForCommitsPanel(string) error {
- // we usually lazyload these commits but now that we're searching we need to load them now
- if gui.State.Panels.Commits.LimitCommits {
- gui.State.Panels.Commits.LimitCommits = false
- if err := gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
- return err
- }
- }
-
- return gui.handleOpenSearch("commits")
-}
-
-func (gui *Gui) handleGotoBottomForCommitsPanel() error {
- // we usually lazyload these commits but now that we're searching we need to load them now
- if gui.State.Panels.Commits.LimitCommits {
- gui.State.Panels.Commits.LimitCommits = false
- if err := gui.refreshSidePanels(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
- return err
- }
- }
-
- for _, context := range gui.getListContexts() {
- if context.GetViewName() == "commits" {
- return context.handleGotoBottom()
- }
- }
-
- return nil
-}
-
-func (gui *Gui) handleCopySelectedCommitMessageToClipboard() error {
- commit := gui.getSelectedLocalCommit()
- if commit == nil {
- return nil
- }
-
- message, err := gui.Git.Commit.GetCommitMessage(commit.Sha)
- if err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- gui.logAction(gui.Tr.Actions.CopyCommitMessageToClipboard)
- if err := gui.OSCommand.CopyToClipboard(message); err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- gui.raiseToast(gui.Tr.CommitMessageCopiedToClipboard)
-
- return nil
-}
-
-func (gui *Gui) handleOpenLogMenu() error {
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{
- Title: gui.Tr.LogMenuTitle,
- Items: []*popup.MenuItem{
- {
- DisplayString: gui.Tr.ToggleShowGitGraphAll,
- OnPress: func() error {
- gui.ShowWholeGitGraph = !gui.ShowWholeGitGraph
-
- if gui.ShowWholeGitGraph {
- gui.State.Panels.Commits.LimitCommits = false
- }
-
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.LcLoadingCommits, func() error {
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}})
- })
- },
- },
- {
- DisplayString: gui.Tr.ShowGitGraph,
- OpensMenu: true,
- OnPress: func() error {
- onPress := func(value string) func() error {
- return func() error {
- gui.UserConfig.Git.Log.ShowGraph = value
- gui.render()
- return nil
- }
- }
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{
- Title: gui.Tr.LogMenuTitle,
- Items: []*popup.MenuItem{
- {
- DisplayString: "always",
- OnPress: onPress("always"),
- },
- {
- DisplayString: "never",
- OnPress: onPress("never"),
- },
- {
- DisplayString: "when maximised",
- OnPress: onPress("when-maximised"),
- },
- },
- })
- },
- },
- {
- DisplayString: gui.Tr.SortCommits,
- OpensMenu: true,
- OnPress: func() error {
- onPress := func(value string) func() error {
- return func() error {
- gui.UserConfig.Git.Log.Order = value
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.LcLoadingCommits, func() error {
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}})
- })
- }
- }
-
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{
- Title: gui.Tr.LogMenuTitle,
- Items: []*popup.MenuItem{
- {
- DisplayString: "topological (topo-order)",
- OnPress: onPress("topo-order"),
- },
- {
- DisplayString: "date-order",
- OnPress: onPress("date-order"),
- },
- {
- DisplayString: "author-date-order",
- OnPress: onPress("author-date-order"),
- },
- },
- })
- },
- },
- },
- })
-}
-
-func (gui *Gui) handleOpenCommitInBrowser() error {
- commit := gui.getSelectedLocalCommit()
- if commit == nil {
- return nil
- }
-
- hostingServiceMgr := gui.getHostingServiceMgr()
-
- url, err := hostingServiceMgr.GetCommitURL(commit.Sha)
- if err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- gui.logAction(gui.Tr.Actions.OpenCommitInBrowser)
- if err := gui.OSCommand.OpenLink(url); err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- return nil
+ return gui.c.PostRefreshUpdate(gui.State.Contexts.BranchCommits)
}
diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go
index 0d5457101..1d865348f 100644
--- a/pkg/gui/confirmation_panel.go
+++ b/pkg/gui/confirmation_panel.go
@@ -19,7 +19,7 @@ func (gui *Gui) wrappedConfirmationFunction(handlersManageFocus bool, function f
if function != nil {
if err := function(); err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
}
@@ -35,7 +35,7 @@ func (gui *Gui) wrappedPromptConfirmationFunction(handlersManageFocus bool, func
if function != nil {
if err := function(getResponse()); err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
}
@@ -132,7 +132,7 @@ func (gui *Gui) prepareConfirmationPanel(
suggestionsView.FgColor = theme.GocuiDefaultTextColor
gui.setSuggestions(findSuggestionsFunc(""))
suggestionsView.Visible = true
- suggestionsView.Title = fmt.Sprintf(gui.Tr.SuggestionsTitle, gui.UserConfig.Keybinding.Universal.TogglePanel)
+ suggestionsView.Title = fmt.Sprintf(gui.c.Tr.SuggestionsTitle, gui.c.UserConfig.Keybinding.Universal.TogglePanel)
}
return nil
@@ -171,12 +171,12 @@ func (gui *Gui) createPopupPanel(opts popup.CreatePopupPanelOpts) error {
return err
}
- return gui.pushContext(gui.State.Contexts.Confirmation)
+ return gui.c.PushContext(gui.State.Contexts.Confirmation)
}
func (gui *Gui) setKeyBindings(opts popup.CreatePopupPanelOpts) error {
actions := utils.ResolvePlaceholderString(
- gui.Tr.CloseConfirm,
+ gui.c.Tr.CloseConfirm,
map[string]string{
"keyBindClose": "esc",
"keyBindConfirm": "enter",
@@ -197,7 +197,7 @@ func (gui *Gui) setKeyBindings(opts popup.CreatePopupPanelOpts) error {
handler func() error
}
- keybindingConfig := gui.UserConfig.Keybinding
+ keybindingConfig := gui.c.UserConfig.Keybinding
onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(
opts.HandlersManageFocus,
opts.HandleConfirmPrompt,
@@ -262,7 +262,7 @@ func (gui *Gui) setKeyBindings(opts popup.CreatePopupPanelOpts) error {
}
func (gui *Gui) clearConfirmationViewKeyBindings() {
- keybindingConfig := gui.UserConfig.Keybinding
+ keybindingConfig := gui.c.UserConfig.Keybinding
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone)
@@ -276,3 +276,10 @@ func (gui *Gui) wrappedHandler(f func() error) func(g *gocui.Gui, v *gocui.View)
return f()
}
}
+
+func (gui *Gui) refreshSuggestions() {
+ gui.suggestionsAsyncHandler.Do(func() func() {
+ suggestions := gui.findSuggestions(gui.c.GetPromptInput())
+ return func() { gui.setSuggestions(suggestions) }
+ })
+}
diff --git a/pkg/gui/context.go b/pkg/gui/context.go
index 7aa9a1046..f92c73e26 100644
--- a/pkg/gui/context.go
+++ b/pkg/gui/context.go
@@ -5,44 +5,13 @@ import (
"fmt"
"github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
)
-type ContextKind int
-
-const (
- SIDE_CONTEXT ContextKind = iota
- MAIN_CONTEXT
- TEMPORARY_POPUP
- PERSISTENT_POPUP
- EXTRAS_CONTEXT
-)
-
-type OnFocusOpts struct {
- ClickedViewName string
- ClickedViewLineIdx int
-}
-
-type Context interface {
- HandleFocus(opts ...OnFocusOpts) error
- HandleFocusLost() error
- HandleRender() error
- HandleRenderToMain() error
- GetKind() ContextKind
- GetViewName() string
- GetWindowName() string
- SetWindowName(string)
- GetKey() ContextKey
- SetParentContext(Context)
-
- // we return a bool here to tell us whether or not the returned value just wraps a nil
- GetParentContext() (Context, bool)
- GetOptionsMap() map[string]string
-}
-
func (gui *Gui) popupViewNames() []string {
result := []string{}
for _, context := range gui.allContexts() {
- if context.GetKind() == PERSISTENT_POPUP || context.GetKind() == TEMPORARY_POPUP {
+ if context.GetKind() == types.PERSISTENT_POPUP || context.GetKind() == types.TEMPORARY_POPUP {
result = append(result, context.GetViewName())
}
}
@@ -50,7 +19,7 @@ func (gui *Gui) popupViewNames() []string {
return result
}
-func (gui *Gui) currentContextKeyIgnoringPopups() ContextKey {
+func (gui *Gui) currentContextKeyIgnoringPopups() types.ContextKey {
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
@@ -60,7 +29,7 @@ func (gui *Gui) currentContextKeyIgnoringPopups() ContextKey {
reversedIndex := len(stack) - 1 - i
context := stack[reversedIndex]
kind := stack[reversedIndex].GetKind()
- if kind != TEMPORARY_POPUP && kind != PERSISTENT_POPUP {
+ if kind != types.TEMPORARY_POPUP && kind != types.PERSISTENT_POPUP {
return context.GetKey()
}
}
@@ -70,12 +39,12 @@ func (gui *Gui) currentContextKeyIgnoringPopups() ContextKey {
// use replaceContext when you don't want to return to the original context upon
// hitting escape: you want to go that context's parent instead.
-func (gui *Gui) replaceContext(c Context) error {
+func (gui *Gui) replaceContext(c types.Context) error {
gui.State.ContextManager.Lock()
defer gui.State.ContextManager.Unlock()
if len(gui.State.ContextManager.ContextStack) == 0 {
- gui.State.ContextManager.ContextStack = []Context{c}
+ gui.State.ContextManager.ContextStack = []types.Context{c}
} else {
// replace the last item with the given item
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack[0:len(gui.State.ContextManager.ContextStack)-1], c)
@@ -84,7 +53,7 @@ func (gui *Gui) replaceContext(c Context) error {
return gui.activateContext(c)
}
-func (gui *Gui) pushContext(c Context, opts ...OnFocusOpts) error {
+func (gui *Gui) pushContext(c types.Context, opts ...types.OnFocusOpts) error {
// using triple dot but you should only ever pass one of these opt structs
if len(opts) > 1 {
return errors.New("cannot pass multiple opts to pushContext")
@@ -94,7 +63,7 @@ func (gui *Gui) pushContext(c Context, opts ...OnFocusOpts) error {
// push onto stack
// if we are switching to a side context, remove all other contexts in the stack
- if c.GetKind() == SIDE_CONTEXT {
+ if c.GetKind() == types.SIDE_CONTEXT {
for _, stackContext := range gui.State.ContextManager.ContextStack {
if stackContext.GetKey() != c.GetKey() {
if err := gui.deactivateContext(stackContext); err != nil {
@@ -103,7 +72,7 @@ func (gui *Gui) pushContext(c Context, opts ...OnFocusOpts) error {
}
}
}
- gui.State.ContextManager.ContextStack = []Context{c}
+ gui.State.ContextManager.ContextStack = []types.Context{c}
} else if len(gui.State.ContextManager.ContextStack) == 0 || gui.currentContextWithoutLock().GetKey() != c.GetKey() {
// Do not append if the one at the end is the same context (e.g. opening a menu from a menu)
// In that case we'll just close the menu entirely when the user hits escape.
@@ -123,7 +92,7 @@ func (gui *Gui) pushContext(c Context, opts ...OnFocusOpts) error {
// want to switch to: you only know the view that you want to switch to. It will
// look up the context currently active for that view and switch to that context
func (gui *Gui) pushContextWithView(viewName string) error {
- return gui.pushContext(gui.State.ViewContextMap[viewName])
+ return gui.c.PushContext(gui.State.ViewContextMap[viewName])
}
func (gui *Gui) returnFromContext() error {
@@ -151,7 +120,7 @@ func (gui *Gui) returnFromContext() error {
return gui.activateContext(newContext)
}
-func (gui *Gui) deactivateContext(c Context) error {
+func (gui *Gui) deactivateContext(c types.Context) error {
view, _ := gui.g.View(c.GetViewName())
if view != nil && view.IsSearching() {
@@ -161,7 +130,7 @@ func (gui *Gui) deactivateContext(c Context) error {
}
// if we are the kind of context that is sent to back upon deactivation, we should do that
- if view != nil && (c.GetKind() == TEMPORARY_POPUP || c.GetKind() == PERSISTENT_POPUP || c.GetKey() == COMMIT_FILES_CONTEXT_KEY) {
+ if view != nil && (c.GetKind() == types.TEMPORARY_POPUP || c.GetKind() == types.PERSISTENT_POPUP || c.GetKey() == COMMIT_FILES_CONTEXT_KEY) {
view.Visible = false
}
@@ -175,13 +144,13 @@ func (gui *Gui) deactivateContext(c Context) error {
// postRefreshUpdate is to be called on a context after the state that it depends on has been refreshed
// if the context's view is set to another context we do nothing.
// if the context's view is the current view we trigger a focus; re-selecting the current item.
-func (gui *Gui) postRefreshUpdate(c Context) error {
+func (gui *Gui) postRefreshUpdate(c types.Context) error {
v, err := gui.g.View(c.GetViewName())
if err != nil {
return nil
}
- if ContextKey(v.Context) != c.GetKey() {
+ if types.ContextKey(v.Context) != c.GetKey() {
return nil
}
@@ -198,13 +167,13 @@ func (gui *Gui) postRefreshUpdate(c Context) error {
return nil
}
-func (gui *Gui) activateContext(c Context, opts ...OnFocusOpts) error {
+func (gui *Gui) activateContext(c types.Context, opts ...types.OnFocusOpts) error {
viewName := c.GetViewName()
v, err := gui.g.View(viewName)
if err != nil {
return err
}
- originalViewContextKey := ContextKey(v.Context)
+ originalViewContextKey := types.ContextKey(v.Context)
// ensure that any other window for which this view was active is now set to the default for that window.
gui.setViewAsActiveForWindow(v)
@@ -260,14 +229,14 @@ func (gui *Gui) activateContext(c Context, opts ...OnFocusOpts) error {
// return result
// }
-func (gui *Gui) currentContext() Context {
+func (gui *Gui) currentContext() types.Context {
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
return gui.currentContextWithoutLock()
}
-func (gui *Gui) currentContextWithoutLock() Context {
+func (gui *Gui) currentContextWithoutLock() types.Context {
if len(gui.State.ContextManager.ContextStack) == 0 {
return gui.defaultSideContext()
}
@@ -277,16 +246,16 @@ func (gui *Gui) currentContextWithoutLock() Context {
// the status panel is not yet a list context (and may never be), so this method is not
// quite the same as currentSideContext()
-func (gui *Gui) currentSideListContext() IListContext {
+func (gui *Gui) currentSideListContext() types.IListContext {
context := gui.currentSideContext()
- listContext, ok := context.(IListContext)
+ listContext, ok := context.(types.IListContext)
if !ok {
return nil
}
return listContext
}
-func (gui *Gui) currentSideContext() Context {
+func (gui *Gui) currentSideContext() types.Context {
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
@@ -297,11 +266,11 @@ func (gui *Gui) currentSideContext() Context {
return gui.defaultSideContext()
}
- // find the first context in the stack with the type of SIDE_CONTEXT
+ // find the first context in the stack with the type of types.SIDE_CONTEXT
for i := range stack {
context := stack[len(stack)-1-i]
- if context.GetKind() == SIDE_CONTEXT {
+ if context.GetKind() == types.SIDE_CONTEXT {
return context
}
}
@@ -310,7 +279,7 @@ func (gui *Gui) currentSideContext() Context {
}
// static as opposed to popup
-func (gui *Gui) currentStaticContext() Context {
+func (gui *Gui) currentStaticContext() types.Context {
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
@@ -324,7 +293,7 @@ func (gui *Gui) currentStaticContext() Context {
for i := range stack {
context := stack[len(stack)-1-i]
- if context.GetKind() != TEMPORARY_POPUP && context.GetKind() != PERSISTENT_POPUP {
+ if context.GetKind() != types.TEMPORARY_POPUP && context.GetKind() != types.PERSISTENT_POPUP {
return context
}
}
@@ -332,7 +301,7 @@ func (gui *Gui) currentStaticContext() Context {
return gui.defaultSideContext()
}
-func (gui *Gui) defaultSideContext() Context {
+func (gui *Gui) defaultSideContext() types.Context {
if gui.State.Modes.Filtering.Active() {
return gui.State.Contexts.BranchCommits
} else {
@@ -407,7 +376,7 @@ func (gui *Gui) onViewFocusLost(oldView *gocui.View, newView *gocui.View) error
// which currently just means a context that affects both the main and secondary views
// other views can have their context changed directly but this function helps
// keep the main and secondary views in sync
-func (gui *Gui) changeMainViewsContext(contextKey ContextKey) {
+func (gui *Gui) changeMainViewsContext(contextKey types.ContextKey) {
if gui.State.MainContext == contextKey {
return
}
@@ -432,13 +401,13 @@ func (gui *Gui) viewTabNames(viewName string) []string {
result := make([]string, len(tabContexts))
for i, tabContext := range tabContexts {
- result[i] = tabContext.tab
+ result[i] = tabContext.Tab
}
return result
}
-func (gui *Gui) setViewTabForContext(c Context) {
+func (gui *Gui) setViewTabForContext(c types.Context) {
// search for the context in our map and if we find it, set the tab for the corresponding view
tabContexts, ok := gui.State.ViewTabContextMap[c.GetViewName()]
if !ok {
@@ -446,12 +415,12 @@ func (gui *Gui) setViewTabForContext(c Context) {
}
for tabIndex, tabContext := range tabContexts {
- for _, context := range tabContext.contexts {
+ for _, context := range tabContext.Contexts {
if context.GetKey() == c.GetKey() {
// get the view, set the tab
v, err := gui.g.View(c.GetViewName())
if err != nil {
- gui.Log.Error(err)
+ gui.c.Log.Error(err)
return
}
v.TabIndex = tabIndex
@@ -461,12 +430,7 @@ func (gui *Gui) setViewTabForContext(c Context) {
}
}
-type tabContext struct {
- tab string
- contexts []Context
-}
-
-func (gui *Gui) mustContextForContextKey(contextKey ContextKey) Context {
+func (gui *Gui) mustContextForContextKey(contextKey types.ContextKey) types.Context {
context, ok := gui.contextForContextKey(contextKey)
if !ok {
@@ -476,7 +440,7 @@ func (gui *Gui) mustContextForContextKey(contextKey ContextKey) Context {
return context
}
-func (gui *Gui) contextForContextKey(contextKey ContextKey) (Context, bool) {
+func (gui *Gui) contextForContextKey(contextKey types.ContextKey) (types.Context, bool) {
for _, context := range gui.allContexts() {
if context.GetKey() == contextKey {
return context, true
@@ -487,7 +451,7 @@ func (gui *Gui) contextForContextKey(contextKey ContextKey) (Context, bool) {
}
func (gui *Gui) rerenderView(view *gocui.View) error {
- contextKey := ContextKey(view.Context)
+ contextKey := types.ContextKey(view.Context)
context := gui.mustContextForContextKey(contextKey)
return context.HandleRender()
diff --git a/pkg/gui/context/context.go b/pkg/gui/context/context.go
new file mode 100644
index 000000000..67de237ed
--- /dev/null
+++ b/pkg/gui/context/context.go
@@ -0,0 +1,98 @@
+package context
+
+import "github.com/jesseduffield/lazygit/pkg/gui/types"
+
+type ContextTree struct {
+ Status types.Context
+ Files types.IListContext
+ Submodules types.IListContext
+ Menu types.IListContext
+ Branches types.IListContext
+ Remotes types.IListContext
+ RemoteBranches types.IListContext
+ Tags types.IListContext
+ BranchCommits types.IListContext
+ CommitFiles types.IListContext
+ ReflogCommits types.IListContext
+ SubCommits types.IListContext
+ Stash types.IListContext
+ Suggestions types.IListContext
+ Normal types.Context
+ Staging types.Context
+ PatchBuilding types.Context
+ Merging types.Context
+ Credentials types.Context
+ Confirmation types.Context
+ CommitMessage types.Context
+ Search types.Context
+ CommandLog types.Context
+}
+
+func (tree ContextTree) InitialViewContextMap() map[string]types.Context {
+ return map[string]types.Context{
+ "status": tree.Status,
+ "files": tree.Files,
+ "branches": tree.Branches,
+ "commits": tree.BranchCommits,
+ "commitFiles": tree.CommitFiles,
+ "stash": tree.Stash,
+ "menu": tree.Menu,
+ "confirmation": tree.Confirmation,
+ "credentials": tree.Credentials,
+ "commitMessage": tree.CommitMessage,
+ "main": tree.Normal,
+ "secondary": tree.Normal,
+ "extras": tree.CommandLog,
+ }
+}
+
+type TabContext struct {
+ Tab string
+ Contexts []types.Context
+}
+
+func (tree ContextTree) InitialViewTabContextMap() map[string][]TabContext {
+ return map[string][]TabContext{
+ "branches": {
+ {
+ Tab: "Local Branches",
+ Contexts: []types.Context{tree.Branches},
+ },
+ {
+ Tab: "Remotes",
+ Contexts: []types.Context{
+ tree.Remotes,
+ tree.RemoteBranches,
+ },
+ },
+ {
+ Tab: "Tags",
+ Contexts: []types.Context{tree.Tags},
+ },
+ },
+ "commits": {
+ {
+ Tab: "Commits",
+ Contexts: []types.Context{tree.BranchCommits},
+ },
+ {
+ Tab: "Reflog",
+ Contexts: []types.Context{
+ tree.ReflogCommits,
+ },
+ },
+ },
+ "files": {
+ {
+ Tab: "Files",
+ Contexts: []types.Context{tree.Files},
+ },
+ {
+ Tab: "Submodules",
+ Contexts: []types.Context{
+ tree.Submodules,
+ },
+ },
+ },
+ }
+}
diff --git a/pkg/gui/context_config.go b/pkg/gui/context_config.go
index e884d32bd..b3e94e15f 100644
--- a/pkg/gui/context_config.go
+++ b/pkg/gui/context_config.go
@@ -1,34 +1,37 @@
package gui
-type ContextKey string
-
-const (
- STATUS_CONTEXT_KEY ContextKey = "status"
- FILES_CONTEXT_KEY ContextKey = "files"
- LOCAL_BRANCHES_CONTEXT_KEY ContextKey = "localBranches"
- REMOTES_CONTEXT_KEY ContextKey = "remotes"
- REMOTE_BRANCHES_CONTEXT_KEY ContextKey = "remoteBranches"
- TAGS_CONTEXT_KEY ContextKey = "tags"
- BRANCH_COMMITS_CONTEXT_KEY ContextKey = "commits"
- REFLOG_COMMITS_CONTEXT_KEY ContextKey = "reflogCommits"
- SUB_COMMITS_CONTEXT_KEY ContextKey = "subCommits"
- COMMIT_FILES_CONTEXT_KEY ContextKey = "commitFiles"
- STASH_CONTEXT_KEY ContextKey = "stash"
- MAIN_NORMAL_CONTEXT_KEY ContextKey = "normal"
- MAIN_MERGING_CONTEXT_KEY ContextKey = "merging"
- MAIN_PATCH_BUILDING_CONTEXT_KEY ContextKey = "patchBuilding"
- MAIN_STAGING_CONTEXT_KEY ContextKey = "staging"
- MENU_CONTEXT_KEY ContextKey = "menu"
- CREDENTIALS_CONTEXT_KEY ContextKey = "credentials"
- CONFIRMATION_CONTEXT_KEY ContextKey = "confirmation"
- SEARCH_CONTEXT_KEY ContextKey = "search"
- COMMIT_MESSAGE_CONTEXT_KEY ContextKey = "commitMessage"
- SUBMODULES_CONTEXT_KEY ContextKey = "submodules"
- SUGGESTIONS_CONTEXT_KEY ContextKey = "suggestions"
- COMMAND_LOG_CONTEXT_KEY ContextKey = "cmdLog"
+import (
+ "github.com/jesseduffield/lazygit/pkg/gui/context"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
)
-var allContextKeys = []ContextKey{
+const (
+ STATUS_CONTEXT_KEY types.ContextKey = "status"
+ FILES_CONTEXT_KEY types.ContextKey = "files"
+ LOCAL_BRANCHES_CONTEXT_KEY types.ContextKey = "localBranches"
+ REMOTES_CONTEXT_KEY types.ContextKey = "remotes"
+ REMOTE_BRANCHES_CONTEXT_KEY types.ContextKey = "remoteBranches"
+ TAGS_CONTEXT_KEY types.ContextKey = "tags"
+ BRANCH_COMMITS_CONTEXT_KEY types.ContextKey = "commits"
+ REFLOG_COMMITS_CONTEXT_KEY types.ContextKey = "reflogCommits"
+ SUB_COMMITS_CONTEXT_KEY types.ContextKey = "subCommits"
+ COMMIT_FILES_CONTEXT_KEY types.ContextKey = "commitFiles"
+ STASH_CONTEXT_KEY types.ContextKey = "stash"
+ MAIN_NORMAL_CONTEXT_KEY types.ContextKey = "normal"
+ MAIN_MERGING_CONTEXT_KEY types.ContextKey = "merging"
+ MAIN_PATCH_BUILDING_CONTEXT_KEY types.ContextKey = "patchBuilding"
+ MAIN_STAGING_CONTEXT_KEY types.ContextKey = "staging"
+ MENU_CONTEXT_KEY types.ContextKey = "menu"
+ CREDENTIALS_CONTEXT_KEY types.ContextKey = "credentials"
+ CONFIRMATION_CONTEXT_KEY types.ContextKey = "confirmation"
+ SEARCH_CONTEXT_KEY types.ContextKey = "search"
+ COMMIT_MESSAGE_CONTEXT_KEY types.ContextKey = "commitMessage"
+ SUBMODULES_CONTEXT_KEY types.ContextKey = "submodules"
+ SUGGESTIONS_CONTEXT_KEY types.ContextKey = "suggestions"
+ COMMAND_LOG_CONTEXT_KEY types.ContextKey = "cmdLog"
+)
+
+var AllContextKeys = []types.ContextKey{
STATUS_CONTEXT_KEY,
FILES_CONTEXT_KEY,
LOCAL_BRANCHES_CONTEXT_KEY,
@@ -54,34 +57,8 @@ var allContextKeys = []ContextKey{
COMMAND_LOG_CONTEXT_KEY,
}
-type ContextTree struct {
- Status Context
- Files IListContext
- Submodules IListContext
- Menu IListContext
- Branches IListContext
- Remotes IListContext
- RemoteBranches IListContext
- Tags IListContext
- BranchCommits IListContext
- CommitFiles IListContext
- ReflogCommits IListContext
- SubCommits IListContext
- Stash IListContext
- Suggestions IListContext
- Normal Context
- Staging Context
- PatchBuilding Context
- Merging Context
- Credentials Context
- Confirmation Context
- CommitMessage Context
- Search Context
- CommandLog Context
-}
-
-func (gui *Gui) allContexts() []Context {
- return []Context{
+func (gui *Gui) allContexts() []types.Context {
+ return []types.Context{
gui.State.Contexts.Status,
gui.State.Contexts.Files,
gui.State.Contexts.Submodules,
@@ -107,11 +84,11 @@ func (gui *Gui) allContexts() []Context {
}
}
-func (gui *Gui) contextTree() ContextTree {
- return ContextTree{
+func (gui *Gui) contextTree() context.ContextTree {
+ return context.ContextTree{
Status: &BasicContext{
OnRenderToMain: OnFocusWrapper(gui.statusRenderToMain),
- Kind: SIDE_CONTEXT,
+ Kind: types.SIDE_CONTEXT,
ViewName: "status",
Key: STATUS_CONTEXT_KEY,
},
@@ -128,15 +105,15 @@ func (gui *Gui) contextTree() ContextTree {
Tags: gui.tagsListContext(),
Stash: gui.stashListContext(),
Normal: &BasicContext{
- OnFocus: func(opts ...OnFocusOpts) error {
+ OnFocus: func(opts ...types.OnFocusOpts) error {
return nil // TODO: should we do something here? We should allow for scrolling the panel
},
- Kind: MAIN_CONTEXT,
+ Kind: types.MAIN_CONTEXT,
ViewName: "main",
Key: MAIN_NORMAL_CONTEXT_KEY,
},
Staging: &BasicContext{
- OnFocus: func(opts ...OnFocusOpts) error {
+ OnFocus: func(opts ...types.OnFocusOpts) error {
forceSecondaryFocused := false
selectedLineIdx := -1
if len(opts) > 0 && opts[0].ClickedViewName != "" {
@@ -149,12 +126,12 @@ func (gui *Gui) contextTree() ContextTree {
}
return gui.onStagingFocus(forceSecondaryFocused, selectedLineIdx)
},
- Kind: MAIN_CONTEXT,
+ Kind: types.MAIN_CONTEXT,
ViewName: "main",
Key: MAIN_STAGING_CONTEXT_KEY,
},
PatchBuilding: &BasicContext{
- OnFocus: func(opts ...OnFocusOpts) error {
+ OnFocus: func(opts ...types.OnFocusOpts) error {
selectedLineIdx := -1
if len(opts) > 0 && (opts[0].ClickedViewName == "main" || opts[0].ClickedViewName == "secondary") {
selectedLineIdx = opts[0].ClickedViewLineIdx
@@ -162,7 +139,7 @@ func (gui *Gui) contextTree() ContextTree {
return gui.onPatchBuildingFocus(selectedLineIdx)
},
- Kind: MAIN_CONTEXT,
+ Kind: types.MAIN_CONTEXT,
ViewName: "main",
Key: MAIN_PATCH_BUILDING_CONTEXT_KEY,
},
@@ -175,30 +152,30 @@ func (gui *Gui) contextTree() ContextTree {
},
Credentials: &BasicContext{
OnFocus: OnFocusWrapper(gui.handleAskFocused),
- Kind: PERSISTENT_POPUP,
+ Kind: types.PERSISTENT_POPUP,
ViewName: "credentials",
Key: CREDENTIALS_CONTEXT_KEY,
},
Confirmation: &BasicContext{
OnFocus: OnFocusWrapper(gui.handleAskFocused),
- Kind: TEMPORARY_POPUP,
+ Kind: types.TEMPORARY_POPUP,
ViewName: "confirmation",
Key: CONFIRMATION_CONTEXT_KEY,
},
Suggestions: gui.suggestionsListContext(),
CommitMessage: &BasicContext{
OnFocus: OnFocusWrapper(gui.handleCommitMessageFocused),
- Kind: PERSISTENT_POPUP,
+ Kind: types.PERSISTENT_POPUP,
ViewName: "commitMessage",
Key: COMMIT_MESSAGE_CONTEXT_KEY,
},
Search: &BasicContext{
- Kind: PERSISTENT_POPUP,
+ Kind: types.PERSISTENT_POPUP,
ViewName: "search",
Key: SEARCH_CONTEXT_KEY,
},
CommandLog: &BasicContext{
- Kind: EXTRAS_CONTEXT,
+ Kind: types.EXTRAS_CONTEXT,
ViewName: "extras",
Key: COMMAND_LOG_CONTEXT_KEY,
OnGetOptionsMap: gui.getMergingOptions,
@@ -212,72 +189,8 @@ func (gui *Gui) contextTree() ContextTree {
// using this wrapper for when an onFocus function doesn't care about any potential
// props that could be passed
-func OnFocusWrapper(f func() error) func(opts ...OnFocusOpts) error {
- return func(opts ...OnFocusOpts) error {
+func OnFocusWrapper(f func() error) func(opts ...types.OnFocusOpts) error {
+ return func(opts ...types.OnFocusOpts) error {
return f()
}
}
-
-func (tree ContextTree) initialViewContextMap() map[string]Context {
- return map[string]Context{
- "status": tree.Status,
- "files": tree.Files,
- "branches": tree.Branches,
- "commits": tree.BranchCommits,
- "commitFiles": tree.CommitFiles,
- "stash": tree.Stash,
- "menu": tree.Menu,
- "confirmation": tree.Confirmation,
- "credentials": tree.Credentials,
- "commitMessage": tree.CommitMessage,
- "main": tree.Normal,
- "secondary": tree.Normal,
- "extras": tree.CommandLog,
- }
-}
-
-func (tree ContextTree) initialViewTabContextMap() map[string][]tabContext {
- return map[string][]tabContext{
- "branches": {
- {
- tab: "Local Branches",
- contexts: []Context{tree.Branches},
- },
- {
- tab: "Remotes",
- contexts: []Context{
- tree.Remotes,
- tree.RemoteBranches,
- },
- },
- {
- tab: "Tags",
- contexts: []Context{tree.Tags},
- },
- },
- "commits": {
- {
- tab: "Commits",
- contexts: []Context{tree.BranchCommits},
- },
- {
- tab: "Reflog",
- contexts: []Context{
- tree.ReflogCommits,
- },
- },
- },
- "files": {
- {
- tab: "Files",
- contexts: []Context{tree.Files},
- },
- {
- tab: "Submodules",
- contexts: []Context{
- tree.Submodules,
- },
- },
- },
- }
-}
diff --git a/pkg/gui/controllers/bisect_controller.go b/pkg/gui/controllers/bisect_controller.go
new file mode 100644
index 000000000..674e79f76
--- /dev/null
+++ b/pkg/gui/controllers/bisect_controller.go
@@ -0,0 +1,273 @@
+package controllers
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/jesseduffield/lazygit/pkg/commands"
+ "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
+ "github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/config"
+ "github.com/jesseduffield/lazygit/pkg/gui/popup"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+)
+
+type BisectController struct {
+ c *ControllerCommon
+ context types.IListContext
+ git *commands.GitCommand
+
+ getSelectedLocalCommit func() *models.Commit
+ getCommits func() []*models.Commit
+}
+
+var _ types.IController = &BisectController{}
+
+func NewBisectController(
+ c *ControllerCommon,
+ context types.IListContext,
+ git *commands.GitCommand,
+
+ getSelectedLocalCommit func() *models.Commit,
+ getCommits func() []*models.Commit,
+) *BisectController {
+ return &BisectController{
+ c: c,
+ context: context,
+ git: git,
+
+ getSelectedLocalCommit: getSelectedLocalCommit,
+ getCommits: getCommits,
+ }
+}
+
+func (self *BisectController) Keybindings(getKey func(key string) interface{}, config config.KeybindingConfig, guards types.KeybindingGuards) []*types.Binding {
+ bindings := []*types.Binding{
+ {
+ Key: getKey(config.Commits.ViewBisectOptions),
+ Handler: guards.OutsideFilterMode(self.checkSelected(self.openMenu)),
+ Description: self.c.Tr.LcViewBisectOptions,
+ OpensMenu: true,
+ },
+ }
+
+ return bindings
+}
+
+func (self *BisectController) openMenu(commit *models.Commit) error {
+ // no shame in getting this directly rather than using the cached value
+ // given how cheap it is to obtain
+ info := self.git.Bisect.GetInfo()
+ if info.Started() {
+ return self.openMidBisectMenu(info, commit)
+ } else {
+ return self.openStartBisectMenu(info, commit)
+ }
+}
+
+func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error {
+ // if there is not yet a 'current' bisect commit, or if we have
+ // selected the current commit, we need to jump to the next 'current' commit
+ // after we perform a bisect action. The reason we don't unconditionally jump
+ // is that sometimes the user will want to go and mark a few commits as skipped
+ // in a row and they wouldn't want to be jumped back to the current bisect
+ // commit each time.
+ // Originally we were allowing the user to, from the bisect menu, select whether
+ // they were talking about the selected commit or the current bisect commit,
+ // and that was a bit confusing (and required extra keypresses).
+ selectCurrentAfter := info.GetCurrentSha() == "" || info.GetCurrentSha() == commit.Sha
+ // we need to wait to reselect if our bisect commits aren't ancestors of our 'start'
+ // ref, because we'll be reloading our commits in that case.
+ waitToReselect := selectCurrentAfter && !self.git.Bisect.ReachableFromStart(info)
+
+ menuItems := []*popup.MenuItem{
+ {
+ DisplayString: fmt.Sprintf(self.c.Tr.Bisect.Mark, commit.ShortSha(), info.NewTerm()),
+ OnPress: func() error {
+ self.c.LogAction(self.c.Tr.Actions.BisectMark)
+ if err := self.git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
+ return self.c.Error(err)
+ }
+
+ return self.afterMark(selectCurrentAfter, waitToReselect)
+ },
+ },
+ {
+ DisplayString: fmt.Sprintf(self.c.Tr.Bisect.Mark, commit.ShortSha(), info.OldTerm()),
+ OnPress: func() error {
+ self.c.LogAction(self.c.Tr.Actions.BisectMark)
+ if err := self.git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
+ return self.c.Error(err)
+ }
+
+ return self.afterMark(selectCurrentAfter, waitToReselect)
+ },
+ },
+ {
+ DisplayString: fmt.Sprintf(self.c.Tr.Bisect.Skip, commit.ShortSha()),
+ OnPress: func() error {
+ self.c.LogAction(self.c.Tr.Actions.BisectSkip)
+ if err := self.git.Bisect.Skip(commit.Sha); err != nil {
+ return self.c.Error(err)
+ }
+
+ return self.afterMark(selectCurrentAfter, waitToReselect)
+ },
+ },
+ {
+ DisplayString: self.c.Tr.Bisect.ResetOption,
+ OnPress: func() error {
+ return self.Reset()
+ },
+ },
+ }
+
+ return self.c.Menu(popup.CreateMenuOptions{
+ Title: self.c.Tr.Bisect.BisectMenuTitle,
+ Items: menuItems,
+ })
+}
+
+func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error {
+ return self.c.Menu(popup.CreateMenuOptions{
+ Title: self.c.Tr.Bisect.BisectMenuTitle,
+ Items: []*popup.MenuItem{
+ {
+ DisplayString: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortSha(), info.NewTerm()),
+ OnPress: func() error {
+ self.c.LogAction(self.c.Tr.Actions.StartBisect)
+ if err := self.git.Bisect.Start(); err != nil {
+ return self.c.Error(err)
+ }
+
+ if err := self.git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
+ return self.c.Error(err)
+ }
+
+ return self.postBisectCommandRefresh()
+ },
+ },
+ {
+ DisplayString: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortSha(), info.OldTerm()),
+ OnPress: func() error {
+ self.c.LogAction(self.c.Tr.Actions.StartBisect)
+ if err := self.git.Bisect.Start(); err != nil {
+ return self.c.Error(err)
+ }
+
+ if err := self.git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
+ return self.c.Error(err)
+ }
+
+ return self.postBisectCommandRefresh()
+ },
+ },
+ },
+ })
+}
+
+func (self *BisectController) Reset() error {
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.Bisect.ResetTitle,
+ Prompt: self.c.Tr.Bisect.ResetPrompt,
+ HandleConfirm: func() error {
+ self.c.LogAction(self.c.Tr.Actions.ResetBisect)
+ if err := self.git.Bisect.Reset(); err != nil {
+ return self.c.Error(err)
+ }
+
+ return self.postBisectCommandRefresh()
+ },
+ })
+}
+
+func (self *BisectController) showBisectCompleteMessage(candidateShas []string) error {
+ prompt := self.c.Tr.Bisect.CompletePrompt
+ if len(candidateShas) > 1 {
+ prompt = self.c.Tr.Bisect.CompletePromptIndeterminate
+ }
+
+ formattedCommits, err := self.git.Commit.GetCommitsOneline(candidateShas)
+ if err != nil {
+ return self.c.Error(err)
+ }
+
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.Bisect.CompleteTitle,
+ Prompt: fmt.Sprintf(prompt, strings.TrimSpace(formattedCommits)),
+ HandleConfirm: func() error {
+ self.c.LogAction(self.c.Tr.Actions.ResetBisect)
+ if err := self.git.Bisect.Reset(); err != nil {
+ return self.c.Error(err)
+ }
+
+ return self.postBisectCommandRefresh()
+ },
+ })
+}
+
+func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool) error {
+ done, candidateShas, err := self.git.Bisect.IsDone()
+ if err != nil {
+ return self.c.Error(err)
+ }
+
+ if err := self.afterBisectMarkRefresh(selectCurrent, waitToReselect); err != nil {
+ return self.c.Error(err)
+ }
+
+ if done {
+ return self.showBisectCompleteMessage(candidateShas)
+ }
+
+ return nil
+}
+
+func (self *BisectController) postBisectCommandRefresh() error {
+ return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{}})
+}
+
+func (self *BisectController) afterBisectMarkRefresh(selectCurrent bool, waitToReselect bool) error {
+ selectFn := func() {
+ if selectCurrent {
+ self.selectCurrentBisectCommit()
+ }
+ }
+
+ if waitToReselect {
+ return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{}, Then: selectFn})
+ } else {
+ selectFn()
+
+ return self.postBisectCommandRefresh()
+ }
+}
+
+func (self *BisectController) selectCurrentBisectCommit() {
+ info := self.git.Bisect.GetInfo()
+ if info.GetCurrentSha() != "" {
+ // find index of commit with that sha, move cursor to that.
+ for i, commit := range self.getCommits() {
+ if commit.Sha == info.GetCurrentSha() {
+ self.context.GetPanelState().SetSelectedLineIdx(i)
+ _ = self.context.HandleFocus()
+ break
+ }
+ }
+ }
+}
+
+func (self *BisectController) checkSelected(callback func(*models.Commit) error) func() error {
+ return func() error {
+ commit := self.getSelectedLocalCommit()
+ if commit == nil {
+ return nil
+ }
+
+ return callback(commit)
+ }
+}
+
+func (self *BisectController) Context() types.Context {
+ return self.context
+}
diff --git a/pkg/gui/controllers/controller_common.go b/pkg/gui/controllers/controller_common.go
new file mode 100644
index 000000000..013439945
--- /dev/null
+++ b/pkg/gui/controllers/controller_common.go
@@ -0,0 +1,10 @@
+package controllers
+
+import "github.com/jesseduffield/lazygit/pkg/common"
+
+// if Go let me do private struct embedding of structs with public fields (which it should)
+// I would just do that. But alas.
+type ControllerCommon struct {
+ *common.Common
+ IGuiCommon
+}
diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go
new file mode 100644
index 000000000..10d378f9f
--- /dev/null
+++ b/pkg/gui/controllers/files_controller.go
@@ -0,0 +1,737 @@
+package controllers
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+
+ "github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/commands"
+ "github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
+ "github.com/jesseduffield/lazygit/pkg/config"
+ "github.com/jesseduffield/lazygit/pkg/gui/context"
+ "github.com/jesseduffield/lazygit/pkg/gui/filetree"
+ "github.com/jesseduffield/lazygit/pkg/gui/popup"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+ "github.com/jesseduffield/lazygit/pkg/utils"
+)
+
+type FilesController struct {
+ // I've said publicly that I'm against single-letter variable names but in this
+ // case I would actually prefer a _zero_ letter variable name in the form of
+ // struct embedding, but Go does not allow hiding public fields in an embedded struct
+ // to the client
+ c *ControllerCommon
+ context types.IListContext
+ git *commands.GitCommand
+ os *oscommands.OSCommand
+
+ getSelectedFileNode func() *filetree.FileNode
+ allContexts context.ContextTree
+ fileTreeViewModel *filetree.FileTreeViewModel
+ enterSubmodule func(submodule *models.SubmoduleConfig) error
+ getSubmodules func() []*models.SubmoduleConfig
+ setCommitMessage func(message string)
+ getCheckedOutBranch func() *models.Branch
+ withGpgHandling func(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error
+ getFailedCommitMessage func() string
+ getCommits func() []*models.Commit
+ getSelectedPath func() string
+ switchToMergeFn func(path string) error
+ suggestionsHelper ISuggestionsHelper
+ refHelper IRefHelper
+ fileHelper IFileHelper
+ workingTreeHelper IWorkingTreeHelper
+}
+
+var _ types.IController = &FilesController{}
+
+func NewFilesController(
+ c *ControllerCommon,
+ context types.IListContext,
+ git *commands.GitCommand,
+ os *oscommands.OSCommand,
+ getSelectedFileNode func() *filetree.FileNode,
+ allContexts context.ContextTree,
+ fileTreeViewModel *filetree.FileTreeViewModel,
+ enterSubmodule func(submodule *models.SubmoduleConfig) error,
+ getSubmodules func() []*models.SubmoduleConfig,
+ setCommitMessage func(message string),
+ withGpgHandling func(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error,
+ getFailedCommitMessage func() string,
+ getCommits func() []*models.Commit,
+ getSelectedPath func() string,
+ switchToMergeFn func(path string) error,
+ suggestionsHelper ISuggestionsHelper,
+ refHelper IRefHelper,
+ fileHelper IFileHelper,
+ workingTreeHelper IWorkingTreeHelper,
+) *FilesController {
+ return &FilesController{
+ c: c,
+ context: context,
+ git: git,
+ os: os,
+ getSelectedFileNode: getSelectedFileNode,
+ allContexts: allContexts,
+ fileTreeViewModel: fileTreeViewModel,
+ enterSubmodule: enterSubmodule,
+ getSubmodules: getSubmodules,
+ setCommitMessage: setCommitMessage,
+ withGpgHandling: withGpgHandling,
+ getFailedCommitMessage: getFailedCommitMessage,
+ getCommits: getCommits,
+ getSelectedPath: getSelectedPath,
+ switchToMergeFn: switchToMergeFn,
+ suggestionsHelper: suggestionsHelper,
+ refHelper: refHelper,
+ fileHelper: fileHelper,
+ workingTreeHelper: workingTreeHelper,
+ }
+}
+
+func (self *FilesController) Keybindings(getKey func(key string) interface{}, config config.KeybindingConfig, guards types.KeybindingGuards) []*types.Binding {
+ bindings := []*types.Binding{
+ {
+ Key: getKey(config.Universal.Select),
+ Handler: self.checkSelectedFileNode(self.press),
+ Description: self.c.Tr.LcToggleStaged,
+ },
+ {
+ Key: gocui.MouseLeft,
+ Handler: func() error { return self.context.HandleClick(self.checkSelectedFileNode(self.press)) },
+ },
+ {
+ Key: getKey(""), // TODO: softcode
+ Handler: self.handleStatusFilterPressed,
+ Description: self.c.Tr.LcFileFilter,
+ },
+ {
+ Key: getKey(config.Files.CommitChanges),
+ Handler: self.HandleCommitPress,
+ Description: self.c.Tr.CommitChanges,
+ },
+ {
+ Key: getKey(config.Files.CommitChangesWithoutHook),
+ Handler: self.HandleWIPCommitPress,
+ Description: self.c.Tr.LcCommitChangesWithoutHook,
+ },
+ {
+ Key: getKey(config.Files.AmendLastCommit),
+ Handler: self.handleAmendCommitPress,
+ Description: self.c.Tr.AmendLastCommit,
+ },
+ {
+ Key: getKey(config.Files.CommitChangesWithEditor),
+ Handler: self.HandleCommitEditorPress,
+ Description: self.c.Tr.CommitChangesWithEditor,
+ },
+ {
+ Key: getKey(config.Universal.Edit),
+ Handler: self.edit,
+ Description: self.c.Tr.LcEditFile,
+ },
+ {
+ Key: getKey(config.Universal.OpenFile),
+ Handler: self.Open,
+ Description: self.c.Tr.LcOpenFile,
+ },
+ {
+ Key: getKey(config.Files.IgnoreFile),
+ Handler: self.ignore,
+ Description: self.c.Tr.LcIgnoreFile,
+ },
+ {
+ Key: getKey(config.Files.RefreshFiles),
+ Handler: self.refresh,
+ Description: self.c.Tr.LcRefreshFiles,
+ },
+ {
+ Key: getKey(config.Files.StashAllChanges),
+ Handler: self.stash,
+ Description: self.c.Tr.LcStashAllChanges,
+ },
+ {
+ Key: getKey(config.Files.ViewStashOptions),
+ Handler: self.createStashMenu,
+ Description: self.c.Tr.LcViewStashOptions,
+ OpensMenu: true,
+ },
+ {
+ Key: getKey(config.Files.ToggleStagedAll),
+ Handler: self.stageAll,
+ Description: self.c.Tr.LcToggleStagedAll,
+ },
+ {
+ Key: getKey(config.Universal.GoInto),
+ Handler: self.enter,
+ Description: self.c.Tr.FileEnter,
+ },
+ {
+ ViewName: "",
+ Key: getKey(config.Universal.ExecuteCustomCommand),
+ Handler: self.handleCustomCommand,
+ Description: self.c.Tr.LcExecuteCustomCommand,
+ },
+ {
+ Key: getKey(config.Commits.ViewResetOptions),
+ Handler: self.createResetMenu,
+ Description: self.c.Tr.LcViewResetToUpstreamOptions,
+ OpensMenu: true,
+ },
+ {
+ Key: getKey(config.Files.ToggleTreeView),
+ Handler: self.toggleTreeView,
+ Description: self.c.Tr.LcToggleTreeView,
+ },
+ {
+ Key: getKey(config.Files.OpenMergeTool),
+ Handler: self.OpenMergeTool,
+ Description: self.c.Tr.LcOpenMergeTool,
+ },
+ }
+
+ return append(bindings, self.context.Keybindings(getKey, config, guards)...)
+}
+
+func (self *FilesController) press(node *filetree.FileNode) error {
+ if node.IsLeaf() {
+ file := node.File
+
+ if file.HasInlineMergeConflicts {
+ return self.c.PushContext(self.allContexts.Merging)
+ }
+
+ if file.HasUnstagedChanges {
+ self.c.LogAction(self.c.Tr.Actions.StageFile)
+ if err := self.git.WorkingTree.StageFile(file.Name); err != nil {
+ return self.c.Error(err)
+ }
+ } else {
+ self.c.LogAction(self.c.Tr.Actions.UnstageFile)
+ if err := self.git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
+ return self.c.Error(err)
+ }
+ }
+ } else {
+ // if any files within have inline merge conflicts we can't stage or unstage,
+ // or it'll end up with those >>>>>> lines actually staged
+ if node.GetHasInlineMergeConflicts() {
+ return self.c.ErrorMsg(self.c.Tr.ErrStageDirWithInlineMergeConflicts)
+ }
+
+ if node.GetHasUnstagedChanges() {
+ self.c.LogAction(self.c.Tr.Actions.StageFile)
+ if err := self.git.WorkingTree.StageFile(node.Path); err != nil {
+ return self.c.Error(err)
+ }
+ } else {
+ // pretty sure it doesn't matter that we're always passing true here
+ self.c.LogAction(self.c.Tr.Actions.UnstageFile)
+ if err := self.git.WorkingTree.UnStageFile([]string{node.Path}, true); err != nil {
+ return self.c.Error(err)
+ }
+ }
+ }
+
+ if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
+ return err
+ }
+
+ return self.context.HandleFocus()
+}
+
+func (self *FilesController) checkSelectedFileNode(callback func(*filetree.FileNode) error) func() error {
+ return func() error {
+ node := self.getSelectedFileNode()
+ if node == nil {
+ return nil
+ }
+
+ return callback(node)
+ }
+}
+
+func (self *FilesController) checkSelectedFile(callback func(*models.File) error) func() error {
+ return func() error {
+ file := self.getSelectedFile()
+ if file == nil {
+ return nil
+ }
+
+ return callback(file)
+ }
+}
+
+func (self *FilesController) Context() types.Context {
+ return self.context
+}
+
+func (self *FilesController) getSelectedFile() *models.File {
+ node := self.getSelectedFileNode()
+ if node == nil {
+ return nil
+ }
+ return node.File
+}
+
+func (self *FilesController) enter() error {
+ return self.EnterFile(types.OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1})
+}
+
+func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
+ node := self.getSelectedFileNode()
+ if node == nil {
+ return nil
+ }
+
+ if node.File == nil {
+ return self.handleToggleDirCollapsed()
+ }
+
+ file := node.File
+
+ submoduleConfigs := self.getSubmodules()
+ if file.IsSubmodule(submoduleConfigs) {
+ submoduleConfig := file.SubmoduleConfig(submoduleConfigs)
+ return self.enterSubmodule(submoduleConfig)
+ }
+
+ if file.HasInlineMergeConflicts {
+ return self.switchToMerge()
+ }
+ if file.HasMergeConflicts {
+ return self.c.ErrorMsg(self.c.Tr.FileStagingRequirements)
+ }
+
+ return self.c.PushContext(self.allContexts.Staging, opts)
+}
+
+func (self *FilesController) allFilesStaged() bool {
+ for _, file := range self.fileTreeViewModel.GetAllFiles() {
+ if file.HasUnstagedChanges {
+ return false
+ }
+ }
+ return true
+}
+
+func (self *FilesController) stageAll() error {
+ var err error
+ if self.allFilesStaged() {
+ self.c.LogAction(self.c.Tr.Actions.UnstageAllFiles)
+ err = self.git.WorkingTree.UnstageAll()
+ } else {
+ self.c.LogAction(self.c.Tr.Actions.StageAllFiles)
+ err = self.git.WorkingTree.StageAll()
+ }
+ if err != nil {
+ _ = self.c.Error(err)
+ }
+
+ if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
+ return err
+ }
+
+ return self.allContexts.Files.HandleFocus()
+}
+
+func (self *FilesController) ignore() error {
+ node := self.getSelectedFileNode()
+ if node == nil {
+ return nil
+ }
+
+ if node.GetPath() == ".gitignore" {
+ return self.c.ErrorMsg("Cannot ignore .gitignore")
+ }
+
+ unstageFiles := func() error {
+ return node.ForEachFile(func(file *models.File) error {
+ if file.HasStagedChanges {
+ if err := self.git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
+ return err
+ }
+ }
+
+ return nil
+ })
+ }
+
+ if node.GetIsTracked() {
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.IgnoreTracked,
+ Prompt: self.c.Tr.IgnoreTrackedPrompt,
+ HandleConfirm: func() error {
+ self.c.LogAction(self.c.Tr.Actions.IgnoreFile)
+ // not 100% sure if this is necessary but I'll assume it is
+ if err := unstageFiles(); err != nil {
+ return err
+ }
+
+ if err := self.git.WorkingTree.RemoveTrackedFiles(node.GetPath()); err != nil {
+ return err
+ }
+
+ if err := self.git.WorkingTree.Ignore(node.GetPath()); err != nil {
+ return err
+ }
+ return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
+ },
+ })
+ }
+
+ self.c.LogAction(self.c.Tr.Actions.IgnoreFile)
+
+ if err := unstageFiles(); err != nil {
+ return err
+ }
+
+ if err := self.git.WorkingTree.Ignore(node.GetPath()); err != nil {
+ return self.c.Error(err)
+ }
+
+ return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
+}
+
+func (self *FilesController) HandleWIPCommitPress() error {
+ skipHookPrefix := self.c.UserConfig.Git.SkipHookPrefix
+ if skipHookPrefix == "" {
+ return self.c.ErrorMsg(self.c.Tr.SkipHookPrefixNotConfigured)
+ }
+
+ self.setCommitMessage(skipHookPrefix)
+
+ return self.HandleCommitPress()
+}
+
+func (self *FilesController) commitPrefixConfigForRepo() *config.CommitPrefixConfig {
+ cfg, ok := self.c.UserConfig.Git.CommitPrefixes[utils.GetCurrentRepoName()]
+ if !ok {
+ return nil
+ }
+
+ return &cfg
+}
+
+func (self *FilesController) prepareFilesForCommit() error {
+ noStagedFiles := !self.workingTreeHelper.AnyStagedFiles()
+ if noStagedFiles && self.c.UserConfig.Gui.SkipNoStagedFilesWarning {
+ self.c.LogAction(self.c.Tr.Actions.StageAllFiles)
+ err := self.git.WorkingTree.StageAll()
+ if err != nil {
+ return err
+ }
+
+ return self.syncRefresh()
+ }
+
+ return nil
+}
+
+// for when you need to refetch files before continuing an action. Runs synchronously.
+func (self *FilesController) syncRefresh() error {
+ return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}})
+}
+
+func (self *FilesController) refresh() error {
+ return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
+}
+
+func (self *FilesController) HandleCommitPress() error {
+ if err := self.prepareFilesForCommit(); err != nil {
+ return self.c.Error(err)
+ }
+
+ if self.fileTreeViewModel.GetItemsLength() == 0 {
+ return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
+ }
+
+ if !self.workingTreeHelper.AnyStagedFiles() {
+ return self.promptToStageAllAndRetry(self.HandleCommitPress)
+ }
+
+ failedCommitMessage := self.getFailedCommitMessage()
+ if len(failedCommitMessage) > 0 {
+ self.setCommitMessage(failedCommitMessage)
+ } else {
+ commitPrefixConfig := self.commitPrefixConfigForRepo()
+ if commitPrefixConfig != nil {
+ prefixPattern := commitPrefixConfig.Pattern
+ prefixReplace := commitPrefixConfig.Replace
+ rgx, err := regexp.Compile(prefixPattern)
+ if err != nil {
+ return self.c.ErrorMsg(fmt.Sprintf("%s: %s", self.c.Tr.LcCommitPrefixPatternError, err.Error()))
+ }
+ prefix := rgx.ReplaceAllString(self.getCheckedOutBranch().Name, prefixReplace)
+ self.setCommitMessage(prefix)
+ }
+ }
+
+ if err := self.c.PushContext(self.allContexts.CommitMessage); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (self *FilesController) promptToStageAllAndRetry(retry func() error) error {
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.NoFilesStagedTitle,
+ Prompt: self.c.Tr.NoFilesStagedPrompt,
+ HandleConfirm: func() error {
+ self.c.LogAction(self.c.Tr.Actions.StageAllFiles)
+ if err := self.git.WorkingTree.StageAll(); err != nil {
+ return self.c.Error(err)
+ }
+ if err := self.syncRefresh(); err != nil {
+ return self.c.Error(err)
+ }
+
+ return retry()
+ },
+ })
+}
+
+func (self *FilesController) handleAmendCommitPress() error {
+ if self.fileTreeViewModel.GetItemsLength() == 0 {
+ return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
+ }
+
+ if !self.workingTreeHelper.AnyStagedFiles() {
+ return self.promptToStageAllAndRetry(self.handleAmendCommitPress)
+ }
+
+ if len(self.getCommits()) == 0 {
+ return self.c.ErrorMsg(self.c.Tr.NoCommitToAmend)
+ }
+
+ return self.c.Ask(popup.AskOpts{
+ Title: strings.Title(self.c.Tr.AmendLastCommit),
+ Prompt: self.c.Tr.SureToAmend,
+ HandleConfirm: func() error {
+ cmdObj := self.git.Commit.AmendHeadCmdObj()
+ self.c.LogAction(self.c.Tr.Actions.AmendCommit)
+ return self.withGpgHandling(cmdObj, self.c.Tr.AmendingStatus, nil)
+ },
+ })
+}
+
+// HandleCommitEditorPress - handle when the user wants to commit changes via
+// their editor rather than via the popup panel
+func (self *FilesController) HandleCommitEditorPress() error {
+ if self.fileTreeViewModel.GetItemsLength() == 0 {
+ return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
+ }
+
+ if !self.workingTreeHelper.AnyStagedFiles() {
+ return self.promptToStageAllAndRetry(self.HandleCommitEditorPress)
+ }
+
+ self.c.LogAction(self.c.Tr.Actions.Commit)
+ return self.c.RunSubprocessAndRefresh(
+ self.git.Commit.CommitEditorCmdObj(),
+ )
+}
+
+func (self *FilesController) handleStatusFilterPressed() error {
+ return self.c.Menu(popup.CreateMenuOptions{
+ Title: self.c.Tr.FilteringMenuTitle,
+ Items: []*popup.MenuItem{
+ {
+ DisplayString: self.c.Tr.FilterStagedFiles,
+ OnPress: func() error {
+ return self.setStatusFiltering(filetree.DisplayStaged)
+ },
+ },
+ {
+ DisplayString: self.c.Tr.FilterUnstagedFiles,
+ OnPress: func() error {
+ return self.setStatusFiltering(filetree.DisplayUnstaged)
+ },
+ },
+ {
+ DisplayString: self.c.Tr.ResetCommitFilterState,
+ OnPress: func() error {
+ return self.setStatusFiltering(filetree.DisplayAll)
+ },
+ },
+ },
+ })
+}
+
+func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error {
+ self.fileTreeViewModel.SetFilter(filter)
+ return self.c.PostRefreshUpdate(self.context)
+}
+
+func (self *FilesController) edit() error {
+ node := self.getSelectedFileNode()
+ if node == nil {
+ return nil
+ }
+
+ if node.File == nil {
+ return self.c.ErrorMsg(self.c.Tr.ErrCannotEditDirectory)
+ }
+
+ return self.fileHelper.EditFile(node.GetPath())
+}
+
+func (self *FilesController) Open() error {
+ node := self.getSelectedFileNode()
+ if node == nil {
+ return nil
+ }
+
+ return self.fileHelper.OpenFile(node.GetPath())
+}
+
+func (self *FilesController) switchToMerge() error {
+ file := self.getSelectedFile()
+ if file == nil {
+ return nil
+ }
+
+ self.switchToMergeFn(path)
+}
+
+func (self *FilesController) handleCustomCommand() error {
+ return self.c.Prompt(popup.PromptOpts{
+ Title: self.c.Tr.CustomCommand,
+ FindSuggestionsFunc: self.suggestionsHelper.GetCustomCommandsHistorySuggestionsFunc(),
+ HandleConfirm: func(command string) error {
+ self.c.GetAppState().CustomCommandsHistory = utils.Limit(
+ utils.Uniq(
+ append(self.c.GetAppState().CustomCommandsHistory, command),
+ ),
+ 1000,
+ )
+
+ err := self.c.SaveAppState()
+ if err != nil {
+ self.c.Log.Error(err)
+ }
+
+ self.c.LogAction(self.c.Tr.Actions.CustomCommand)
+ return self.c.RunSubprocessAndRefresh(
+ self.os.Cmd.NewShell(command),
+ )
+ },
+ })
+}
+
+func (self *FilesController) createStashMenu() error {
+ return self.c.Menu(popup.CreateMenuOptions{
+ Title: self.c.Tr.LcStashOptions,
+ Items: []*popup.MenuItem{
+ {
+ DisplayString: self.c.Tr.LcStashAllChanges,
+ OnPress: func() error {
+ self.c.LogAction(self.c.Tr.Actions.StashAllChanges)
+ return self.handleStashSave(self.git.Stash.Save)
+ },
+ },
+ {
+ DisplayString: self.c.Tr.LcStashStagedChanges,
+ OnPress: func() error {
+ self.c.LogAction(self.c.Tr.Actions.StashStagedChanges)
+ return self.handleStashSave(self.git.Stash.SaveStagedChanges)
+ },
+ },
+ },
+ })
+}
+
+func (self *FilesController) stash() error {
+ return self.handleStashSave(self.git.Stash.Save)
+}
+
+func (self *FilesController) createResetMenu() error {
+ return self.refHelper.CreateGitResetMenu("@{upstream}")
+}
+
+func (self *FilesController) handleToggleDirCollapsed() error {
+ node := self.getSelectedFileNode()
+ if node == nil {
+ return nil
+ }
+
+ self.fileTreeViewModel.ToggleCollapsed(node.GetPath())
+
+ if err := self.c.PostRefreshUpdate(self.allContexts.Files); err != nil {
+ self.c.Log.Error(err)
+ }
+
+ return nil
+}
+
+func (self *FilesController) toggleTreeView() error {
+ // get path of currently selected file
+ path := self.getSelectedPath()
+
+ self.fileTreeViewModel.ToggleShowTree()
+
+ // find that same node in the new format and move the cursor to it
+ if path != "" {
+ self.fileTreeViewModel.ExpandToPath(path)
+ index, found := self.fileTreeViewModel.GetIndexForPath(path)
+ if found {
+ self.context.GetPanelState().SetSelectedLineIdx(index)
+ }
+ }
+
+ return self.c.PostRefreshUpdate(self.context)
+}
+
+func (self *FilesController) OpenMergeTool() error {
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.MergeToolTitle,
+ Prompt: self.c.Tr.MergeToolPrompt,
+ HandleConfirm: func() error {
+ self.c.LogAction(self.c.Tr.Actions.OpenMergeTool)
+ return self.c.RunSubprocessAndRefresh(
+ self.git.WorkingTree.OpenMergeToolCmdObj(),
+ )
+ },
+ })
+}
+
+func (self *FilesController) ResetSubmodule(submodule *models.SubmoduleConfig) error {
+ return self.c.WithWaitingStatus(self.c.Tr.LcResettingSubmoduleStatus, func() error {
+ self.c.LogAction(self.c.Tr.Actions.ResetSubmodule)
+
+ file := self.workingTreeHelper.FileForSubmodule(submodule)
+ if file != nil {
+ if err := self.git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
+ return self.c.Error(err)
+ }
+ }
+
+ if err := self.git.Submodule.Stash(submodule); err != nil {
+ return self.c.Error(err)
+ }
+ if err := self.git.Submodule.Reset(submodule); err != nil {
+ return self.c.Error(err)
+ }
+
+ return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.SUBMODULES}})
+ })
+}
+
+func (self *FilesController) handleStashSave(stashFunc func(message string) error) error {
+ if !self.workingTreeHelper.IsWorkingTreeDirty() {
+ return self.c.ErrorMsg(self.c.Tr.NoTrackedStagedFilesStash)
+ }
+
+ return self.c.Prompt(popup.PromptOpts{
+ Title: self.c.Tr.StashChanges,
+ HandleConfirm: func(stashComment string) error {
+ if err := stashFunc(stashComment); err != nil {
+ return self.c.Error(err)
+ }
+ return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}})
+ },
+ })
+}
diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go
new file mode 100644
index 000000000..1d79bd2dd
--- /dev/null
+++ b/pkg/gui/controllers/local_commits_controller.go
@@ -0,0 +1,783 @@
+package controllers
+
+import (
+ "fmt"
+
+ "github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/commands"
+ "github.com/jesseduffield/lazygit/pkg/commands/hosting_service"
+ "github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
+ "github.com/jesseduffield/lazygit/pkg/config"
+ "github.com/jesseduffield/lazygit/pkg/gui/popup"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+ "github.com/jesseduffield/lazygit/pkg/utils"
+)
+
+type (
+ CheckoutRefFn func(refName string, opts types.CheckoutRefOptions) error
+ CreateGitResetMenuFn func(refName string) error
+ SwitchToCommitFilesContextFn func(SwitchToCommitFilesContextOpts) error
+ CreateTagMenuFn func(commitSha string) error
+ GetHostingServiceMgrFn func() *hosting_service.HostingServiceMgr
+ PullFilesFn func() error
+ CheckMergeOrRebase func(error) error
+ OpenSearchFn func(viewName string) error
+)
+
+type LocalCommitsController struct {
+ c *ControllerCommon
+ context types.IListContext
+ os *oscommands.OSCommand
+ git *commands.GitCommand
+ refHelper IRefHelper
+
+ getSelectedLocalCommit func() *models.Commit
+ getCommits func() []*models.Commit
+ getSelectedLocalCommitIdx func() int
+ checkMergeOrRebase CheckMergeOrRebase
+ pullFiles PullFilesFn
+ createTagMenu CreateTagMenuFn
+ getHostingServiceMgr GetHostingServiceMgrFn
+ switchToCommitFilesContext SwitchToCommitFilesContextFn
+ openSearch OpenSearchFn
+ getLimitCommits func() bool
+ setLimitCommits func(bool)
+ getShowWholeGitGraph func() bool
+ setShowWholeGitGraph func(bool)
+}
+
+var _ types.IController = &LocalCommitsController{}
+
+func NewLocalCommitsController(
+ c *ControllerCommon,
+ context types.IListContext,
+ os *oscommands.OSCommand,
+ git *commands.GitCommand,
+ refHelper IRefHelper,
+ getSelectedLocalCommit func() *models.Commit,
+ getCommits func() []*models.Commit,
+ getSelectedLocalCommitIdx func() int,
+ checkMergeOrRebase CheckMergeOrRebase,
+ pullFiles PullFilesFn,
+ createTagMenu CreateTagMenuFn,
+ getHostingServiceMgr GetHostingServiceMgrFn,
+ switchToCommitFilesContext SwitchToCommitFilesContextFn,
+ openSearch OpenSearchFn,
+ getLimitCommits func() bool,
+ setLimitCommits func(bool),
+ getShowWholeGitGraph func() bool,
+ setShowWholeGitGraph func(bool),
+) *LocalCommitsController {
+ return &LocalCommitsController{
+ c: c,
+ context: context,
+ os: os,
+ git: git,
+ refHelper: refHelper,
+ getSelectedLocalCommit: getSelectedLocalCommit,
+ getCommits: getCommits,
+ getSelectedLocalCommitIdx: getSelectedLocalCommitIdx,
+ checkMergeOrRebase: checkMergeOrRebase,
+ pullFiles: pullFiles,
+ createTagMenu: createTagMenu,
+ getHostingServiceMgr: getHostingServiceMgr,
+ switchToCommitFilesContext: switchToCommitFilesContext,
+ openSearch: openSearch,
+ getLimitCommits: getLimitCommits,
+ setLimitCommits: setLimitCommits,
+ getShowWholeGitGraph: getShowWholeGitGraph,
+ setShowWholeGitGraph: setShowWholeGitGraph,
+ }
+}
+
+func (self *LocalCommitsController) Keybindings(
+ getKey func(key string) interface{},
+ config config.KeybindingConfig,
+ guards types.KeybindingGuards,
+) []*types.Binding {
+ outsideFilterModeBindings := []*types.Binding{
+ {
+ Key: getKey(config.Commits.SquashDown),
+ Handler: self.squashDown,
+ Description: self.c.Tr.LcSquashDown,
+ },
+ {
+ Key: getKey(config.Commits.MarkCommitAsFixup),
+ Handler: self.fixup,
+ Description: self.c.Tr.LcFixupCommit,
+ },
+ {
+ Key: getKey(config.Commits.RenameCommit),
+ Handler: self.checkSelected(self.reword),
+ Description: self.c.Tr.LcRewordCommit,
+ },
+ {
+ Key: getKey(config.Commits.RenameCommitWithEditor),
+ Handler: self.rewordEditor,
+ Description: self.c.Tr.LcRenameCommitEditor,
+ },
+ {
+ Key: getKey(config.Universal.Remove),
+ Handler: self.drop,
+ Description: self.c.Tr.LcDeleteCommit,
+ },
+ {
+ Key: getKey(config.Universal.Edit),
+ Handler: self.edit,
+ Description: self.c.Tr.LcEditCommit,
+ },
+ {
+ Key: getKey(config.Commits.PickCommit),
+ Handler: self.pick,
+ Description: self.c.Tr.LcPickCommit,
+ },
+ {
+ Key: getKey(config.Commits.CreateFixupCommit),
+ Handler: self.checkSelected(self.handleCreateFixupCommit),
+ Description: self.c.Tr.LcCreateFixupCommit,
+ },
+ {
+ Key: getKey(config.Commits.SquashAboveCommits),
+ Handler: self.checkSelected(self.handleSquashAllAboveFixupCommits),
+ Description: self.c.Tr.LcSquashAboveCommits,
+ },
+ {
+ Key: getKey(config.Commits.MoveDownCommit),
+ Handler: self.handleCommitMoveDown,
+ Description: self.c.Tr.LcMoveDownCommit,
+ },
+ {
+ Key: getKey(config.Commits.MoveUpCommit),
+ Handler: self.handleCommitMoveUp,
+ Description: self.c.Tr.LcMoveUpCommit,
+ },
+ {
+ Key: getKey(config.Commits.AmendToCommit),
+ Handler: self.handleCommitAmendTo,
+ Description: self.c.Tr.LcAmendToCommit,
+ },
+ {
+ Key: getKey(config.Commits.RevertCommit),
+ Handler: self.checkSelected(self.handleCommitRevert),
+ Description: self.c.Tr.LcRevertCommit,
+ },
+ // overriding these navigation keybindings because we might need to load
+ // more commits on demand
+ {
+ Key: getKey(config.Universal.StartSearch),
+ Handler: func() error { return self.handleOpenSearch("commits") },
+ Description: self.c.Tr.LcStartSearch,
+ Tag: "navigation",
+ },
+ {
+ Key: getKey(config.Universal.GotoBottom),
+ Handler: self.gotoBottom,
+ Description: self.c.Tr.LcGotoBottom,
+ Tag: "navigation",
+ },
+ {
+ Key: gocui.MouseLeft,
+ Handler: func() error { return self.context.HandleClick(self.checkSelected(self.enter)) },
+ },
+ }
+
+ for _, binding := range outsideFilterModeBindings {
+ binding.Handler = guards.OutsideFilterMode(binding.Handler)
+ }
+
+ bindings := append(outsideFilterModeBindings, []*types.Binding{
+ {
+ Key: getKey(config.Commits.OpenLogMenu),
+ Handler: self.handleOpenLogMenu,
+ Description: self.c.Tr.LcOpenLogMenu,
+ OpensMenu: true,
+ },
+ {
+ Key: getKey(config.Commits.ViewResetOptions),
+ Handler: self.checkSelected(self.handleCreateCommitResetMenu),
+ Description: self.c.Tr.LcResetToThisCommit,
+ },
+ {
+ Key: getKey(config.Universal.GoInto),
+ Handler: self.checkSelected(self.enter),
+ Description: self.c.Tr.LcViewCommitFiles,
+ },
+ {
+ Key: getKey(config.Commits.CheckoutCommit),
+ Handler: self.checkSelected(self.handleCheckoutCommit),
+ Description: self.c.Tr.LcCheckoutCommit,
+ },
+ {
+ Key: getKey(config.Commits.TagCommit),
+ Handler: self.checkSelected(self.handleTagCommit),
+ Description: self.c.Tr.LcTagCommit,
+ },
+ {
+ Key: getKey(config.Commits.CopyCommitMessageToClipboard),
+ Handler: self.checkSelected(self.handleCopySelectedCommitMessageToClipboard),
+ Description: self.c.Tr.LcCopyCommitMessageToClipboard,
+ },
+ {
+ Key: getKey(config.Commits.OpenInBrowser),
+ Handler: self.checkSelected(self.handleOpenCommitInBrowser),
+ Description: self.c.Tr.LcOpenCommitInBrowser,
+ },
+ }...)
+
+ return append(bindings, self.context.Keybindings(getKey, config, guards)...)
+}
+
+func (self *LocalCommitsController) squashDown() error {
+ if len(self.getCommits()) <= 1 {
+ return self.c.ErrorMsg(self.c.Tr.YouNoCommitsToSquash)
+ }
+
+ applied, err := self.handleMidRebaseCommand("squash")
+ if err != nil {
+ return err
+ }
+ if applied {
+ return nil
+ }
+
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.Squash,
+ Prompt: self.c.Tr.SureSquashThisCommit,
+ HandleConfirm: func() error {
+ return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func() error {
+ self.c.LogAction(self.c.Tr.Actions.SquashCommitDown)
+ return self.interactiveRebase("squash")
+ })
+ },
+ })
+}
+
+func (self *LocalCommitsController) fixup() error {
+ if len(self.getCommits()) <= 1 {
+ return self.c.ErrorMsg(self.c.Tr.YouNoCommitsToSquash)
+ }
+
+ applied, err := self.handleMidRebaseCommand("fixup")
+ if err != nil {
+ return err
+ }
+ if applied {
+ return nil
+ }
+
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.Fixup,
+ Prompt: self.c.Tr.SureFixupThisCommit,
+ HandleConfirm: func() error {
+ return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func() error {
+ self.c.LogAction(self.c.Tr.Actions.FixupCommit)
+ return self.interactiveRebase("fixup")
+ })
+ },
+ })
+}
+
+func (self *LocalCommitsController) reword(commit *models.Commit) error {
+ applied, err := self.handleMidRebaseCommand("reword")
+ if err != nil {
+ return err
+ }
+ if applied {
+ return nil
+ }
+
+ message, err := self.git.Commit.GetCommitMessage(commit.Sha)
+ if err != nil {
+ return self.c.Error(err)
+ }
+
+ // TODO: use the commit message panel here
+ return self.c.Prompt(popup.PromptOpts{
+ Title: self.c.Tr.LcRewordCommit,
+ InitialContent: message,
+ HandleConfirm: func(response string) error {
+ self.c.LogAction(self.c.Tr.Actions.RewordCommit)
+ if err := self.git.Rebase.RewordCommit(self.getCommits(), self.getSelectedLocalCommitIdx(), response); err != nil {
+ return self.c.Error(err)
+ }
+
+ return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
+ },
+ })
+}
+
+func (self *LocalCommitsController) rewordEditor() error {
+ applied, err := self.handleMidRebaseCommand("reword")
+ if err != nil {
+ return err
+ }
+ if applied {
+ return nil
+ }
+
+ self.c.LogAction(self.c.Tr.Actions.RewordCommit)
+ subProcess, err := self.git.Rebase.RewordCommitInEditor(
+ self.getCommits(), self.getSelectedLocalCommitIdx(),
+ )
+ if err != nil {
+ return self.c.Error(err)
+ }
+ if subProcess != nil {
+ return self.c.RunSubprocessAndRefresh(subProcess)
+ }
+
+ return nil
+}
+
+func (self *LocalCommitsController) drop() error {
+ applied, err := self.handleMidRebaseCommand("drop")
+ if err != nil {
+ return err
+ }
+ if applied {
+ return nil
+ }
+
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.DeleteCommitTitle,
+ Prompt: self.c.Tr.DeleteCommitPrompt,
+ HandleConfirm: func() error {
+ return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func() error {
+ self.c.LogAction(self.c.Tr.Actions.DropCommit)
+ return self.interactiveRebase("drop")
+ })
+ },
+ })
+}
+
+func (self *LocalCommitsController) edit() error {
+ applied, err := self.handleMidRebaseCommand("edit")
+ if err != nil {
+ return err
+ }
+ if applied {
+ return nil
+ }
+
+ return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
+ self.c.LogAction(self.c.Tr.Actions.EditCommit)
+ return self.interactiveRebase("edit")
+ })
+}
+
+func (self *LocalCommitsController) pick() error {
+ applied, err := self.handleMidRebaseCommand("pick")
+ if err != nil {
+ return err
+ }
+ if applied {
+ return nil
+ }
+
+ // at this point we aren't actually rebasing so we will interpret this as an
+ // attempt to pull. We might revoke this later after enabling configurable keybindings
+ return self.pullFiles()
+}
+
+func (self *LocalCommitsController) interactiveRebase(action string) error {
+ err := self.git.Rebase.InteractiveRebase(self.getCommits(), self.getSelectedLocalCommitIdx(), action)
+ return self.checkMergeOrRebase(err)
+}
+
+// handleMidRebaseCommand sees if the selected commit is in fact a rebasing
+// commit meaning you are trying to edit the todo file rather than actually
+// begin a rebase. It then updates the todo file with that action
+func (self *LocalCommitsController) handleMidRebaseCommand(action string) (bool, error) {
+ selectedCommit := self.getSelectedLocalCommit()
+ if selectedCommit.Status != "rebasing" {
+ return false, nil
+ }
+
+ // for now we do not support setting 'reword' because it requires an editor
+ // and that means we either unconditionally wait around for the subprocess to ask for
+ // our input or we set a lazygit client as the EDITOR env variable and have it
+ // request us to edit the commit message when prompted.
+ if action == "reword" {
+ return true, self.c.ErrorMsg(self.c.Tr.LcRewordNotSupported)
+ }
+
+ self.c.LogAction("Update rebase TODO")
+ self.c.LogCommand(
+ fmt.Sprintf("Updating rebase action of commit %s to '%s'", selectedCommit.ShortSha(), action),
+ false,
+ )
+
+ if err := self.git.Rebase.EditRebaseTodo(
+ self.getSelectedLocalCommitIdx(), action,
+ ); err != nil {
+ return false, self.c.Error(err)
+ }
+
+ return true, self.c.Refresh(types.RefreshOptions{
+ Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
+ })
+}
+
+func (self *LocalCommitsController) handleCommitMoveDown() error {
+ index := self.context.GetPanelState().GetSelectedLineIdx()
+ commits := self.getCommits()
+ selectedCommit := self.getCommits()[index]
+ if selectedCommit.Status == "rebasing" {
+ if commits[index+1].Status != "rebasing" {
+ return nil
+ }
+
+ // logging directly here because MoveTodoDown doesn't have enough information
+ // to provide a useful log
+ self.c.LogAction(self.c.Tr.Actions.MoveCommitDown)
+ self.c.LogCommand(fmt.Sprintf("Moving commit %s down", selectedCommit.ShortSha()), false)
+
+ if err := self.git.Rebase.MoveTodoDown(index); err != nil {
+ return self.c.Error(err)
+ }
+ self.context.HandleNextLine()
+ return self.c.Refresh(types.RefreshOptions{
+ Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
+ })
+ }
+
+ return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func() error {
+ self.c.LogAction(self.c.Tr.Actions.MoveCommitDown)
+ err := self.git.Rebase.MoveCommitDown(self.getCommits(), index)
+ if err == nil {
+ self.context.HandleNextLine()
+ }
+ return self.checkMergeOrRebase(err)
+ })
+}
+
+func (self *LocalCommitsController) handleCommitMoveUp() error {
+ index := self.context.GetPanelState().GetSelectedLineIdx()
+ if index == 0 {
+ return nil
+ }
+
+ selectedCommit := self.getCommits()[index]
+ if selectedCommit.Status == "rebasing" {
+ // logging directly here because MoveTodoDown doesn't have enough information
+ // to provide a useful log
+ self.c.LogAction(self.c.Tr.Actions.MoveCommitUp)
+ self.c.LogCommand(
+ fmt.Sprintf("Moving commit %s up", selectedCommit.ShortSha()),
+ false,
+ )
+
+ if err := self.git.Rebase.MoveTodoDown(index - 1); err != nil {
+ return self.c.Error(err)
+ }
+ self.context.HandlePrevLine()
+ return self.c.Refresh(types.RefreshOptions{
+ Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
+ })
+ }
+
+ return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func() error {
+ self.c.LogAction(self.c.Tr.Actions.MoveCommitUp)
+ err := self.git.Rebase.MoveCommitDown(self.getCommits(), index-1)
+ if err == nil {
+ self.context.HandlePrevLine()
+ }
+ return self.checkMergeOrRebase(err)
+ })
+}
+
+func (self *LocalCommitsController) handleCommitAmendTo() error {
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.AmendCommitTitle,
+ Prompt: self.c.Tr.AmendCommitPrompt,
+ HandleConfirm: func() error {
+ return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error {
+ self.c.LogAction(self.c.Tr.Actions.AmendCommit)
+ err := self.git.Rebase.AmendTo(self.getSelectedLocalCommit().Sha)
+ return self.checkMergeOrRebase(err)
+ })
+ },
+ })
+}
+
+func (self *LocalCommitsController) handleCommitRevert(commit *models.Commit) error {
+ if commit.IsMerge() {
+ return self.createRevertMergeCommitMenu(commit)
+ } else {
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.Actions.RevertCommit,
+ Prompt: utils.ResolvePlaceholderString(
+ self.c.Tr.ConfirmRevertCommit,
+ map[string]string{
+ "selectedCommit": commit.ShortSha(),
+ }),
+ HandleConfirm: func() error {
+ self.c.LogAction(self.c.Tr.Actions.RevertCommit)
+ if err := self.git.Commit.Revert(commit.Sha); err != nil {
+ return self.c.Error(err)
+ }
+ return self.afterRevertCommit()
+ },
+ })
+ }
+}
+
+func (self *LocalCommitsController) createRevertMergeCommitMenu(commit *models.Commit) error {
+ menuItems := make([]*popup.MenuItem, len(commit.Parents))
+ for i, parentSha := range commit.Parents {
+ i := i
+ message, err := self.git.Commit.GetCommitMessageFirstLine(parentSha)
+ if err != nil {
+ return self.c.Error(err)
+ }
+
+ menuItems[i] = &popup.MenuItem{
+ DisplayString: fmt.Sprintf("%s: %s", utils.SafeTruncate(parentSha, 8), message),
+ OnPress: func() error {
+ parentNumber := i + 1
+ self.c.LogAction(self.c.Tr.Actions.RevertCommit)
+ if err := self.git.Commit.RevertMerge(commit.Sha, parentNumber); err != nil {
+ return self.c.Error(err)
+ }
+ return self.afterRevertCommit()
+ },
+ }
+ }
+
+ return self.c.Menu(popup.CreateMenuOptions{Title: self.c.Tr.SelectParentCommitForMerge, Items: menuItems})
+}
+
+func (self *LocalCommitsController) afterRevertCommit() error {
+ self.context.HandleNextLine()
+ return self.c.Refresh(types.RefreshOptions{
+ Mode: types.BLOCK_UI, Scope: []types.RefreshableView{types.COMMITS, types.BRANCHES},
+ })
+}
+
+func (self *LocalCommitsController) enter(commit *models.Commit) error {
+ return self.switchToCommitFilesContext(SwitchToCommitFilesContextOpts{
+ RefName: commit.Sha,
+ CanRebase: true,
+ Context: self.context,
+ WindowName: "commits",
+ })
+}
+
+func (self *LocalCommitsController) handleCreateFixupCommit(commit *models.Commit) error {
+ prompt := utils.ResolvePlaceholderString(
+ self.c.Tr.SureCreateFixupCommit,
+ map[string]string{
+ "commit": commit.Sha,
+ },
+ )
+
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.CreateFixupCommit,
+ Prompt: prompt,
+ HandleConfirm: func() error {
+ self.c.LogAction(self.c.Tr.Actions.CreateFixupCommit)
+ if err := self.git.Commit.CreateFixupCommit(commit.Sha); err != nil {
+ return self.c.Error(err)
+ }
+
+ return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
+ },
+ })
+}
+
+func (self *LocalCommitsController) handleSquashAllAboveFixupCommits(commit *models.Commit) error {
+ prompt := utils.ResolvePlaceholderString(
+ self.c.Tr.SureSquashAboveCommits,
+ map[string]string{
+ "commit": commit.Sha,
+ },
+ )
+
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.SquashAboveCommits,
+ Prompt: prompt,
+ HandleConfirm: func() error {
+ return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func() error {
+ self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits)
+ err := self.git.Rebase.SquashAllAboveFixupCommits(commit.Sha)
+ return self.checkMergeOrRebase(err)
+ })
+ },
+ })
+}
+
+func (self *LocalCommitsController) handleTagCommit(commit *models.Commit) error {
+ return self.createTagMenu(commit.Sha)
+}
+
+func (self *LocalCommitsController) handleCheckoutCommit(commit *models.Commit) error {
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.LcCheckoutCommit,
+ Prompt: self.c.Tr.SureCheckoutThisCommit,
+ HandleConfirm: func() error {
+ self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
+ return self.refHelper.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
+ },
+ })
+}
+
+func (self *LocalCommitsController) handleCreateCommitResetMenu(commit *models.Commit) error {
+ return self.refHelper.CreateGitResetMenu(commit.Sha)
+}
+
+func (self *LocalCommitsController) handleOpenSearch(string) error {
+ // we usually lazyload these commits but now that we're searching we need to load them now
+ if self.getLimitCommits() {
+ self.setLimitCommits(false)
+ if err := self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
+ return err
+ }
+ }
+
+ return self.openSearch("commits")
+}
+
+func (self *LocalCommitsController) gotoBottom() error {
+ // we usually lazyload these commits but now that we're jumping to the bottom we need to load them now
+ if self.getLimitCommits() {
+ self.setLimitCommits(false)
+ if err := self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
+ return err
+ }
+ }
+
+ self.context.HandleGotoBottom()
+
+ return nil
+}
+
+func (self *LocalCommitsController) handleCopySelectedCommitMessageToClipboard(commit *models.Commit) error {
+ message, err := self.git.Commit.GetCommitMessage(commit.Sha)
+ if err != nil {
+ return self.c.Error(err)
+ }
+
+ self.c.LogAction(self.c.Tr.Actions.CopyCommitMessageToClipboard)
+ if err := self.os.CopyToClipboard(message); err != nil {
+ return self.c.Error(err)
+ }
+
+ self.c.Toast(self.c.Tr.CommitMessageCopiedToClipboard)
+
+ return nil
+}
+
+func (self *LocalCommitsController) handleOpenLogMenu() error {
+ return self.c.Menu(popup.CreateMenuOptions{
+ Title: self.c.Tr.LogMenuTitle,
+ Items: []*popup.MenuItem{
+ {
+ DisplayString: self.c.Tr.ToggleShowGitGraphAll,
+ OnPress: func() error {
+ self.setShowWholeGitGraph(!self.getShowWholeGitGraph())
+
+ if self.getShowWholeGitGraph() {
+ self.setLimitCommits(false)
+ }
+
+ return self.c.WithWaitingStatus(self.c.Tr.LcLoadingCommits, func() error {
+ return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}})
+ })
+ },
+ },
+ {
+ DisplayString: self.c.Tr.ShowGitGraph,
+ OpensMenu: true,
+ OnPress: func() error {
+ onPress := func(value string) func() error {
+ return func() error {
+ self.c.UserConfig.Git.Log.ShowGraph = value
+ return nil
+ }
+ }
+ return self.c.Menu(popup.CreateMenuOptions{
+ Title: self.c.Tr.LogMenuTitle,
+ Items: []*popup.MenuItem{
+ {
+ DisplayString: "always",
+ OnPress: onPress("always"),
+ },
+ {
+ DisplayString: "never",
+ OnPress: onPress("never"),
+ },
+ {
+ DisplayString: "when maximised",
+ OnPress: onPress("when-maximised"),
+ },
+ },
+ })
+ },
+ },
+ {
+ DisplayString: self.c.Tr.SortCommits,
+ OpensMenu: true,
+ OnPress: func() error {
+ onPress := func(value string) func() error {
+ return func() error {
+ self.c.UserConfig.Git.Log.Order = value
+ return self.c.WithWaitingStatus(self.c.Tr.LcLoadingCommits, func() error {
+ return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}})
+ })
+ }
+ }
+
+ return self.c.Menu(popup.CreateMenuOptions{
+ Title: self.c.Tr.LogMenuTitle,
+ Items: []*popup.MenuItem{
+ {
+ DisplayString: "topological (topo-order)",
+ OnPress: onPress("topo-order"),
+ },
+ {
+ DisplayString: "date-order",
+ OnPress: onPress("date-order"),
+ },
+ {
+ DisplayString: "author-date-order",
+ OnPress: onPress("author-date-order"),
+ },
+ },
+ })
+ },
+ },
+ },
+ })
+}
+
+func (self *LocalCommitsController) handleOpenCommitInBrowser(commit *models.Commit) error {
+ hostingServiceMgr := self.getHostingServiceMgr()
+
+ url, err := hostingServiceMgr.GetCommitURL(commit.Sha)
+ if err != nil {
+ return self.c.Error(err)
+ }
+
+ self.c.LogAction(self.c.Tr.Actions.OpenCommitInBrowser)
+ if err := self.os.OpenLink(url); err != nil {
+ return self.c.Error(err)
+ }
+
+ return nil
+}
+
+func (self *LocalCommitsController) checkSelected(callback func(*models.Commit) error) func() error {
+ return func() error {
+ commit := self.getSelectedLocalCommit()
+ if commit == nil {
+ return nil
+ }
+
+ return callback(commit)
+ }
+}
+
+func (self *LocalCommitsController) Context() types.Context {
+ return self.context
+}
diff --git a/pkg/gui/controllers/menu_controller.go b/pkg/gui/controllers/menu_controller.go
new file mode 100644
index 000000000..329cab1f6
--- /dev/null
+++ b/pkg/gui/controllers/menu_controller.go
@@ -0,0 +1,70 @@
+package controllers
+
+import (
+ "github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/config"
+ "github.com/jesseduffield/lazygit/pkg/gui/popup"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+)
+
+type MenuController struct {
+ c *ControllerCommon
+ context types.IListContext
+
+ getSelectedMenuItem func() *popup.MenuItem
+}
+
+var _ types.IController = &MenuController{}
+
+func NewMenuController(
+ c *ControllerCommon,
+ context types.IListContext,
+ getSelectedMenuItem func() *popup.MenuItem,
+) *MenuController {
+ return &MenuController{
+ c: c,
+ context: context,
+ getSelectedMenuItem: getSelectedMenuItem,
+ }
+}
+
+func (self *MenuController) Keybindings(getKey func(key string) interface{}, config config.KeybindingConfig, guards types.KeybindingGuards) []*types.Binding {
+ bindings := []*types.Binding{
+ {
+ Key: getKey(config.Universal.Select),
+ Handler: self.press,
+ },
+ {
+ Key: getKey(config.Universal.Confirm),
+ Handler: self.press,
+ },
+ {
+ Key: getKey(config.Universal.ConfirmAlt1),
+ Handler: self.press,
+ },
+ {
+ Key: gocui.MouseLeft,
+ Handler: func() error { return self.context.HandleClick(self.press) },
+ },
+ }
+
+ return append(bindings, self.context.Keybindings(getKey, config, guards)...)
+}
+
+func (self *MenuController) press() error {
+ selectedItem := self.getSelectedMenuItem()
+
+ if err := self.c.PopContext(); err != nil {
+ return err
+ }
+
+ if err := selectedItem.OnPress(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (self *MenuController) Context() types.Context {
+ return self.context
+}
diff --git a/pkg/gui/controllers/remotes_controller.go b/pkg/gui/controllers/remotes_controller.go
new file mode 100644
index 000000000..f37c8efef
--- /dev/null
+++ b/pkg/gui/controllers/remotes_controller.go
@@ -0,0 +1,204 @@
+package controllers
+
+import (
+ "sync"
+
+ "github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/commands"
+ "github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/config"
+ "github.com/jesseduffield/lazygit/pkg/gui/context"
+ "github.com/jesseduffield/lazygit/pkg/gui/popup"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+ "github.com/jesseduffield/lazygit/pkg/utils"
+)
+
+type RemotesController struct {
+ c *ControllerCommon
+ context types.IListContext
+ git *commands.GitCommand
+
+ getSelectedRemote func() *models.Remote
+ setRemoteBranches func([]*models.RemoteBranch)
+ allContexts context.ContextTree
+ fetchMutex *sync.Mutex
+}
+
+var _ types.IController = &RemotesController{}
+
+func NewRemotesController(
+ c *ControllerCommon,
+ context types.IListContext,
+ git *commands.GitCommand,
+ allContexts context.ContextTree,
+ getSelectedRemote func() *models.Remote,
+ setRemoteBranches func([]*models.RemoteBranch),
+ fetchMutex *sync.Mutex,
+) *RemotesController {
+ return &RemotesController{
+ c: c,
+ git: git,
+ allContexts: allContexts,
+ context: context,
+ getSelectedRemote: getSelectedRemote,
+ setRemoteBranches: setRemoteBranches,
+ fetchMutex: fetchMutex,
+ }
+}
+
+func (self *RemotesController) Keybindings(getKey func(key string) interface{}, config config.KeybindingConfig, guards types.KeybindingGuards) []*types.Binding {
+ bindings := []*types.Binding{
+ {
+ Key: getKey(config.Universal.GoInto),
+ Handler: self.checkSelected(self.enter),
+ },
+ {
+ Key: gocui.MouseLeft,
+ Handler: func() error { return self.context.HandleClick(self.checkSelected(self.enter)) },
+ },
+ {
+ Key: getKey(config.Branches.FetchRemote),
+ Handler: self.checkSelected(self.fetch),
+ Description: self.c.Tr.LcFetchRemote,
+ },
+ {
+ Key: getKey(config.Universal.New),
+ Handler: self.add,
+ Description: self.c.Tr.LcAddNewRemote,
+ },
+ {
+ Key: getKey(config.Universal.Remove),
+ Handler: self.checkSelected(self.remove),
+ Description: self.c.Tr.LcRemoveRemote,
+ },
+ {
+ Key: getKey(config.Universal.Edit),
+ Handler: self.checkSelected(self.edit),
+ Description: self.c.Tr.LcEditRemote,
+ },
+ }
+
+ return append(bindings, self.context.Keybindings(getKey, config, guards)...)
+}
+
+func (self *RemotesController) enter(remote *models.Remote) error {
+ // naive implementation: get the branches from the remote and render them to the list, change the context
+ self.setRemoteBranches(remote.Branches)
+
+ newSelectedLine := 0
+ if len(remote.Branches) == 0 {
+ newSelectedLine = -1
+ }
+ self.allContexts.RemoteBranches.GetPanelState().SetSelectedLineIdx(newSelectedLine)
+
+ return self.c.PushContext(self.allContexts.RemoteBranches)
+}
+
+func (self *RemotesController) add() error {
+ return self.c.Prompt(popup.PromptOpts{
+ Title: self.c.Tr.LcNewRemoteName,
+ HandleConfirm: func(remoteName string) error {
+ return self.c.Prompt(popup.PromptOpts{
+ Title: self.c.Tr.LcNewRemoteUrl,
+ HandleConfirm: func(remoteUrl string) error {
+ self.c.LogAction(self.c.Tr.Actions.AddRemote)
+ if err := self.git.Remote.AddRemote(remoteName, remoteUrl); err != nil {
+ return err
+ }
+ return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.REMOTES}})
+ },
+ })
+ },
+ })
+}
+
+func (self *RemotesController) remove(remote *models.Remote) error {
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.LcRemoveRemote,
+ Prompt: self.c.Tr.LcRemoveRemotePrompt + " '" + remote.Name + "'?",
+ HandleConfirm: func() error {
+ self.c.LogAction(self.c.Tr.Actions.RemoveRemote)
+ if err := self.git.Remote.RemoveRemote(remote.Name); err != nil {
+ return self.c.Error(err)
+ }
+
+ return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
+ },
+ })
+}
+
+func (self *RemotesController) edit(remote *models.Remote) error {
+ editNameMessage := utils.ResolvePlaceholderString(
+ self.c.Tr.LcEditRemoteName,
+ map[string]string{
+ "remoteName": remote.Name,
+ },
+ )
+
+ return self.c.Prompt(popup.PromptOpts{
+ Title: editNameMessage,
+ InitialContent: remote.Name,
+ HandleConfirm: func(updatedRemoteName string) error {
+ if updatedRemoteName != remote.Name {
+ self.c.LogAction(self.c.Tr.Actions.UpdateRemote)
+ if err := self.git.Remote.RenameRemote(remote.Name, updatedRemoteName); err != nil {
+ return self.c.Error(err)
+ }
+ }
+
+ editUrlMessage := utils.ResolvePlaceholderString(
+ self.c.Tr.LcEditRemoteUrl,
+ map[string]string{
+ "remoteName": updatedRemoteName,
+ },
+ )
+
+ urls := remote.Urls
+ url := ""
+ if len(urls) > 0 {
+ url = urls[0]
+ }
+
+ return self.c.Prompt(popup.PromptOpts{
+ Title: editUrlMessage,
+ InitialContent: url,
+ HandleConfirm: func(updatedRemoteUrl string) error {
+ self.c.LogAction(self.c.Tr.Actions.UpdateRemote)
+ if err := self.git.Remote.UpdateRemoteUrl(updatedRemoteName, updatedRemoteUrl); err != nil {
+ return self.c.Error(err)
+ }
+ return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
+ },
+ })
+ },
+ })
+}
+
+func (self *RemotesController) fetch(remote *models.Remote) error {
+ return self.c.WithWaitingStatus(self.c.Tr.FetchingRemoteStatus, func() error {
+ self.fetchMutex.Lock()
+ defer self.fetchMutex.Unlock()
+
+ err := self.git.Sync.FetchRemote(remote.Name)
+ if err != nil {
+ _ = self.c.Error(err)
+ }
+
+ return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
+ })
+}
+
+func (self *RemotesController) checkSelected(callback func(*models.Remote) error) func() error {
+ return func() error {
+ file := self.getSelectedRemote()
+ if file == nil {
+ return nil
+ }
+
+ return callback(file)
+ }
+}
+
+func (self *RemotesController) Context() types.Context {
+ return self.context
+}
diff --git a/pkg/gui/controllers/submodules_controller.go b/pkg/gui/controllers/submodules_controller.go
index e5eaf98a0..a380154ae 100644
--- a/pkg/gui/controllers/submodules_controller.go
+++ b/pkg/gui/controllers/submodules_controller.go
@@ -5,65 +5,57 @@ import (
"path/filepath"
"strings"
+ "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
- "github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
-// if Go let me do private struct embedding of structs with public fields (which it should)
-// I would just do that. But alas.
-type ControllerCommon struct {
- *common.Common
- IGuiCommon
+type SubmodulesController struct {
+ c *ControllerCommon
+ context types.IListContext
+ git *commands.GitCommand
+
+ enterSubmodule func(submodule *models.SubmoduleConfig) error
+ getSelectedSubmodule func() *models.SubmoduleConfig
}
-type SubmodulesController struct {
- // I've said publicly that I'm against single-letter variable names but in this
- // case I would actually prefer a _zero_ letter variable name in the form of
- // struct embedding, but Go does not allow hiding public fields in an embedded struct
- // to the client
- c *ControllerCommon
- enterSubmoduleFn func(submodule *models.SubmoduleConfig) error
- getSelectedSubmodule func() *models.SubmoduleConfig
- git *commands.GitCommand
- submodules []*models.SubmoduleConfig
-}
+var _ types.IController = &SubmodulesController{}
func NewSubmodulesController(
c *ControllerCommon,
- enterSubmoduleFn func(submodule *models.SubmoduleConfig) error,
+ context types.IListContext,
git *commands.GitCommand,
- submodules []*models.SubmoduleConfig,
+ enterSubmodule func(submodule *models.SubmoduleConfig) error,
getSelectedSubmodule func() *models.SubmoduleConfig,
) *SubmodulesController {
return &SubmodulesController{
c: c,
- enterSubmoduleFn: enterSubmoduleFn,
+ context: context,
git: git,
- submodules: submodules,
+ enterSubmodule: enterSubmodule,
getSelectedSubmodule: getSelectedSubmodule,
}
}
-func (self *SubmodulesController) Keybindings(getKey func(key string) interface{}, config config.KeybindingConfig) []*types.Binding {
- return []*types.Binding{
+func (self *SubmodulesController) Keybindings(getKey func(key string) interface{}, config config.KeybindingConfig, guards types.KeybindingGuards) []*types.Binding {
+ bindings := []*types.Binding{
{
Key: getKey(config.Universal.GoInto),
- Handler: self.forSubmodule(self.enter),
+ Handler: self.checkSelected(self.enter),
Description: self.c.Tr.LcEnterSubmodule,
},
{
Key: getKey(config.Universal.Remove),
- Handler: self.forSubmodule(self.remove),
+ Handler: self.checkSelected(self.remove),
Description: self.c.Tr.LcRemoveSubmodule,
},
{
Key: getKey(config.Submodules.Update),
- Handler: self.forSubmodule(self.update),
+ Handler: self.checkSelected(self.update),
Description: self.c.Tr.LcSubmoduleUpdate,
},
{
@@ -73,12 +65,12 @@ func (self *SubmodulesController) Keybindings(getKey func(key string) interface{
},
{
Key: getKey(config.Universal.Edit),
- Handler: self.forSubmodule(self.editURL),
+ Handler: self.checkSelected(self.editURL),
Description: self.c.Tr.LcEditSubmoduleUrl,
},
{
Key: getKey(config.Submodules.Init),
- Handler: self.forSubmodule(self.init),
+ Handler: self.checkSelected(self.init),
Description: self.c.Tr.LcInitSubmodule,
},
{
@@ -87,11 +79,17 @@ func (self *SubmodulesController) Keybindings(getKey func(key string) interface{
Description: self.c.Tr.LcViewBulkSubmoduleOptions,
OpensMenu: true,
},
+ {
+ Key: gocui.MouseLeft,
+ Handler: func() error { return self.context.HandleClick(self.checkSelected(self.enter)) },
+ },
}
+
+ return append(bindings, self.context.Keybindings(getKey, config, guards)...)
}
func (self *SubmodulesController) enter(submodule *models.SubmoduleConfig) error {
- return self.enterSubmoduleFn(submodule)
+ return self.enterSubmodule(submodule)
}
func (self *SubmodulesController) add() error {
@@ -231,7 +229,7 @@ func (self *SubmodulesController) remove(submodule *models.SubmoduleConfig) erro
})
}
-func (self *SubmodulesController) forSubmodule(callback func(*models.SubmoduleConfig) error) func() error {
+func (self *SubmodulesController) checkSelected(callback func(*models.SubmoduleConfig) error) func() error {
return func() error {
submodule := self.getSelectedSubmodule()
if submodule == nil {
@@ -241,3 +239,7 @@ func (self *SubmodulesController) forSubmodule(callback func(*models.SubmoduleCo
return callback(submodule)
}
}
+
+func (self *SubmodulesController) Context() types.Context {
+ return self.context
+}
diff --git a/pkg/gui/controllers/sync_controller.go b/pkg/gui/controllers/sync_controller.go
new file mode 100644
index 000000000..a2fda53a1
--- /dev/null
+++ b/pkg/gui/controllers/sync_controller.go
@@ -0,0 +1,253 @@
+package controllers
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/jesseduffield/lazygit/pkg/commands"
+ "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
+ "github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/config"
+ "github.com/jesseduffield/lazygit/pkg/gui/popup"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+)
+
+type SyncController struct {
+ // I've said publicly that I'm against single-letter variable names but in this
+ // case I would actually prefer a _zero_ letter variable name in the form of
+ // struct embedding, but Go does not allow hiding public fields in an embedded struct
+ // to the client
+ c *ControllerCommon
+ git *commands.GitCommand
+
+ getCheckedOutBranch func() *models.Branch
+ suggestionsHelper ISuggestionsHelper
+ getSuggestedRemote func() string
+ checkMergeOrRebase func(error) error
+}
+
+var _ types.IController = &SyncController{}
+
+func NewSyncController(
+ c *ControllerCommon,
+ git *commands.GitCommand,
+ getCheckedOutBranch func() *models.Branch,
+ suggestionsHelper ISuggestionsHelper,
+ getSuggestedRemote func() string,
+ checkMergeOrRebase func(error) error,
+) *SyncController {
+ return &SyncController{
+ c: c,
+ git: git,
+
+ getCheckedOutBranch: getCheckedOutBranch,
+ suggestionsHelper: suggestionsHelper,
+ getSuggestedRemote: getSuggestedRemote,
+ checkMergeOrRebase: checkMergeOrRebase,
+ }
+}
+
+func (self *SyncController) Keybindings(getKey func(key string) interface{}, config config.KeybindingConfig, guards types.KeybindingGuards) []*types.Binding {
+ bindings := []*types.Binding{
+ {
+ Key: getKey(config.Universal.PushFiles),
+ Handler: guards.NoPopupPanel(self.HandlePush),
+ Description: self.c.Tr.LcPush,
+ },
+ {
+ Key: getKey(config.Universal.PullFiles),
+ Handler: guards.NoPopupPanel(self.HandlePull),
+ Description: self.c.Tr.LcPull,
+ },
+ }
+
+ return bindings
+}
+
+func (self *SyncController) Context() types.Context {
+ return nil
+}
+
+func (self *SyncController) HandlePush() error {
+ return self.branchCheckedOut(self.push)()
+}
+
+func (self *SyncController) HandlePull() error {
+ return self.branchCheckedOut(self.pull)()
+}
+
+func (self *SyncController) branchCheckedOut(f func(*models.Branch) error) func() error {
+ return func() error {
+ currentBranch := self.getCheckedOutBranch()
+ if currentBranch == nil {
+ // need to wait for branches to refresh
+ return nil
+ }
+
+ return f(currentBranch)
+ }
+}
+
+func (self *SyncController) push(currentBranch *models.Branch) error {
+ // if we have pullables we'll ask if the user wants to force push
+ if currentBranch.IsTrackingRemote() {
+ opts := pushOpts{
+ force: false,
+ upstreamRemote: currentBranch.UpstreamRemote,
+ upstreamBranch: currentBranch.UpstreamBranch,
+ }
+ if currentBranch.HasCommitsToPull() {
+ opts.force = true
+ return self.requestToForcePush(opts)
+ } else {
+ return self.pushAux(opts)
+ }
+ } else {
+ if self.git.Config.GetPushToCurrent() {
+ return self.pushAux(pushOpts{setUpstream: true})
+ } else {
+ return self.promptForUpstream(currentBranch, func(upstream string) error {
+ var upstreamBranch, upstreamRemote string
+ split := strings.Split(upstream, " ")
+ if len(split) == 2 {
+ upstreamRemote = split[0]
+ upstreamBranch = split[1]
+ } else {
+ upstreamRemote = upstream
+ upstreamBranch = ""
+ }
+
+ return self.pushAux(pushOpts{
+ force: false,
+ upstreamRemote: upstreamRemote,
+ upstreamBranch: upstreamBranch,
+ setUpstream: true,
+ })
+ })
+ }
+ }
+}
+
+func (self *SyncController) pull(currentBranch *models.Branch) error {
+ action := self.c.Tr.Actions.Pull
+
+ // if we have no upstream branch we need to set that first
+ if !currentBranch.IsTrackingRemote() {
+ return self.promptForUpstream(currentBranch, func(upstream string) error {
+ var upstreamBranch, upstreamRemote string
+ split := strings.Split(upstream, " ")
+ if len(split) != 2 {
+ return self.c.ErrorMsg(self.c.Tr.InvalidUpstream)
+ }
+
+ upstreamRemote = split[0]
+ upstreamBranch = split[1]
+
+ if err := self.git.Branch.SetCurrentBranchUpstream(upstreamRemote, upstreamBranch); err != nil {
+ errorMessage := err.Error()
+ if strings.Contains(errorMessage, "does not exist") {
+ errorMessage = fmt.Sprintf("upstream branch %s not found.\nIf you expect it to exist, you should fetch (with 'f').\nOtherwise, you should push (with 'shift+P')", upstream)
+ }
+ return self.c.ErrorMsg(errorMessage)
+ }
+ return self.PullAux(PullFilesOptions{UpstreamRemote: upstreamRemote, UpstreamBranch: upstreamBranch, Action: action})
+ })
+ }
+
+ return self.PullAux(PullFilesOptions{UpstreamRemote: currentBranch.UpstreamRemote, UpstreamBranch: currentBranch.UpstreamBranch, Action: action})
+}
+
+func (self *SyncController) promptForUpstream(currentBranch *models.Branch, onConfirm func(string) error) error {
+ suggestedRemote := self.getSuggestedRemote()
+
+ return self.c.Prompt(popup.PromptOpts{
+ Title: self.c.Tr.EnterUpstream,
+ InitialContent: suggestedRemote + " " + currentBranch.Name,
+ FindSuggestionsFunc: self.suggestionsHelper.GetRemoteBranchesSuggestionsFunc(" "),
+ HandleConfirm: onConfirm,
+ })
+}
+
+type PullFilesOptions struct {
+ UpstreamRemote string
+ UpstreamBranch string
+ FastForwardOnly bool
+ Action string
+}
+
+func (self *SyncController) PullAux(opts PullFilesOptions) error {
+ return self.c.WithLoaderPanel(self.c.Tr.PullWait, func() error {
+ return self.pullWithLock(opts)
+ })
+}
+
+func (self *SyncController) pullWithLock(opts PullFilesOptions) error {
+ self.c.LogAction(opts.Action)
+
+ err := self.git.Sync.Pull(
+ git_commands.PullOptions{
+ RemoteName: opts.UpstreamRemote,
+ BranchName: opts.UpstreamBranch,
+ FastForwardOnly: opts.FastForwardOnly,
+ },
+ )
+
+ return self.checkMergeOrRebase(err)
+}
+
+type pushOpts struct {
+ force bool
+ upstreamRemote string
+ upstreamBranch string
+ setUpstream bool
+}
+
+func (self *SyncController) pushAux(opts pushOpts) error {
+ return self.c.WithLoaderPanel(self.c.Tr.PushWait, func() error {
+ self.c.LogAction(self.c.Tr.Actions.Push)
+ err := self.git.Sync.Push(git_commands.PushOpts{
+ Force: opts.force,
+ UpstreamRemote: opts.upstreamRemote,
+ UpstreamBranch: opts.upstreamBranch,
+ SetUpstream: opts.setUpstream,
+ })
+
+ if err != nil {
+ if !opts.force && strings.Contains(err.Error(), "Updates were rejected") {
+ forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing
+ if forcePushDisabled {
+ _ = self.c.ErrorMsg(self.c.Tr.UpdatesRejectedAndForcePushDisabled)
+ return nil
+ }
+ _ = self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.ForcePush,
+ Prompt: self.c.Tr.ForcePushPrompt,
+ HandleConfirm: func() error {
+ newOpts := opts
+ newOpts.force = true
+
+ return self.pushAux(newOpts)
+ },
+ })
+ return nil
+ }
+ _ = self.c.Error(err)
+ }
+ return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
+ })
+}
+
+func (self *SyncController) requestToForcePush(opts pushOpts) error {
+ forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing
+ if forcePushDisabled {
+ return self.c.ErrorMsg(self.c.Tr.ForcePushDisabled)
+ }
+
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.ForcePush,
+ Prompt: self.c.Tr.ForcePushPrompt,
+ HandleConfirm: func() error {
+ return self.pushAux(opts)
+ },
+ })
+}
diff --git a/pkg/gui/controllers/tags_controller.go b/pkg/gui/controllers/tags_controller.go
new file mode 100644
index 000000000..723a1074b
--- /dev/null
+++ b/pkg/gui/controllers/tags_controller.go
@@ -0,0 +1,229 @@
+package controllers
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/commands"
+ "github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/config"
+ "github.com/jesseduffield/lazygit/pkg/gui/context"
+ "github.com/jesseduffield/lazygit/pkg/gui/popup"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+ "github.com/jesseduffield/lazygit/pkg/utils"
+)
+
+type TagsController struct {
+ c *ControllerCommon
+ context types.IListContext
+ git *commands.GitCommand
+ allContexts context.ContextTree
+
+ refHelper IRefHelper
+ suggestionsHelper ISuggestionsHelper
+
+ getSelectedTag func() *models.Tag
+ switchToSubCommitsContext func(string) error
+}
+
+var _ types.IController = &TagsController{}
+
+func NewTagsController(
+ c *ControllerCommon,
+ context types.IListContext,
+ git *commands.GitCommand,
+ allContexts context.ContextTree,
+ refHelper IRefHelper,
+ suggestionsHelper ISuggestionsHelper,
+
+ getSelectedTag func() *models.Tag,
+ switchToSubCommitsContext func(string) error,
+) *TagsController {
+ return &TagsController{
+ c: c,
+ context: context,
+ git: git,
+ allContexts: allContexts,
+ refHelper: refHelper,
+ suggestionsHelper: suggestionsHelper,
+
+ getSelectedTag: getSelectedTag,
+ switchToSubCommitsContext: switchToSubCommitsContext,
+ }
+}
+
+func (self *TagsController) Keybindings(getKey func(key string) interface{}, config config.KeybindingConfig, guards types.KeybindingGuards) []*types.Binding {
+ bindings := []*types.Binding{
+ {
+ Key: getKey(config.Universal.Select),
+ Handler: self.withSelectedTag(self.checkout),
+ Description: self.c.Tr.LcCheckout,
+ },
+ {
+ Key: getKey(config.Universal.Remove),
+ Handler: self.withSelectedTag(self.delete),
+ Description: self.c.Tr.LcDeleteTag,
+ },
+ {
+ Key: getKey(config.Branches.PushTag),
+ Handler: self.withSelectedTag(self.push),
+ Description: self.c.Tr.LcPushTag,
+ },
+ {
+ Key: getKey(config.Universal.New),
+ Handler: self.create,
+ Description: self.c.Tr.LcCreateTag,
+ },
+ {
+ Key: getKey(config.Commits.ViewResetOptions),
+ Handler: self.withSelectedTag(self.createResetMenu),
+ Description: self.c.Tr.LcViewResetOptions,
+ OpensMenu: true,
+ },
+ {
+ Key: getKey(config.Universal.GoInto),
+ Handler: self.withSelectedTag(self.enter),
+ Description: self.c.Tr.LcViewCommits,
+ },
+ }
+
+ return append(bindings, self.context.Keybindings(getKey, config, guards)...)
+}
+
+func (self *TagsController) checkout(tag *models.Tag) error {
+ self.c.LogAction(self.c.Tr.Actions.CheckoutTag)
+ if err := self.refHelper.CheckoutRef(tag.Name, types.CheckoutRefOptions{}); err != nil {
+ return err
+ }
+ return self.c.PushContext(self.allContexts.Branches)
+}
+
+func (self *TagsController) enter(tag *models.Tag) error {
+ return self.switchToSubCommitsContext(tag.Name)
+}
+
+func (self *TagsController) delete(tag *models.Tag) error {
+ prompt := utils.ResolvePlaceholderString(
+ self.c.Tr.DeleteTagPrompt,
+ map[string]string{
+ "tagName": tag.Name,
+ },
+ )
+
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.DeleteTagTitle,
+ Prompt: prompt,
+ HandleConfirm: func() error {
+ self.c.LogAction(self.c.Tr.Actions.DeleteTag)
+ if err := self.git.Tag.Delete(tag.Name); err != nil {
+ return self.c.Error(err)
+ }
+ return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
+ },
+ })
+}
+
+func (self *TagsController) push(tag *models.Tag) error {
+ title := utils.ResolvePlaceholderString(
+ self.c.Tr.PushTagTitle,
+ map[string]string{
+ "tagName": tag.Name,
+ },
+ )
+
+ return self.c.Prompt(popup.PromptOpts{
+ Title: title,
+ InitialContent: "origin",
+ FindSuggestionsFunc: self.suggestionsHelper.GetRemoteSuggestionsFunc(),
+ HandleConfirm: func(response string) error {
+ return self.c.WithWaitingStatus(self.c.Tr.PushingTagStatus, func() error {
+ self.c.LogAction(self.c.Tr.Actions.PushTag)
+ err := self.git.Tag.Push(response, tag.Name)
+ if err != nil {
+ _ = self.c.Error(err)
+ }
+
+ return nil
+ })
+ },
+ })
+}
+
+func (self *TagsController) createResetMenu(tag *models.Tag) error {
+ return self.refHelper.CreateGitResetMenu(tag.Name)
+}
+
+func (self *TagsController) CreateTagMenu(commitSha string) error {
+ return self.c.Menu(popup.CreateMenuOptions{
+ Title: self.c.Tr.TagMenuTitle,
+ Items: []*popup.MenuItem{
+ {
+ DisplayString: self.c.Tr.LcLightweightTag,
+ OnPress: func() error {
+ return self.handleCreateLightweightTag(commitSha)
+ },
+ },
+ {
+ DisplayString: self.c.Tr.LcAnnotatedTag,
+ OnPress: func() error {
+ return self.handleCreateAnnotatedTag(commitSha)
+ },
+ },
+ },
+ })
+}
+
+func (self *TagsController) afterTagCreate() error {
+ self.context.GetPanelState().SetSelectedLineIdx(0)
+ return self.c.Refresh(types.RefreshOptions{
+ Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS},
+ })
+}
+
+func (self *TagsController) handleCreateAnnotatedTag(commitSha string) error {
+ return self.c.Prompt(popup.PromptOpts{
+ Title: self.c.Tr.TagNameTitle,
+ HandleConfirm: func(tagName string) error {
+ return self.c.Prompt(popup.PromptOpts{
+ Title: self.c.Tr.TagMessageTitle,
+ HandleConfirm: func(msg string) error {
+ self.c.LogAction(self.c.Tr.Actions.CreateAnnotatedTag)
+ if err := self.git.Tag.CreateAnnotated(tagName, commitSha, msg); err != nil {
+ return self.c.Error(err)
+ }
+ return self.afterTagCreate()
+ },
+ })
+ },
+ })
+}
+
+func (self *TagsController) handleCreateLightweightTag(commitSha string) error {
+ return self.c.Prompt(popup.PromptOpts{
+ Title: self.c.Tr.TagNameTitle,
+ HandleConfirm: func(tagName string) error {
+ self.c.LogAction(self.c.Tr.Actions.CreateLightweightTag)
+ if err := self.git.Tag.CreateLightweight(tagName, commitSha); err != nil {
+ return self.c.Error(err)
+ }
+ return self.afterTagCreate()
+ },
+ })
+}
+
+func (self *TagsController) create() error {
+ // leaving commit SHA blank so that we're just creating the tag for the current commit
+ return self.CreateTagMenu("")
+}
+
+func (self *TagsController) withSelectedTag(f func(tag *models.Tag) error) func() error {
+ return func() error {
+ tag := self.getSelectedTag()
+ if tag == nil {
+ return nil
+ }
+
+ return f(tag)
+ }
+}
+
+func (self *TagsController) Context() types.Context {
+ return self.context
+}
diff --git a/pkg/gui/controllers/types.go b/pkg/gui/controllers/types.go
index 75abc1704..21f774944 100644
--- a/pkg/gui/controllers/types.go
+++ b/pkg/gui/controllers/types.go
@@ -1,6 +1,9 @@
package controllers
import (
+ "github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
+ "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -8,6 +11,54 @@ import (
type IGuiCommon interface {
popup.IPopupHandler
- LogAction(string)
+ LogAction(action string)
+ LogCommand(cmdStr string, isCommandLine bool)
+ // we call this when we want to refetch some models and render the result. Internally calls PostRefreshUpdate
Refresh(types.RefreshOptions) error
+ // we call this when we've changed something in the view model but not the actual model,
+ // e.g. expanding or collapsing a folder in a file view. Calling 'Refresh' in this
+ // case would be overkill, although refresh will internally call 'PostRefreshUpdate'
+ PostRefreshUpdate(types.Context) error
+ RunSubprocessAndRefresh(oscommands.ICmdObj) error
+ PushContext(context types.Context, opts ...types.OnFocusOpts) error
+ PopContext() error
+
+ GetAppState() *config.AppState
+ SaveAppState() error
+}
+
+type IRefHelper interface {
+ CheckoutRef(ref string, options types.CheckoutRefOptions) error
+ CreateGitResetMenu(ref string) error
+ ResetToRef(ref string, strength string, envVars []string) error
+}
+
+type ISuggestionsHelper interface {
+ GetRemoteSuggestionsFunc() func(string) []*types.Suggestion
+ GetBranchNameSuggestionsFunc() func(string) []*types.Suggestion
+ GetFilePathSuggestionsFunc() func(string) []*types.Suggestion
+ GetRemoteBranchesSuggestionsFunc(separator string) func(string) []*types.Suggestion
+ GetRefsSuggestionsFunc() func(string) []*types.Suggestion
+ GetCustomCommandsHistorySuggestionsFunc() func(string) []*types.Suggestion
+}
+
+type IFileHelper interface {
+ EditFile(filename string) error
+ EditFileAtLine(filename string, lineNumber int) error
+ OpenFile(filename string) error
+}
+
+type IWorkingTreeHelper interface {
+ AnyStagedFiles() bool
+ AnyTrackedFiles() bool
+ IsWorkingTreeDirty() bool
+ FileForSubmodule(submodule *models.SubmoduleConfig) *models.File
+}
+
+// all fields mandatory (except `CanRebase` because it's boolean)
+type SwitchToCommitFilesContextOpts struct {
+ RefName string
+ CanRebase bool
+ Context types.Context
+ WindowName string
}
diff --git a/pkg/gui/undoing.go b/pkg/gui/controllers/undo_controller.go
similarity index 56%
rename from pkg/gui/undoing.go
rename to pkg/gui/controllers/undo_controller.go
index e61950700..984b8e1a6 100644
--- a/pkg/gui/undoing.go
+++ b/pkg/gui/controllers/undo_controller.go
@@ -1,7 +1,10 @@
-package gui
+package controllers
import (
+ "github.com/jesseduffield/lazygit/pkg/commands"
+ "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
+ "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -17,6 +20,36 @@ import (
// the reflog will read UUCBA, and when I read the first two undos, I know to skip the following
// two user actions, meaning we end up undoing reflog entry C. Redoing works in a similar way.
+type UndoController struct {
+ c *ControllerCommon
+ git *commands.GitCommand
+
+ refHelper IRefHelper
+ workingTreeHelper IWorkingTreeHelper
+
+ getFilteredReflogCommits func() []*models.Commit
+}
+
+var _ types.IController = &UndoController{}
+
+func NewUndoController(
+ c *ControllerCommon,
+ git *commands.GitCommand,
+ refHelper IRefHelper,
+ workingTreeHelper IWorkingTreeHelper,
+
+ getFilteredReflogCommits func() []*models.Commit,
+) *UndoController {
+ return &UndoController{
+ c: c,
+ git: git,
+ refHelper: refHelper,
+ workingTreeHelper: workingTreeHelper,
+
+ getFilteredReflogCommits: getFilteredReflogCommits,
+ }
+}
+
type ReflogActionKind int
const (
@@ -32,15 +65,113 @@ type reflogAction struct {
to string
}
+func (self *UndoController) Keybindings(
+ getKey func(key string) interface{},
+ config config.KeybindingConfig,
+ guards types.KeybindingGuards,
+) []*types.Binding {
+ bindings := []*types.Binding{
+ {
+ Key: getKey(config.Universal.Undo),
+ Handler: self.reflogUndo,
+ Description: self.c.Tr.LcUndoReflog,
+ },
+ {
+ Key: getKey(config.Universal.Redo),
+ Handler: self.reflogRedo,
+ Description: self.c.Tr.LcRedoReflog,
+ },
+ }
+
+ return bindings
+}
+
+func (self *UndoController) Context() types.Context {
+ return nil
+}
+
+func (self *UndoController) reflogUndo() error {
+ undoEnvVars := []string{"GIT_REFLOG_ACTION=[lazygit undo]"}
+ undoingStatus := self.c.Tr.UndoingStatus
+
+ if self.git.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
+ return self.c.ErrorMsg(self.c.Tr.LcCantUndoWhileRebasing)
+ }
+
+ return self.parseReflogForActions(func(counter int, action reflogAction) (bool, error) {
+ if counter != 0 {
+ return false, nil
+ }
+
+ switch action.kind {
+ case COMMIT, REBASE:
+ self.c.LogAction(self.c.Tr.Actions.Undo)
+ return true, self.hardResetWithAutoStash(action.from, hardResetOptions{
+ EnvVars: undoEnvVars,
+ WaitingStatus: undoingStatus,
+ })
+ case CHECKOUT:
+ self.c.LogAction(self.c.Tr.Actions.Undo)
+ return true, self.refHelper.CheckoutRef(action.from, types.CheckoutRefOptions{
+ EnvVars: undoEnvVars,
+ WaitingStatus: undoingStatus,
+ })
+ case CURRENT_REBASE:
+ // do nothing
+ }
+
+ self.c.Log.Error("didn't match on the user action when trying to undo")
+ return true, nil
+ })
+}
+
+func (self *UndoController) reflogRedo() error {
+ redoEnvVars := []string{"GIT_REFLOG_ACTION=[lazygit redo]"}
+ redoingStatus := self.c.Tr.RedoingStatus
+
+ if self.git.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
+ return self.c.ErrorMsg(self.c.Tr.LcCantRedoWhileRebasing)
+ }
+
+ return self.parseReflogForActions(func(counter int, action reflogAction) (bool, error) {
+ // if we're redoing and the counter is zero, we just return
+ if counter == 0 {
+ return true, nil
+ } else if counter > 1 {
+ return false, nil
+ }
+
+ switch action.kind {
+ case COMMIT, REBASE:
+ self.c.LogAction(self.c.Tr.Actions.Redo)
+ return true, self.hardResetWithAutoStash(action.to, hardResetOptions{
+ EnvVars: redoEnvVars,
+ WaitingStatus: redoingStatus,
+ })
+ case CHECKOUT:
+ self.c.LogAction(self.c.Tr.Actions.Redo)
+ return true, self.refHelper.CheckoutRef(action.to, types.CheckoutRefOptions{
+ EnvVars: redoEnvVars,
+ WaitingStatus: redoingStatus,
+ })
+ case CURRENT_REBASE:
+ // do nothing
+ }
+
+ self.c.Log.Error("didn't match on the user action when trying to redo")
+ return true, nil
+ })
+}
+
// Here we're going through the reflog and maintaining a counter that represents how many
// undos/redos/user actions we've seen. when we hit a user action we call the callback specifying
// what the counter is up to and the nature of the action.
// If we find ourselves mid-rebase, we just return because undo/redo mid rebase
// requires knowledge of previous TODO file states, which you can't just get from the reflog.
// Though we might support this later, hence the use of the CURRENT_REBASE action kind.
-func (gui *Gui) parseReflogForActions(onUserAction func(counter int, action reflogAction) (bool, error)) error {
+func (self *UndoController) parseReflogForActions(onUserAction func(counter int, action reflogAction) (bool, error)) error {
counter := 0
- reflogCommits := gui.State.FilteredReflogCommits
+ reflogCommits := self.getFilteredReflogCommits()
rebaseFinishCommitSha := ""
var action *reflogAction
for reflogCommitIdx, reflogCommit := range reflogCommits {
@@ -86,115 +217,42 @@ func (gui *Gui) parseReflogForActions(onUserAction func(counter int, action refl
return nil
}
-func (gui *Gui) reflogUndo() error {
- undoEnvVars := []string{"GIT_REFLOG_ACTION=[lazygit undo]"}
- undoingStatus := gui.Tr.UndoingStatus
-
- if gui.Git.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
- return gui.PopupHandler.ErrorMsg(gui.Tr.LcCantUndoWhileRebasing)
- }
-
- return gui.parseReflogForActions(func(counter int, action reflogAction) (bool, error) {
- if counter != 0 {
- return false, nil
- }
-
- switch action.kind {
- case COMMIT, REBASE:
- gui.logAction(gui.Tr.Actions.Undo)
- return true, gui.handleHardResetWithAutoStash(action.from, handleHardResetWithAutoStashOptions{
- EnvVars: undoEnvVars,
- WaitingStatus: undoingStatus,
- })
- case CHECKOUT:
- gui.logAction(gui.Tr.Actions.Undo)
- return true, gui.handleCheckoutRef(action.from, handleCheckoutRefOptions{
- EnvVars: undoEnvVars,
- WaitingStatus: undoingStatus,
- })
- case CURRENT_REBASE:
- // do nothing
- }
-
- gui.Log.Error("didn't match on the user action when trying to undo")
- return true, nil
- })
-}
-
-func (gui *Gui) reflogRedo() error {
- redoEnvVars := []string{"GIT_REFLOG_ACTION=[lazygit redo]"}
- redoingStatus := gui.Tr.RedoingStatus
-
- if gui.Git.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
- return gui.PopupHandler.ErrorMsg(gui.Tr.LcCantRedoWhileRebasing)
- }
-
- return gui.parseReflogForActions(func(counter int, action reflogAction) (bool, error) {
- // if we're redoing and the counter is zero, we just return
- if counter == 0 {
- return true, nil
- } else if counter > 1 {
- return false, nil
- }
-
- switch action.kind {
- case COMMIT, REBASE:
- gui.logAction(gui.Tr.Actions.Redo)
- return true, gui.handleHardResetWithAutoStash(action.to, handleHardResetWithAutoStashOptions{
- EnvVars: redoEnvVars,
- WaitingStatus: redoingStatus,
- })
- case CHECKOUT:
- gui.logAction(gui.Tr.Actions.Redo)
- return true, gui.handleCheckoutRef(action.to, handleCheckoutRefOptions{
- EnvVars: redoEnvVars,
- WaitingStatus: redoingStatus,
- })
- case CURRENT_REBASE:
- // do nothing
- }
-
- gui.Log.Error("didn't match on the user action when trying to redo")
- return true, nil
- })
-}
-
-type handleHardResetWithAutoStashOptions struct {
+type hardResetOptions struct {
WaitingStatus string
EnvVars []string
}
-// only to be used in the undo flow for now
-func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHardResetWithAutoStashOptions) error {
+// only to be used in the undo flow for now (does an autostash)
+func (self *UndoController) hardResetWithAutoStash(commitSha string, options hardResetOptions) error {
reset := func() error {
- if err := gui.resetToRef(commitSha, "hard", options.EnvVars); err != nil {
- return gui.PopupHandler.Error(err)
+ if err := self.refHelper.ResetToRef(commitSha, "hard", options.EnvVars); err != nil {
+ return self.c.Error(err)
}
return nil
}
// if we have any modified tracked files we need to ask the user if they want us to stash for them
- dirtyWorkingTree := len(gui.trackedFiles()) > 0 || len(gui.stagedFiles()) > 0
+ dirtyWorkingTree := self.workingTreeHelper.IsWorkingTreeDirty()
if dirtyWorkingTree {
// offer to autostash changes
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.AutoStashTitle,
- Prompt: gui.Tr.AutoStashPrompt,
+ return self.c.Ask(popup.AskOpts{
+ Title: self.c.Tr.AutoStashTitle,
+ Prompt: self.c.Tr.AutoStashPrompt,
HandleConfirm: func() error {
- return gui.PopupHandler.WithWaitingStatus(options.WaitingStatus, func() error {
- if err := gui.Git.Stash.Save(gui.Tr.StashPrefix + commitSha); err != nil {
- return gui.PopupHandler.Error(err)
+ return self.c.WithWaitingStatus(options.WaitingStatus, func() error {
+ if err := self.git.Stash.Save(self.c.Tr.StashPrefix + commitSha); err != nil {
+ return self.c.Error(err)
}
if err := reset(); err != nil {
return err
}
- err := gui.Git.Stash.Pop(0)
- if err := gui.refreshSidePanels(types.RefreshOptions{}); err != nil {
+ err := self.git.Stash.Pop(0)
+ if err := self.c.Refresh(types.RefreshOptions{}); err != nil {
return err
}
if err != nil {
- return gui.PopupHandler.Error(err)
+ return self.c.Error(err)
}
return nil
})
@@ -202,7 +260,7 @@ func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHar
})
}
- return gui.PopupHandler.WithWaitingStatus(options.WaitingStatus, func() error {
+ return self.c.WithWaitingStatus(options.WaitingStatus, func() error {
return reset()
})
}
diff --git a/pkg/gui/credentials_panel.go b/pkg/gui/credentials_panel.go
index b7981338d..796aa70c6 100644
--- a/pkg/gui/credentials_panel.go
+++ b/pkg/gui/credentials_panel.go
@@ -17,21 +17,20 @@ func (gui *Gui) promptUserForCredential(passOrUname oscommands.CredentialType) s
credentialsView := gui.Views.Credentials
switch passOrUname {
case oscommands.Username:
- credentialsView.Title = gui.Tr.CredentialsUsername
+ credentialsView.Title = gui.c.Tr.CredentialsUsername
credentialsView.Mask = 0
case oscommands.Password:
- credentialsView.Title = gui.Tr.CredentialsPassword
+ credentialsView.Title = gui.c.Tr.CredentialsPassword
credentialsView.Mask = '*'
case oscommands.Passphrase:
- credentialsView.Title = gui.Tr.CredentialsPassphrase
+ credentialsView.Title = gui.c.Tr.CredentialsPassphrase
credentialsView.Mask = '*'
}
- if err := gui.pushContext(gui.State.Contexts.Credentials); err != nil {
+ if err := gui.c.PushContext(gui.State.Contexts.Credentials); err != nil {
return err
}
- gui.RenderCommitLength()
return nil
})
@@ -49,7 +48,7 @@ func (gui *Gui) handleSubmitCredential() error {
return err
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
}
func (gui *Gui) handleCloseCredentialsView() error {
@@ -59,10 +58,10 @@ func (gui *Gui) handleCloseCredentialsView() error {
}
func (gui *Gui) handleAskFocused() error {
- keybindingConfig := gui.UserConfig.Keybinding
+ keybindingConfig := gui.c.UserConfig.Keybinding
message := utils.ResolvePlaceholderString(
- gui.Tr.CloseConfirm,
+ gui.c.Tr.CloseConfirm,
map[string]string{
"keyBindClose": gui.getKeyDisplay(keybindingConfig.Universal.Return),
"keyBindConfirm": gui.getKeyDisplay(keybindingConfig.Universal.Confirm),
diff --git a/pkg/gui/custom_commands.go b/pkg/gui/custom_commands.go
index 44548ef72..3470797a3 100644
--- a/pkg/gui/custom_commands.go
+++ b/pkg/gui/custom_commands.go
@@ -54,7 +54,7 @@ func (gui *Gui) resolveTemplate(templateStr string, promptResponses []string) (s
SelectedCommitFile: gui.getSelectedCommitFile(),
SelectedCommitFilePath: gui.getSelectedCommitFilePath(),
SelectedSubCommit: gui.getSelectedSubCommit(),
- CheckedOutBranch: gui.currentBranch(),
+ CheckedOutBranch: gui.getCheckedOutBranch(),
PromptResponses: promptResponses,
}
@@ -64,15 +64,15 @@ func (gui *Gui) resolveTemplate(templateStr string, promptResponses []string) (s
func (gui *Gui) inputPrompt(prompt config.CustomCommandPrompt, promptResponses []string, responseIdx int, wrappedF func() error) error {
title, err := gui.resolveTemplate(prompt.Title, promptResponses)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
initialValue, err := gui.resolveTemplate(prompt.InitialValue, promptResponses)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
- return gui.PopupHandler.Prompt(popup.PromptOpts{
+ return gui.c.Prompt(popup.PromptOpts{
Title: title,
InitialContent: initialValue,
HandleConfirm: func(str string) error {
@@ -95,17 +95,17 @@ func (gui *Gui) menuPrompt(prompt config.CustomCommandPrompt, promptResponses []
}
name, err := gui.resolveTemplate(nameTemplate, promptResponses)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
description, err := gui.resolveTemplate(option.Description, promptResponses)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
value, err := gui.resolveTemplate(option.Value, promptResponses)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
menuItems[i] = &popup.MenuItem{
@@ -119,30 +119,30 @@ func (gui *Gui) menuPrompt(prompt config.CustomCommandPrompt, promptResponses []
title, err := gui.resolveTemplate(prompt.Title, promptResponses)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: title, Items: menuItems})
+ return gui.c.Menu(popup.CreateMenuOptions{Title: title, Items: menuItems})
}
func (gui *Gui) GenerateMenuCandidates(commandOutput, filter, valueFormat, labelFormat string) ([]commandMenuEntry, error) {
reg, err := regexp.Compile(filter)
if err != nil {
- return nil, gui.PopupHandler.Error(errors.New("unable to parse filter regex, error: " + err.Error()))
+ return nil, gui.c.Error(errors.New("unable to parse filter regex, error: " + err.Error()))
}
buff := bytes.NewBuffer(nil)
valueTemp, err := template.New("format").Parse(valueFormat)
if err != nil {
- return nil, gui.PopupHandler.Error(errors.New("unable to parse value format, error: " + err.Error()))
+ return nil, gui.c.Error(errors.New("unable to parse value format, error: " + err.Error()))
}
colorFuncMap := style.TemplateFuncMapAddColors(template.FuncMap{})
descTemp, err := template.New("format").Funcs(colorFuncMap).Parse(labelFormat)
if err != nil {
- return nil, gui.PopupHandler.Error(errors.New("unable to parse label format, error: " + err.Error()))
+ return nil, gui.c.Error(errors.New("unable to parse label format, error: " + err.Error()))
}
candidates := []commandMenuEntry{}
@@ -167,7 +167,7 @@ func (gui *Gui) GenerateMenuCandidates(commandOutput, filter, valueFormat, label
err = valueTemp.Execute(buff, tmplData)
if err != nil {
- return candidates, gui.PopupHandler.Error(err)
+ return candidates, gui.c.Error(err)
}
entry := commandMenuEntry{
value: strings.TrimSpace(buff.String()),
@@ -177,7 +177,7 @@ func (gui *Gui) GenerateMenuCandidates(commandOutput, filter, valueFormat, label
buff.Reset()
err = descTemp.Execute(buff, tmplData)
if err != nil {
- return candidates, gui.PopupHandler.Error(err)
+ return candidates, gui.c.Error(err)
}
entry.label = strings.TrimSpace(buff.String())
} else {
@@ -195,25 +195,25 @@ func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptR
// Collect cmd to run from config
cmdStr, err := gui.resolveTemplate(prompt.Command, promptResponses)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
// Collect Filter regexp
filter, err := gui.resolveTemplate(prompt.Filter, promptResponses)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
// Run and save output
- message, err := gui.Git.Custom.RunWithOutput(cmdStr)
+ message, err := gui.git.Custom.RunWithOutput(cmdStr)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
// Need to make a menu out of what the cmd has displayed
candidates, err := gui.GenerateMenuCandidates(message, filter, prompt.ValueFormat, prompt.LabelFormat)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
menuItems := make([]*popup.MenuItem, len(candidates))
@@ -230,10 +230,10 @@ func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptR
title, err := gui.resolveTemplate(prompt.Title, promptResponses)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: title, Items: menuItems})
+ return gui.c.Menu(popup.CreateMenuOptions{Title: title, Items: menuItems})
}
func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand) func() error {
@@ -243,7 +243,7 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand
f := func() error {
cmdStr, err := gui.resolveTemplate(customCommand.Command, promptResponses)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
if customCommand.Subprocess {
@@ -252,19 +252,19 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand
loadingText := customCommand.LoadingText
if loadingText == "" {
- loadingText = gui.Tr.LcRunningCustomCommandStatus
+ loadingText = gui.c.Tr.LcRunningCustomCommandStatus
}
- return gui.PopupHandler.WithWaitingStatus(loadingText, func() error {
- gui.logAction(gui.Tr.Actions.CustomCommand)
+ return gui.c.WithWaitingStatus(loadingText, func() error {
+ gui.c.LogAction(gui.c.Tr.Actions.CustomCommand)
cmdObj := gui.OSCommand.Cmd.NewShell(cmdStr)
if customCommand.Stream {
cmdObj.StreamOutput()
}
err := cmdObj.Run()
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{})
+ return gui.c.Refresh(types.RefreshOptions{})
})
}
@@ -293,7 +293,7 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand
return gui.menuPromptFromCommand(prompt, promptResponses, idx, wrappedF)
}
default:
- return gui.PopupHandler.ErrorMsg("custom command prompt must have a type of 'input', 'menu' or 'menuFromCommand'")
+ return gui.c.ErrorMsg("custom command prompt must have a type of 'input', 'menu' or 'menuFromCommand'")
}
}
@@ -304,7 +304,7 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand
func (gui *Gui) GetCustomCommandKeybindings() []*types.Binding {
bindings := []*types.Binding{}
- customCommands := gui.UserConfig.CustomCommands
+ customCommands := gui.c.UserConfig.CustomCommands
for _, customCommand := range customCommands {
var viewName string
@@ -315,11 +315,11 @@ func (gui *Gui) GetCustomCommandKeybindings() []*types.Binding {
case "":
log.Fatalf("Error parsing custom command keybindings: context not provided (use context: 'global' for the global context). Key: %s, Command: %s", customCommand.Key, customCommand.Command)
default:
- context, ok := gui.contextForContextKey(ContextKey(customCommand.Context))
+ context, ok := gui.contextForContextKey(types.ContextKey(customCommand.Context))
// stupid golang making me build an array of strings for this.
- allContextKeyStrings := make([]string, len(allContextKeys))
- for i := range allContextKeys {
- allContextKeyStrings[i] = string(allContextKeys[i])
+ allContextKeyStrings := make([]string, len(AllContextKeys))
+ for i := range AllContextKeys {
+ allContextKeyStrings[i] = string(AllContextKeys[i])
}
if !ok {
log.Fatalf("Error when setting custom command keybindings: unknown context: %s. Key: %s, Command: %s.\nPermitted contexts: %s", customCommand.Context, customCommand.Key, customCommand.Command, strings.Join(allContextKeyStrings, ", "))
diff --git a/pkg/gui/diff_context_size.go b/pkg/gui/diff_context_size.go
index e5ea340a6..5caefd032 100644
--- a/pkg/gui/diff_context_size.go
+++ b/pkg/gui/diff_context_size.go
@@ -2,9 +2,11 @@ package gui
import (
"errors"
+
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
)
-var CONTEXT_KEYS_SHOWING_DIFFS = []ContextKey{
+var CONTEXT_KEYS_SHOWING_DIFFS = []types.ContextKey{
FILES_CONTEXT_KEY,
COMMIT_FILES_CONTEXT_KEY,
STASH_CONTEXT_KEY,
@@ -28,10 +30,10 @@ func isShowingDiff(gui *Gui) bool {
func (gui *Gui) IncreaseContextInDiffView() error {
if isShowingDiff(gui) {
if err := gui.CheckCanChangeContext(); err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
- gui.UserConfig.Git.DiffContextSize = gui.UserConfig.Git.DiffContextSize + 1
+ gui.c.UserConfig.Git.DiffContextSize = gui.c.UserConfig.Git.DiffContextSize + 1
return gui.handleDiffContextSizeChange()
}
@@ -39,14 +41,14 @@ func (gui *Gui) IncreaseContextInDiffView() error {
}
func (gui *Gui) DecreaseContextInDiffView() error {
- old_size := gui.UserConfig.Git.DiffContextSize
+ old_size := gui.c.UserConfig.Git.DiffContextSize
if isShowingDiff(gui) && old_size > 1 {
if err := gui.CheckCanChangeContext(); err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
- gui.UserConfig.Git.DiffContextSize = old_size - 1
+ gui.c.UserConfig.Git.DiffContextSize = old_size - 1
return gui.handleDiffContextSizeChange()
}
@@ -67,8 +69,8 @@ func (gui *Gui) handleDiffContextSizeChange() error {
}
func (gui *Gui) CheckCanChangeContext() error {
- if gui.Git.Patch.PatchManager.Active() {
- return errors.New(gui.Tr.CantChangeContextSizeError)
+ if gui.git.Patch.PatchManager.Active() {
+ return errors.New(gui.c.Tr.CantChangeContextSizeError)
}
return nil
diff --git a/pkg/gui/diff_context_size_test.go b/pkg/gui/diff_context_size_test.go
index 4f53cd3ff..4515118bf 100644
--- a/pkg/gui/diff_context_size_test.go
+++ b/pkg/gui/diff_context_size_test.go
@@ -29,7 +29,7 @@ func setupGuiForTest(gui *Gui) {
gui.Views.Main, _ = gui.prepareView("main")
gui.Views.Secondary, _ = gui.prepareView("secondary")
gui.Views.Options, _ = gui.prepareView("options")
- gui.Git.Patch.PatchManager = &patch.PatchManager{}
+ gui.git.Patch.PatchManager = &patch.PatchManager{}
_, _ = gui.refreshLineByLinePanel(diffForTest, "", false, 11)
}
@@ -48,12 +48,12 @@ func TestIncreasesContextInDiffViewByOneInContextWithDiff(t *testing.T) {
gui := NewDummyGui()
context := c(gui)
setupGuiForTest(gui)
- gui.UserConfig.Git.DiffContextSize = 1
- _ = gui.pushContext(context)
+ gui.c.UserConfig.Git.DiffContextSize = 1
+ _ = gui.c.PushContext(context)
_ = gui.IncreaseContextInDiffView()
- assert.Equal(t, 2, gui.UserConfig.Git.DiffContextSize, string(context.GetKey()))
+ assert.Equal(t, 2, gui.c.UserConfig.Git.DiffContextSize, string(context.GetKey()))
}
}
@@ -76,12 +76,12 @@ func TestDoesntIncreaseContextInDiffViewInContextWithoutDiff(t *testing.T) {
gui := NewDummyGui()
context := c(gui)
setupGuiForTest(gui)
- gui.UserConfig.Git.DiffContextSize = 1
- _ = gui.pushContext(context)
+ gui.c.UserConfig.Git.DiffContextSize = 1
+ _ = gui.c.PushContext(context)
_ = gui.IncreaseContextInDiffView()
- assert.Equal(t, 1, gui.UserConfig.Git.DiffContextSize, string(context.GetKey()))
+ assert.Equal(t, 1, gui.c.UserConfig.Git.DiffContextSize, string(context.GetKey()))
}
}
@@ -100,12 +100,12 @@ func TestDecreasesContextInDiffViewByOneInContextWithDiff(t *testing.T) {
gui := NewDummyGui()
context := c(gui)
setupGuiForTest(gui)
- gui.UserConfig.Git.DiffContextSize = 2
- _ = gui.pushContext(context)
+ gui.c.UserConfig.Git.DiffContextSize = 2
+ _ = gui.c.PushContext(context)
_ = gui.DecreaseContextInDiffView()
- assert.Equal(t, 1, gui.UserConfig.Git.DiffContextSize, string(context.GetKey()))
+ assert.Equal(t, 1, gui.c.UserConfig.Git.DiffContextSize, string(context.GetKey()))
}
}
@@ -128,26 +128,26 @@ func TestDoesntDecreaseContextInDiffViewInContextWithoutDiff(t *testing.T) {
gui := NewDummyGui()
context := c(gui)
setupGuiForTest(gui)
- gui.UserConfig.Git.DiffContextSize = 2
- _ = gui.pushContext(context)
+ gui.c.UserConfig.Git.DiffContextSize = 2
+ _ = gui.c.PushContext(context)
_ = gui.DecreaseContextInDiffView()
- assert.Equal(t, 2, gui.UserConfig.Git.DiffContextSize, string(context.GetKey()))
+ assert.Equal(t, 2, gui.c.UserConfig.Git.DiffContextSize, string(context.GetKey()))
}
}
func TestDoesntIncreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *testing.T) {
gui := NewDummyGui()
setupGuiForTest(gui)
- gui.UserConfig.Git.DiffContextSize = 2
- _ = gui.pushContext(gui.State.Contexts.CommitFiles)
- gui.Git.Patch.PatchManager.Start("from", "to", false, false)
+ gui.c.UserConfig.Git.DiffContextSize = 2
+ _ = gui.c.PushContext(gui.State.Contexts.CommitFiles)
+ gui.git.Patch.PatchManager.Start("from", "to", false, false)
errorCount := 0
gui.PopupHandler = &popup.TestPopupHandler{
OnErrorMsg: func(message string) error {
- assert.Equal(t, gui.Tr.CantChangeContextSizeError, message)
+ assert.Equal(t, gui.c.Tr.CantChangeContextSizeError, message)
errorCount += 1
return nil
},
@@ -156,20 +156,20 @@ func TestDoesntIncreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *test
_ = gui.IncreaseContextInDiffView()
assert.Equal(t, 1, errorCount)
- assert.Equal(t, 2, gui.UserConfig.Git.DiffContextSize)
+ assert.Equal(t, 2, gui.c.UserConfig.Git.DiffContextSize)
}
func TestDoesntDecreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *testing.T) {
gui := NewDummyGui()
setupGuiForTest(gui)
- gui.UserConfig.Git.DiffContextSize = 2
- _ = gui.pushContext(gui.State.Contexts.CommitFiles)
- gui.Git.Patch.PatchManager.Start("from", "to", false, false)
+ gui.c.UserConfig.Git.DiffContextSize = 2
+ _ = gui.c.PushContext(gui.State.Contexts.CommitFiles)
+ gui.git.Patch.PatchManager.Start("from", "to", false, false)
errorCount := 0
gui.PopupHandler = &popup.TestPopupHandler{
OnErrorMsg: func(message string) error {
- assert.Equal(t, gui.Tr.CantChangeContextSizeError, message)
+ assert.Equal(t, gui.c.Tr.CantChangeContextSizeError, message)
errorCount += 1
return nil
},
@@ -177,15 +177,15 @@ func TestDoesntDecreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *test
_ = gui.DecreaseContextInDiffView()
- assert.Equal(t, 2, gui.UserConfig.Git.DiffContextSize)
+ assert.Equal(t, 2, gui.c.UserConfig.Git.DiffContextSize)
}
func TestDecreasesContextInDiffViewNoFurtherThanOne(t *testing.T) {
gui := NewDummyGui()
setupGuiForTest(gui)
- gui.UserConfig.Git.DiffContextSize = 1
+ gui.c.UserConfig.Git.DiffContextSize = 1
_ = gui.DecreaseContextInDiffView()
- assert.Equal(t, 1, gui.UserConfig.Git.DiffContextSize)
+ assert.Equal(t, 1, gui.c.UserConfig.Git.DiffContextSize)
}
diff --git a/pkg/gui/diffing.go b/pkg/gui/diffing.go
index 232f9130b..2408cc79f 100644
--- a/pkg/gui/diffing.go
+++ b/pkg/gui/diffing.go
@@ -11,7 +11,7 @@ import (
func (gui *Gui) exitDiffMode() error {
gui.State.Modes.Diffing = diffing.New()
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
}
func (gui *Gui) renderDiff() error {
@@ -112,11 +112,11 @@ func (gui *Gui) handleCreateDiffingMenuPanel() error {
name := name
menuItems = append(menuItems, []*popup.MenuItem{
{
- DisplayString: fmt.Sprintf("%s %s", gui.Tr.LcDiff, name),
+ DisplayString: fmt.Sprintf("%s %s", gui.c.Tr.LcDiff, name),
OnPress: func() error {
gui.State.Modes.Diffing.Ref = name
// can scope this down based on current view but too lazy right now
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
},
},
}...)
@@ -124,14 +124,14 @@ func (gui *Gui) handleCreateDiffingMenuPanel() error {
menuItems = append(menuItems, []*popup.MenuItem{
{
- DisplayString: gui.Tr.LcEnterRefToDiff,
+ DisplayString: gui.c.Tr.LcEnterRefToDiff,
OnPress: func() error {
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- Title: gui.Tr.LcEnteRefName,
- FindSuggestionsFunc: gui.getRefsSuggestionsFunc(),
+ return gui.c.Prompt(popup.PromptOpts{
+ Title: gui.c.Tr.LcEnteRefName,
+ FindSuggestionsFunc: gui.suggestionsHelper.GetRefsSuggestionsFunc(),
HandleConfirm: func(response string) error {
gui.State.Modes.Diffing.Ref = strings.TrimSpace(response)
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
},
})
},
@@ -141,21 +141,21 @@ func (gui *Gui) handleCreateDiffingMenuPanel() error {
if gui.State.Modes.Diffing.Active() {
menuItems = append(menuItems, []*popup.MenuItem{
{
- DisplayString: gui.Tr.LcSwapDiff,
+ DisplayString: gui.c.Tr.LcSwapDiff,
OnPress: func() error {
gui.State.Modes.Diffing.Reverse = !gui.State.Modes.Diffing.Reverse
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
},
},
{
- DisplayString: gui.Tr.LcExitDiffMode,
+ DisplayString: gui.c.Tr.LcExitDiffMode,
OnPress: func() error {
gui.State.Modes.Diffing = diffing.New()
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
},
},
}...)
}
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: gui.Tr.DiffingMenuTitle, Items: menuItems})
+ return gui.c.Menu(popup.CreateMenuOptions{Title: gui.c.Tr.DiffingMenuTitle, Items: menuItems})
}
diff --git a/pkg/gui/discard_changes_menu_panel.go b/pkg/gui/discard_changes_menu_panel.go
index 7624730e0..473a4611f 100644
--- a/pkg/gui/discard_changes_menu_panel.go
+++ b/pkg/gui/discard_changes_menu_panel.go
@@ -15,27 +15,27 @@ func (gui *Gui) handleCreateDiscardMenu() error {
if node.File == nil {
menuItems = []*popup.MenuItem{
{
- DisplayString: gui.Tr.LcDiscardAllChanges,
+ DisplayString: gui.c.Tr.LcDiscardAllChanges,
OnPress: func() error {
- gui.logAction(gui.Tr.Actions.DiscardAllChangesInDirectory)
- if err := gui.Git.WorkingTree.DiscardAllDirChanges(node); err != nil {
- return gui.PopupHandler.Error(err)
+ gui.c.LogAction(gui.c.Tr.Actions.DiscardAllChangesInDirectory)
+ if err := gui.git.WorkingTree.DiscardAllDirChanges(node); err != nil {
+ return gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
}
if node.GetHasStagedChanges() && node.GetHasUnstagedChanges() {
menuItems = append(menuItems, &popup.MenuItem{
- DisplayString: gui.Tr.LcDiscardUnstagedChanges,
+ DisplayString: gui.c.Tr.LcDiscardUnstagedChanges,
OnPress: func() error {
- gui.logAction(gui.Tr.Actions.DiscardUnstagedChangesInDirectory)
- if err := gui.Git.WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
- return gui.PopupHandler.Error(err)
+ gui.c.LogAction(gui.c.Tr.Actions.DiscardUnstagedChangesInDirectory)
+ if err := gui.git.WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
+ return gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
})
}
@@ -48,41 +48,41 @@ func (gui *Gui) handleCreateDiscardMenu() error {
menuItems = []*popup.MenuItem{
{
- DisplayString: gui.Tr.LcSubmoduleStashAndReset,
+ DisplayString: gui.c.Tr.LcSubmoduleStashAndReset,
OnPress: func() error {
- return gui.resetSubmodule(submodule)
+ return gui.Controllers.Files.ResetSubmodule(submodule)
},
},
}
} else {
menuItems = []*popup.MenuItem{
{
- DisplayString: gui.Tr.LcDiscardAllChanges,
+ DisplayString: gui.c.Tr.LcDiscardAllChanges,
OnPress: func() error {
- gui.logAction(gui.Tr.Actions.DiscardAllChangesInFile)
- if err := gui.Git.WorkingTree.DiscardAllFileChanges(file); err != nil {
- return gui.PopupHandler.Error(err)
+ gui.c.LogAction(gui.c.Tr.Actions.DiscardAllChangesInFile)
+ if err := gui.git.WorkingTree.DiscardAllFileChanges(file); err != nil {
+ return gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
}
if file.HasStagedChanges && file.HasUnstagedChanges {
menuItems = append(menuItems, &popup.MenuItem{
- DisplayString: gui.Tr.LcDiscardUnstagedChanges,
+ DisplayString: gui.c.Tr.LcDiscardUnstagedChanges,
OnPress: func() error {
- gui.logAction(gui.Tr.Actions.DiscardAllUnstagedChangesInFile)
- if err := gui.Git.WorkingTree.DiscardUnstagedFileChanges(file); err != nil {
- return gui.PopupHandler.Error(err)
+ gui.c.LogAction(gui.c.Tr.Actions.DiscardAllUnstagedChangesInFile)
+ if err := gui.git.WorkingTree.DiscardUnstagedFileChanges(file); err != nil {
+ return gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
})
}
}
}
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: node.GetPath(), Items: menuItems})
+ return gui.c.Menu(popup.CreateMenuOptions{Title: node.GetPath(), Items: menuItems})
}
diff --git a/pkg/gui/editors.go b/pkg/gui/editors.go
index 054a36094..145a6a62b 100644
--- a/pkg/gui/editors.go
+++ b/pkg/gui/editors.go
@@ -7,7 +7,7 @@ import (
)
func (gui *Gui) handleEditorKeypress(textArea *gocui.TextArea, key gocui.Key, ch rune, mod gocui.Modifier, allowMultiline bool) bool {
- newlineKey, ok := gui.getKey(gui.UserConfig.Keybinding.Universal.AppendNewline).(gocui.Key)
+ newlineKey, ok := gui.getKey(gui.c.UserConfig.Keybinding.Universal.AppendNewline).(gocui.Key)
if !ok {
newlineKey = gocui.KeyAltEnter
}
@@ -62,7 +62,7 @@ func (gui *Gui) commitMessageEditor(v *gocui.View, key gocui.Key, ch rune, mod g
// considered out of bounds to add a newline, meaning we can avoid unnecessary scrolling.
err := gui.resizePopupPanel(v, v.TextArea.GetContent())
if err != nil {
- gui.Log.Error(err)
+ gui.c.Log.Error(err)
}
v.RenderTextArea()
gui.RenderCommitLength()
diff --git a/pkg/gui/extras_panel.go b/pkg/gui/extras_panel.go
index bd65dea87..fd2998cb3 100644
--- a/pkg/gui/extras_panel.go
+++ b/pkg/gui/extras_panel.go
@@ -8,11 +8,11 @@ import (
)
func (gui *Gui) handleCreateExtrasMenuPanel() error {
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{
- Title: gui.Tr.CommandLog,
+ return gui.c.Menu(popup.CreateMenuOptions{
+ Title: gui.c.Tr.CommandLog,
Items: []*popup.MenuItem{
{
- DisplayString: gui.Tr.ToggleShowCommandLog,
+ DisplayString: gui.c.Tr.ToggleShowCommandLog,
OnPress: func() error {
currentContext := gui.currentStaticContext()
if gui.ShowExtrasWindow && currentContext.GetKey() == COMMAND_LOG_CONTEXT_KEY {
@@ -22,13 +22,13 @@ func (gui *Gui) handleCreateExtrasMenuPanel() error {
}
show := !gui.ShowExtrasWindow
gui.ShowExtrasWindow = show
- gui.Config.GetAppState().HideCommandLog = !show
- _ = gui.Config.SaveAppState()
+ gui.c.GetAppState().HideCommandLog = !show
+ _ = gui.c.SaveAppState()
return nil
},
},
{
- DisplayString: gui.Tr.FocusCommandLog,
+ DisplayString: gui.c.Tr.FocusCommandLog,
OnPress: gui.handleFocusCommandLog,
},
},
@@ -37,8 +37,9 @@ func (gui *Gui) handleCreateExtrasMenuPanel() error {
func (gui *Gui) handleFocusCommandLog() error {
gui.ShowExtrasWindow = true
+ // TODO: is this necessary? Can't I just call 'return from context'?
gui.State.Contexts.CommandLog.SetParentContext(gui.currentSideContext())
- return gui.pushContext(gui.State.Contexts.CommandLog)
+ return gui.c.PushContext(gui.State.Contexts.CommandLog)
}
func (gui *Gui) scrollUpExtra() error {
@@ -58,7 +59,7 @@ func (gui *Gui) scrollDownExtra() error {
}
func (gui *Gui) getCmdWriter() io.Writer {
- return &prefixWriter{writer: gui.Views.Extras, prefix: style.FgMagenta.Sprintf("\n\n%s\n", gui.Tr.GitOutput)}
+ return &prefixWriter{writer: gui.Views.Extras, prefix: style.FgMagenta.Sprintf("\n\n%s\n", gui.c.Tr.GitOutput)}
}
// Ensures that the first write is preceded by writing a prefix.
diff --git a/pkg/gui/file_helper.go b/pkg/gui/file_helper.go
new file mode 100644
index 000000000..1b50aac82
--- /dev/null
+++ b/pkg/gui/file_helper.go
@@ -0,0 +1,51 @@
+package gui
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/commands"
+ "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
+ "github.com/jesseduffield/lazygit/pkg/gui/controllers"
+)
+
+type FileHelper struct {
+ c *controllers.ControllerCommon
+ git *commands.GitCommand
+ os *oscommands.OSCommand
+}
+
+func NewFileHelper(
+ c *controllers.ControllerCommon,
+ git *commands.GitCommand,
+ os *oscommands.OSCommand,
+) *FileHelper {
+ return &FileHelper{
+ c: c,
+ git: git,
+ os: os,
+ }
+}
+
+var _ controllers.IFileHelper = &FileHelper{}
+
+func (self *FileHelper) EditFile(filename string) error {
+ return self.EditFileAtLine(filename, 1)
+}
+
+func (self *FileHelper) EditFileAtLine(filename string, lineNumber int) error {
+ cmdStr, err := self.git.File.GetEditCmdStr(filename, lineNumber)
+ if err != nil {
+ return self.c.Error(err)
+ }
+
+ self.c.LogAction(self.c.Tr.Actions.EditFile)
+ return self.c.RunSubprocessAndRefresh(
+ self.os.Cmd.NewShell(cmdStr),
+ )
+}
+
+func (self *FileHelper) OpenFile(filename string) error {
+ self.c.LogAction(self.c.Tr.Actions.OpenFile)
+ if err := self.os.OpenFile(filename); err != nil {
+ return self.c.Error(err)
+ }
+ return nil
+}
diff --git a/pkg/gui/file_watching.go b/pkg/gui/file_watching.go
index 9f4e84a0f..01a2d0b88 100644
--- a/pkg/gui/file_watching.go
+++ b/pkg/gui/file_watching.go
@@ -118,13 +118,13 @@ func (gui *Gui) watchFilesForChanges() {
}
// only refresh if we're not already
if !gui.State.IsRefreshingFiles {
- _ = gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
+ _ = gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
}
// watch for errors
case err := <-gui.fileWatcher.Watcher.Errors:
if err != nil {
- gui.Log.Error(err)
+ gui.c.Log.Error(err)
}
}
}
diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go
index b442a566f..8ebecba14 100644
--- a/pkg/gui/files_panel.go
+++ b/pkg/gui/files_panel.go
@@ -1,15 +1,10 @@
package gui
import (
- "fmt"
- "regexp"
- "strings"
-
- "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
+ "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
- "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
@@ -52,7 +47,7 @@ func (gui *Gui) filesRenderToMain() error {
return gui.refreshMainViews(refreshMainOpts{
main: &viewUpdateOpts{
title: "",
- task: NewRenderStringTask(gui.Tr.NoChangedFiles),
+ task: NewRenderStringTask(gui.c.Tr.NoChangedFiles),
},
})
}
@@ -69,24 +64,24 @@ func (gui *Gui) filesRenderToMain() error {
gui.resetMergeStateWithLock()
- cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.IgnoreWhitespaceInDiffView)
+ cmdObj := gui.git.WorkingTree.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.IgnoreWhitespaceInDiffView)
refreshOpts := refreshMainOpts{main: &viewUpdateOpts{
- title: gui.Tr.UnstagedChanges,
+ title: gui.c.Tr.UnstagedChanges,
task: NewRunPtyTask(cmdObj.GetCmd()),
}}
if node.GetHasUnstagedChanges() {
if node.GetHasStagedChanges() {
- cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.IgnoreWhitespaceInDiffView)
+ cmdObj := gui.git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.IgnoreWhitespaceInDiffView)
refreshOpts.secondary = &viewUpdateOpts{
- title: gui.Tr.StagedChanges,
+ title: gui.c.Tr.StagedChanges,
task: NewRunPtyTask(cmdObj.GetCmd()),
}
}
} else {
- refreshOpts.main.title = gui.Tr.StagedChanges
+ refreshOpts.main.title = gui.c.Tr.StagedChanges
}
return gui.refreshMainViews(refreshOpts)
@@ -115,12 +110,12 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
}
gui.OnUIThread(func() error {
- if err := gui.postRefreshUpdate(gui.State.Contexts.Submodules); err != nil {
- gui.Log.Error(err)
+ if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Submodules); err != nil {
+ gui.c.Log.Error(err)
}
- if ContextKey(gui.Views.Files.Context) == FILES_CONTEXT_KEY {
- // doing this a little custom (as opposed to using gui.postRefreshUpdate) because we handle selecting the file explicitly below
+ if types.ContextKey(gui.Views.Files.Context) == FILES_CONTEXT_KEY {
+ // doing this a little custom (as opposed to using gui.c.PostRefreshUpdate) because we handle selecting the file explicitly below
if err := gui.State.Contexts.Files.HandleRender(); err != nil {
return err
}
@@ -143,418 +138,6 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
return nil
}
-// specific functions
-
-func (gui *Gui) stagedFiles() []*models.File {
- files := gui.State.FileTreeViewModel.GetAllFiles()
- result := make([]*models.File, 0)
- for _, file := range files {
- if file.HasStagedChanges {
- result = append(result, file)
- }
- }
- return result
-}
-
-func (gui *Gui) trackedFiles() []*models.File {
- files := gui.State.FileTreeViewModel.GetAllFiles()
- result := make([]*models.File, 0, len(files))
- for _, file := range files {
- if file.Tracked {
- result = append(result, file)
- }
- }
- return result
-}
-
-func (gui *Gui) handleEnterFile() error {
- return gui.enterFile(OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1})
-}
-
-func (gui *Gui) enterFile(opts OnFocusOpts) error {
- node := gui.getSelectedFileNode()
- if node == nil {
- return nil
- }
-
- if node.File == nil {
- return gui.handleToggleDirCollapsed()
- }
-
- file := node.File
-
- submoduleConfigs := gui.State.Submodules
- if file.IsSubmodule(submoduleConfigs) {
- submoduleConfig := file.SubmoduleConfig(submoduleConfigs)
- return gui.enterSubmodule(submoduleConfig)
- }
-
- if file.HasInlineMergeConflicts {
- return gui.switchToMerge()
- }
- if file.HasMergeConflicts {
- return gui.PopupHandler.ErrorMsg(gui.Tr.FileStagingRequirements)
- }
-
- return gui.pushContext(gui.State.Contexts.Staging, opts)
-}
-
-func (gui *Gui) handleFilePress() error {
- node := gui.getSelectedFileNode()
- if node == nil {
- return nil
- }
-
- if node.IsLeaf() {
- file := node.File
-
- if file.HasInlineMergeConflicts {
- return gui.switchToMerge()
- }
-
- if file.HasUnstagedChanges {
- gui.logAction(gui.Tr.Actions.StageFile)
- if err := gui.Git.WorkingTree.StageFile(file.Name); err != nil {
- return gui.PopupHandler.Error(err)
- }
- } else {
- gui.logAction(gui.Tr.Actions.UnstageFile)
- if err := gui.Git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
- return gui.PopupHandler.Error(err)
- }
- }
- } else {
- // if any files within have inline merge conflicts we can't stage or unstage,
- // or it'll end up with those >>>>>> lines actually staged
- if node.GetHasInlineMergeConflicts() {
- return gui.PopupHandler.ErrorMsg(gui.Tr.ErrStageDirWithInlineMergeConflicts)
- }
-
- if node.GetHasUnstagedChanges() {
- gui.logAction(gui.Tr.Actions.StageFile)
- if err := gui.Git.WorkingTree.StageFile(node.Path); err != nil {
- return gui.PopupHandler.Error(err)
- }
- } else {
- // pretty sure it doesn't matter that we're always passing true here
- gui.logAction(gui.Tr.Actions.UnstageFile)
- if err := gui.Git.WorkingTree.UnStageFile([]string{node.Path}, true); err != nil {
- return gui.PopupHandler.Error(err)
- }
- }
- }
-
- if err := gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
- return err
- }
-
- return gui.State.Contexts.Files.HandleFocus()
-}
-
-func (gui *Gui) allFilesStaged() bool {
- for _, file := range gui.State.FileTreeViewModel.GetAllFiles() {
- if file.HasUnstagedChanges {
- return false
- }
- }
- return true
-}
-
-func (gui *Gui) onFocusFile() error {
- gui.takeOverMergeConflictScrolling()
- return nil
-}
-
-func (gui *Gui) handleStageAll() error {
- var err error
- if gui.allFilesStaged() {
- gui.logAction(gui.Tr.Actions.UnstageAllFiles)
- err = gui.Git.WorkingTree.UnstageAll()
- } else {
- gui.logAction(gui.Tr.Actions.StageAllFiles)
- err = gui.Git.WorkingTree.StageAll()
- }
- if err != nil {
- _ = gui.PopupHandler.Error(err)
- }
-
- if err := gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
- return err
- }
-
- return gui.State.Contexts.Files.HandleFocus()
-}
-
-func (gui *Gui) handleIgnoreFile() error {
- node := gui.getSelectedFileNode()
- if node == nil {
- return nil
- }
-
- if node.GetPath() == ".gitignore" {
- return gui.PopupHandler.ErrorMsg("Cannot ignore .gitignore")
- }
-
- unstageFiles := func() error {
- return node.ForEachFile(func(file *models.File) error {
- if file.HasStagedChanges {
- if err := gui.Git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
- return err
- }
- }
-
- return nil
- })
- }
-
- if node.GetIsTracked() {
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.IgnoreTracked,
- Prompt: gui.Tr.IgnoreTrackedPrompt,
- HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.IgnoreFile)
- // not 100% sure if this is necessary but I'll assume it is
- if err := unstageFiles(); err != nil {
- return err
- }
-
- if err := gui.Git.WorkingTree.RemoveTrackedFiles(node.GetPath()); err != nil {
- return err
- }
-
- if err := gui.Git.WorkingTree.Ignore(node.GetPath()); err != nil {
- return err
- }
- return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
- },
- })
- }
-
- gui.logAction(gui.Tr.Actions.IgnoreFile)
-
- if err := unstageFiles(); err != nil {
- return err
- }
-
- if err := gui.Git.WorkingTree.Ignore(node.GetPath()); err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
-}
-
-func (gui *Gui) handleWIPCommitPress() error {
- skipHookPrefix := gui.UserConfig.Git.SkipHookPrefix
- if skipHookPrefix == "" {
- return gui.PopupHandler.ErrorMsg(gui.Tr.SkipHookPrefixNotConfigured)
- }
-
- textArea := gui.Views.CommitMessage.TextArea
- textArea.Clear()
- textArea.TypeString(skipHookPrefix)
- gui.Views.CommitMessage.RenderTextArea()
-
- return gui.handleCommitPress()
-}
-
-func (gui *Gui) commitPrefixConfigForRepo() *config.CommitPrefixConfig {
- cfg, ok := gui.UserConfig.Git.CommitPrefixes[utils.GetCurrentRepoName()]
- if !ok {
- return nil
- }
-
- return &cfg
-}
-
-func (gui *Gui) prepareFilesForCommit() error {
- noStagedFiles := len(gui.stagedFiles()) == 0
- if noStagedFiles && gui.UserConfig.Gui.SkipNoStagedFilesWarning {
- gui.logAction(gui.Tr.Actions.StageAllFiles)
- err := gui.Git.WorkingTree.StageAll()
- if err != nil {
- return err
- }
-
- return gui.refreshFilesAndSubmodules()
- }
-
- return nil
-}
-
-func (gui *Gui) handleCommitPress() error {
- if err := gui.prepareFilesForCommit(); err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- if gui.State.FileTreeViewModel.GetItemsLength() == 0 {
- return gui.PopupHandler.ErrorMsg(gui.Tr.NoFilesStagedTitle)
- }
-
- if len(gui.stagedFiles()) == 0 {
- return gui.promptToStageAllAndRetry(gui.handleCommitPress)
- }
-
- if len(gui.State.failedCommitMessage) > 0 {
- gui.Views.CommitMessage.ClearTextArea()
- gui.Views.CommitMessage.TextArea.TypeString(gui.State.failedCommitMessage)
- gui.Views.CommitMessage.RenderTextArea()
- } else {
- commitPrefixConfig := gui.commitPrefixConfigForRepo()
- if commitPrefixConfig != nil {
- prefixPattern := commitPrefixConfig.Pattern
- prefixReplace := commitPrefixConfig.Replace
- rgx, err := regexp.Compile(prefixPattern)
- if err != nil {
- return gui.PopupHandler.ErrorMsg(fmt.Sprintf("%s: %s", gui.Tr.LcCommitPrefixPatternError, err.Error()))
- }
- prefix := rgx.ReplaceAllString(gui.getCheckedOutBranch().Name, prefixReplace)
- gui.Views.CommitMessage.ClearTextArea()
- gui.Views.CommitMessage.TextArea.TypeString(prefix)
- gui.Views.CommitMessage.RenderTextArea()
- }
- }
-
- if err := gui.pushContext(gui.State.Contexts.CommitMessage); err != nil {
- return err
- }
-
- gui.RenderCommitLength()
- return nil
-}
-
-func (gui *Gui) promptToStageAllAndRetry(retry func() error) error {
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.NoFilesStagedTitle,
- Prompt: gui.Tr.NoFilesStagedPrompt,
- HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.StageAllFiles)
- if err := gui.Git.WorkingTree.StageAll(); err != nil {
- return gui.PopupHandler.Error(err)
- }
- if err := gui.refreshFilesAndSubmodules(); err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- return retry()
- },
- })
-}
-
-func (gui *Gui) handleAmendCommitPress() error {
- if gui.State.FileTreeViewModel.GetItemsLength() == 0 {
- return gui.PopupHandler.ErrorMsg(gui.Tr.NoFilesStagedTitle)
- }
-
- if len(gui.stagedFiles()) == 0 {
- return gui.promptToStageAllAndRetry(gui.handleAmendCommitPress)
- }
-
- if len(gui.State.Commits) == 0 {
- return gui.PopupHandler.ErrorMsg(gui.Tr.NoCommitToAmend)
- }
-
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: strings.Title(gui.Tr.AmendLastCommit),
- Prompt: gui.Tr.SureToAmend,
- HandleConfirm: func() error {
- cmdObj := gui.Git.Commit.AmendHeadCmdObj()
- gui.logAction(gui.Tr.Actions.AmendCommit)
- return gui.withGpgHandling(cmdObj, gui.Tr.AmendingStatus, nil)
- },
- })
-}
-
-// handleCommitEditorPress - handle when the user wants to commit changes via
-// their editor rather than via the popup panel
-func (gui *Gui) handleCommitEditorPress() error {
- if gui.State.FileTreeViewModel.GetItemsLength() == 0 {
- return gui.PopupHandler.ErrorMsg(gui.Tr.NoFilesStagedTitle)
- }
-
- if len(gui.stagedFiles()) == 0 {
- return gui.promptToStageAllAndRetry(gui.handleCommitEditorPress)
- }
-
- gui.logAction(gui.Tr.Actions.Commit)
- return gui.runSubprocessWithSuspenseAndRefresh(
- gui.Git.Commit.CommitEditorCmdObj(),
- )
-}
-
-func (gui *Gui) handleStatusFilterPressed() error {
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{
- Title: gui.Tr.FilteringMenuTitle,
- Items: []*popup.MenuItem{
- {
- DisplayString: gui.Tr.FilterStagedFiles,
- OnPress: func() error {
- return gui.setStatusFiltering(filetree.DisplayStaged)
- },
- },
- {
- DisplayString: gui.Tr.FilterUnstagedFiles,
- OnPress: func() error {
- return gui.setStatusFiltering(filetree.DisplayUnstaged)
- },
- },
- {
- DisplayString: gui.Tr.ResetCommitFilterState,
- OnPress: func() error {
- return gui.setStatusFiltering(filetree.DisplayAll)
- },
- },
- },
- })
-}
-
-func (gui *Gui) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error {
- state := gui.State
- state.FileTreeViewModel.SetFilter(filter)
- return gui.handleRefreshFiles()
-}
-
-func (gui *Gui) editFile(filename string) error {
- return gui.editFileAtLine(filename, 1)
-}
-
-func (gui *Gui) editFileAtLine(filename string, lineNumber int) error {
- cmdStr, err := gui.Git.File.GetEditCmdStr(filename, lineNumber)
- if err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- gui.logAction(gui.Tr.Actions.EditFile)
- return gui.runSubprocessWithSuspenseAndRefresh(
- gui.OSCommand.Cmd.NewShell(cmdStr),
- )
-}
-
-func (gui *Gui) handleFileEdit() error {
- node := gui.getSelectedFileNode()
- if node == nil {
- return nil
- }
-
- if node.File == nil {
- return gui.PopupHandler.ErrorMsg(gui.Tr.ErrCannotEditDirectory)
- }
-
- return gui.editFile(node.GetPath())
-}
-
-func (gui *Gui) handleFileOpen() error {
- node := gui.getSelectedFileNode()
- if node == nil {
- return nil
- }
-
- return gui.openFile(node.GetPath())
-}
-
-func (gui *Gui) handleRefreshFiles() error {
- return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
-}
-
func (gui *Gui) refreshStateFiles() error {
state := gui.State
@@ -591,13 +174,13 @@ func (gui *Gui) refreshStateFiles() error {
}
if len(pathsToStage) > 0 {
- gui.logAction(gui.Tr.Actions.StageResolvedFiles)
- if err := gui.Git.WorkingTree.StageFiles(pathsToStage); err != nil {
- return gui.surfaceError(err)
+ gui.c.LogAction(gui.Tr.Actions.StageResolvedFiles)
+ if err := gui.git.WorkingTree.StageFiles(pathsToStage); err != nil {
+ return gui.c.Error(err)
}
}
- files := gui.Git.Loaders.Files.
+ files := gui.git.Loaders.Files.
GetStatusFiles(loaders.GetStatusFileOptions{})
conflictFileCount := 0
@@ -607,7 +190,7 @@ func (gui *Gui) refreshStateFiles() error {
}
}
- if gui.Git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE && conflictFileCount == 0 && prevConflictFileCount > 0 {
+ if gui.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE && conflictFileCount == 0 && prevConflictFileCount > 0 {
gui.OnUIThread(func() error { return gui.promptToContinueRebase() })
}
@@ -716,218 +299,7 @@ func (gui *Gui) findNewSelectedIdx(prevNodes []*filetree.FileNode, currNodes []*
return -1
}
-func (gui *Gui) handlePullFiles() error {
- if gui.popupPanelFocused() {
- return nil
- }
-
- action := gui.Tr.Actions.Pull
-
- currentBranch := gui.currentBranch()
- if currentBranch == nil {
- // need to wait for branches to refresh
- return nil
- }
-
- // if we have no upstream branch we need to set that first
- if !currentBranch.IsTrackingRemote() {
- suggestedRemote := getSuggestedRemote(gui.State.Remotes)
-
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- Title: gui.Tr.EnterUpstream,
- InitialContent: suggestedRemote + " " + currentBranch.Name,
- FindSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc(" "),
- HandleConfirm: func(upstream string) error {
- var upstreamBranch, upstreamRemote string
- split := strings.Split(upstream, " ")
- if len(split) != 2 {
- return gui.PopupHandler.ErrorMsg(gui.Tr.InvalidUpstream)
- }
-
- upstreamRemote = split[0]
- upstreamBranch = split[1]
-
- if err := gui.Git.Branch.SetCurrentBranchUpstream(upstreamRemote, upstreamBranch); err != nil {
- errorMessage := err.Error()
- if strings.Contains(errorMessage, "does not exist") {
- errorMessage = fmt.Sprintf("upstream branch %s not found.\nIf you expect it to exist, you should fetch (with 'f').\nOtherwise, you should push (with 'shift+P')", upstream)
- }
- return gui.PopupHandler.ErrorMsg(errorMessage)
- }
- return gui.pullFiles(PullFilesOptions{UpstreamRemote: upstreamRemote, UpstreamBranch: upstreamBranch, action: action})
- },
- })
- }
-
- return gui.pullFiles(PullFilesOptions{UpstreamRemote: currentBranch.UpstreamRemote, UpstreamBranch: currentBranch.UpstreamBranch, action: action})
-}
-
-type PullFilesOptions struct {
- UpstreamRemote string
- UpstreamBranch string
- FastForwardOnly bool
- action string
-}
-
-func (gui *Gui) pullFiles(opts PullFilesOptions) error {
- return gui.PopupHandler.WithLoaderPanel(gui.Tr.PullWait, func() error {
- return gui.pullWithLock(opts)
- })
-}
-
-func (gui *Gui) pullWithLock(opts PullFilesOptions) error {
- gui.Mutexes.FetchMutex.Lock()
- defer gui.Mutexes.FetchMutex.Unlock()
-
- gui.logAction(opts.action)
-
- err := gui.Git.Sync.Pull(
- git_commands.PullOptions{
- RemoteName: opts.UpstreamRemote,
- BranchName: opts.UpstreamBranch,
- FastForwardOnly: opts.FastForwardOnly,
- },
- )
- if err == nil {
- _ = gui.closeConfirmationPrompt(false)
- }
- return gui.handleGenericMergeCommandResult(err)
-}
-
-type pushOpts struct {
- force bool
- upstreamRemote string
- upstreamBranch string
- setUpstream bool
-}
-
-func (gui *Gui) push(opts pushOpts) error {
- return gui.PopupHandler.WithLoaderPanel(gui.Tr.PushWait, func() error {
- gui.logAction(gui.Tr.Actions.Push)
- err := gui.Git.Sync.Push(git_commands.PushOpts{
- Force: opts.force,
- UpstreamRemote: opts.upstreamRemote,
- UpstreamBranch: opts.upstreamBranch,
- SetUpstream: opts.setUpstream,
- })
-
- if err != nil {
- if !opts.force && strings.Contains(err.Error(), "Updates were rejected") {
- forcePushDisabled := gui.UserConfig.Git.DisableForcePushing
- if forcePushDisabled {
- _ = gui.PopupHandler.ErrorMsg(gui.Tr.UpdatesRejectedAndForcePushDisabled)
- return nil
- }
- _ = gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.ForcePush,
- Prompt: gui.Tr.ForcePushPrompt,
- HandleConfirm: func() error {
- newOpts := opts
- newOpts.force = true
-
- return gui.push(newOpts)
- },
- })
- return nil
- }
- _ = gui.PopupHandler.Error(err)
- }
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
- })
-}
-
-func (gui *Gui) pushFiles() error {
- if gui.popupPanelFocused() {
- return nil
- }
-
- // 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.IsTrackingRemote() {
- opts := pushOpts{
- force: false,
- upstreamRemote: currentBranch.UpstreamRemote,
- upstreamBranch: currentBranch.UpstreamBranch,
- }
- if currentBranch.HasCommitsToPull() {
- opts.force = true
- return gui.requestToForcePush(opts)
- } else {
- return gui.push(opts)
- }
- } else {
- suggestedRemote := getSuggestedRemote(gui.State.Remotes)
-
- if gui.Git.Config.GetPushToCurrent() {
- return gui.push(pushOpts{setUpstream: true})
- } else {
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- Title: gui.Tr.EnterUpstream,
- InitialContent: suggestedRemote + " " + currentBranch.Name,
- FindSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc(" "),
- HandleConfirm: func(upstream string) error {
- var upstreamBranch, upstreamRemote string
- split := strings.Split(upstream, " ")
- if len(split) == 2 {
- upstreamRemote = split[0]
- upstreamBranch = split[1]
- } else {
- upstreamRemote = upstream
- upstreamBranch = ""
- }
-
- return gui.push(pushOpts{
- force: false,
- upstreamRemote: upstreamRemote,
- upstreamBranch: upstreamBranch,
- setUpstream: true,
- })
- },
- })
- }
- }
-}
-
-func getSuggestedRemote(remotes []*models.Remote) string {
- if len(remotes) == 0 {
- return "origin"
- }
-
- for _, remote := range remotes {
- if remote.Name == "origin" {
- return remote.Name
- }
- }
-
- return remotes[0].Name
-}
-
-func (gui *Gui) requestToForcePush(opts pushOpts) error {
- forcePushDisabled := gui.UserConfig.Git.DisableForcePushing
- if forcePushDisabled {
- return gui.PopupHandler.ErrorMsg(gui.Tr.ForcePushDisabled)
- }
-
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.ForcePush,
- Prompt: gui.Tr.ForcePushPrompt,
- HandleConfirm: func() error {
- return gui.push(opts)
- },
- })
-}
-
-func (gui *Gui) switchToMerge() error {
- file := gui.getSelectedFile()
- if file == nil {
- return nil
- }
-
+func (gui *Gui) onFocusFile() error {
gui.takeOverMergeConflictScrolling()
if gui.State.Panels.Merging.GetPath() != file.Name {
@@ -940,155 +312,14 @@ func (gui *Gui) switchToMerge() error {
}
}
+ // TODO: this can't be right.
return gui.pushContext(gui.State.Contexts.Merging)
}
-func (gui *Gui) openFile(filename string) error {
- gui.logAction(gui.Tr.Actions.OpenFile)
- if err := gui.OSCommand.OpenFile(filename); err != nil {
- return gui.PopupHandler.Error(err)
+func (gui *Gui) getSetTextareaTextFn(view *gocui.View) func(string) {
+ return func(text string) {
+ view.ClearTextArea()
+ view.TextArea.TypeString(text)
+ view.RenderTextArea()
}
- return nil
-}
-
-func (gui *Gui) handleCustomCommand() error {
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- Title: gui.Tr.CustomCommand,
- FindSuggestionsFunc: gui.getCustomCommandsHistorySuggestionsFunc(),
- HandleConfirm: func(command string) error {
- gui.Config.GetAppState().CustomCommandsHistory = utils.Limit(
- utils.Uniq(
- append(gui.Config.GetAppState().CustomCommandsHistory, command),
- ),
- 1000,
- )
-
- err := gui.Config.SaveAppState()
- if err != nil {
- gui.Log.Error(err)
- }
-
- gui.logAction(gui.Tr.Actions.CustomCommand)
- return gui.runSubprocessWithSuspenseAndRefresh(
- gui.OSCommand.Cmd.NewShell(command),
- )
- },
- })
-}
-
-func (gui *Gui) handleCreateStashMenu() error {
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{
- Title: gui.Tr.LcStashOptions,
- Items: []*popup.MenuItem{
- {
- DisplayString: gui.Tr.LcStashAllChanges,
- OnPress: func() error {
- gui.logAction(gui.Tr.Actions.StashAllChanges)
- return gui.handleStashSave(gui.Git.Stash.Save)
- },
- },
- {
- DisplayString: gui.Tr.LcStashStagedChanges,
- OnPress: func() error {
- gui.logAction(gui.Tr.Actions.StashStagedChanges)
- return gui.handleStashSave(gui.Git.Stash.SaveStagedChanges)
- },
- },
- },
- })
-}
-
-func (gui *Gui) handleStashChanges() error {
- return gui.handleStashSave(gui.Git.Stash.Save)
-}
-
-func (gui *Gui) handleCreateResetToUpstreamMenu() error {
- return gui.createResetMenu("@{upstream}")
-}
-
-func (gui *Gui) handleToggleDirCollapsed() error {
- node := gui.getSelectedFileNode()
- if node == nil {
- return nil
- }
-
- gui.State.FileTreeViewModel.ToggleCollapsed(node.GetPath())
-
- if err := gui.postRefreshUpdate(gui.State.Contexts.Files); err != nil {
- gui.Log.Error(err)
- }
-
- return nil
-}
-
-func (gui *Gui) handleToggleFileTreeView() error {
- // get path of currently selected file
- path := gui.getSelectedPath()
-
- gui.State.FileTreeViewModel.ToggleShowTree()
-
- // find that same node in the new format and move the cursor to it
- if path != "" {
- gui.State.FileTreeViewModel.ExpandToPath(path)
- index, found := gui.State.FileTreeViewModel.GetIndexForPath(path)
- if found {
- gui.filesListContext().GetPanelState().SetSelectedLineIdx(index)
- }
- }
-
- if ContextKey(gui.Views.Files.Context) == FILES_CONTEXT_KEY {
- if err := gui.State.Contexts.Files.HandleRender(); err != nil {
- return err
- }
- if err := gui.State.Contexts.Files.HandleFocus(); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (gui *Gui) handleOpenMergeTool() error {
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.MergeToolTitle,
- Prompt: gui.Tr.MergeToolPrompt,
- HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.OpenMergeTool)
- return gui.runSubprocessWithSuspenseAndRefresh(
- gui.Git.WorkingTree.OpenMergeToolCmdObj(),
- )
- },
- })
-}
-
-func (gui *Gui) resetSubmodule(submodule *models.SubmoduleConfig) error {
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.LcResettingSubmoduleStatus, func() error {
- gui.logAction(gui.Tr.Actions.ResetSubmodule)
-
- file := gui.fileForSubmodule(submodule)
- if file != nil {
- if err := gui.Git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
- return gui.PopupHandler.Error(err)
- }
- }
-
- if err := gui.Git.Submodule.Stash(submodule); err != nil {
- return gui.PopupHandler.Error(err)
- }
- if err := gui.Git.Submodule.Reset(submodule); err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.SUBMODULES}})
- })
-}
-
-func (gui *Gui) fileForSubmodule(submodule *models.SubmoduleConfig) *models.File {
- for _, file := range gui.State.FileManager.GetAllFiles() {
- if file.IsSubmodule([]*models.SubmoduleConfig{submodule}) {
- return file
- }
- }
-
- return nil
}
diff --git a/pkg/gui/filetree/commit_file_node.go b/pkg/gui/filetree/commit_file_node.go
index 14960ee30..98428348e 100644
--- a/pkg/gui/filetree/commit_file_node.go
+++ b/pkg/gui/filetree/commit_file_node.go
@@ -2,6 +2,7 @@ package filetree
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
)
type CommitFileNode struct {
@@ -12,8 +13,7 @@ type CommitFileNode struct {
}
var _ INode = &CommitFileNode{}
-
-// methods satisfying ListItem interface
+var _ types.ListItem = &CommitFileNode{}
func (s *CommitFileNode) ID() string {
return s.GetPath()
diff --git a/pkg/gui/filetree/file_node.go b/pkg/gui/filetree/file_node.go
index f332f0a76..5a99b3e12 100644
--- a/pkg/gui/filetree/file_node.go
+++ b/pkg/gui/filetree/file_node.go
@@ -2,6 +2,7 @@ package filetree
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
)
type FileNode struct {
@@ -12,8 +13,7 @@ type FileNode struct {
}
var _ INode = &FileNode{}
-
-// methods satisfying ListItem interface
+var _ types.ListItem = &FileNode{}
func (s *FileNode) ID() string {
return s.GetPath()
diff --git a/pkg/gui/filtering.go b/pkg/gui/filtering.go
index df007b69a..df4fa848d 100644
--- a/pkg/gui/filtering.go
+++ b/pkg/gui/filtering.go
@@ -5,17 +5,27 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
-func (gui *Gui) validateNotInFilterMode() (bool, error) {
+func (gui *Gui) validateNotInFilterMode() bool {
if gui.State.Modes.Filtering.Active() {
- err := gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.MustExitFilterModeTitle,
- Prompt: gui.Tr.MustExitFilterModePrompt,
+ _ = gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.MustExitFilterModeTitle,
+ Prompt: gui.c.Tr.MustExitFilterModePrompt,
HandleConfirm: gui.exitFilterMode,
})
- return false, err
+ return false
+ }
+ return true
+}
+
+func (gui *Gui) outsideFilterMode(f func() error) func() error {
+ return func() error {
+ if !gui.validateNotInFilterMode() {
+ return nil
+ }
+
+ return f()
}
- return true, nil
}
func (gui *Gui) exitFilterMode() error {
@@ -28,7 +38,7 @@ func (gui *Gui) clearFiltering() error {
gui.State.ScreenMode = SCREEN_NORMAL
}
- return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}})
+ return gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}})
}
func (gui *Gui) setFiltering(path string) error {
@@ -37,11 +47,11 @@ func (gui *Gui) setFiltering(path string) error {
gui.State.ScreenMode = SCREEN_HALF
}
- if err := gui.pushContext(gui.State.Contexts.BranchCommits); err != nil {
+ if err := gui.c.PushContext(gui.State.Contexts.BranchCommits); err != nil {
return err
}
- return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}, Then: func() {
+ return gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}, Then: func() {
gui.State.Contexts.BranchCommits.GetPanelState().SetSelectedLineIdx(0)
}})
}
diff --git a/pkg/gui/filtering_menu_panel.go b/pkg/gui/filtering_menu_panel.go
index dcdf1ec40..39d39765e 100644
--- a/pkg/gui/filtering_menu_panel.go
+++ b/pkg/gui/filtering_menu_panel.go
@@ -26,7 +26,7 @@ func (gui *Gui) handleCreateFilteringMenuPanel() error {
if fileName != "" {
menuItems = append(menuItems, &popup.MenuItem{
- DisplayString: fmt.Sprintf("%s '%s'", gui.Tr.LcFilterBy, fileName),
+ DisplayString: fmt.Sprintf("%s '%s'", gui.c.Tr.LcFilterBy, fileName),
OnPress: func() error {
return gui.setFiltering(fileName)
},
@@ -34,11 +34,11 @@ func (gui *Gui) handleCreateFilteringMenuPanel() error {
}
menuItems = append(menuItems, &popup.MenuItem{
- DisplayString: gui.Tr.LcFilterPathOption,
+ DisplayString: gui.c.Tr.LcFilterPathOption,
OnPress: func() error {
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- FindSuggestionsFunc: gui.getFilePathSuggestionsFunc(),
- Title: gui.Tr.EnterFileName,
+ return gui.c.Prompt(popup.PromptOpts{
+ FindSuggestionsFunc: gui.suggestionsHelper.GetFilePathSuggestionsFunc(),
+ Title: gui.c.Tr.EnterFileName,
HandleConfirm: func(response string) error {
return gui.setFiltering(strings.TrimSpace(response))
},
@@ -48,10 +48,10 @@ func (gui *Gui) handleCreateFilteringMenuPanel() error {
if gui.State.Modes.Filtering.Active() {
menuItems = append(menuItems, &popup.MenuItem{
- DisplayString: gui.Tr.LcExitFilterMode,
+ DisplayString: gui.c.Tr.LcExitFilterMode,
OnPress: gui.clearFiltering,
})
}
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: gui.Tr.FilteringMenuTitle, Items: menuItems})
+ return gui.c.Menu(popup.CreateMenuOptions{Title: gui.c.Tr.FilteringMenuTitle, Items: menuItems})
}
diff --git a/pkg/gui/git_flow.go b/pkg/gui/git_flow.go
index ab58adfe8..171310785 100644
--- a/pkg/gui/git_flow.go
+++ b/pkg/gui/git_flow.go
@@ -13,27 +13,27 @@ func (gui *Gui) handleCreateGitFlowMenu() error {
return nil
}
- if !gui.Git.Flow.GitFlowEnabled() {
- return gui.PopupHandler.ErrorMsg("You need to install git-flow and enable it in this repo to use git-flow features")
+ if !gui.git.Flow.GitFlowEnabled() {
+ return gui.c.ErrorMsg("You need to install git-flow and enable it in this repo to use git-flow features")
}
startHandler := func(branchType string) func() error {
return func() error {
- title := utils.ResolvePlaceholderString(gui.Tr.NewGitFlowBranchPrompt, map[string]string{"branchType": branchType})
+ title := utils.ResolvePlaceholderString(gui.c.Tr.NewGitFlowBranchPrompt, map[string]string{"branchType": branchType})
- return gui.PopupHandler.Prompt(popup.PromptOpts{
+ return gui.c.Prompt(popup.PromptOpts{
Title: title,
HandleConfirm: func(name string) error {
- gui.logAction(gui.Tr.Actions.GitFlowStart)
+ gui.c.LogAction(gui.c.Tr.Actions.GitFlowStart)
return gui.runSubprocessWithSuspenseAndRefresh(
- gui.Git.Flow.StartCmdObj(branchType, name),
+ gui.git.Flow.StartCmdObj(branchType, name),
)
},
})
}
}
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{
+ return gui.c.Menu(popup.CreateMenuOptions{
Title: "git flow",
Items: []*popup.MenuItem{
{
@@ -64,11 +64,11 @@ func (gui *Gui) handleCreateGitFlowMenu() error {
}
func (gui *Gui) gitFlowFinishBranch(branchName string) error {
- cmdObj, err := gui.Git.Flow.FinishCmdObj(branchName)
+ cmdObj, err := gui.git.Flow.FinishCmdObj(branchName)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
- gui.logAction(gui.Tr.Actions.GitFlowFinish)
+ gui.c.LogAction(gui.c.Tr.Actions.GitFlowFinish)
return gui.runSubprocessWithSuspenseAndRefresh(cmdObj)
}
diff --git a/pkg/gui/global_handlers.go b/pkg/gui/global_handlers.go
index 1b4394519..380704fbc 100644
--- a/pkg/gui/global_handlers.go
+++ b/pkg/gui/global_handlers.go
@@ -65,7 +65,7 @@ func (gui *Gui) prevScreenMode() error {
func (gui *Gui) scrollUpView(view *gocui.View) error {
ox, oy := view.Origin()
- newOy := int(math.Max(0, float64(oy-gui.UserConfig.Gui.ScrollHeight)))
+ newOy := int(math.Max(0, float64(oy-gui.c.UserConfig.Gui.ScrollHeight)))
return view.SetOrigin(ox, newOy)
}
@@ -87,12 +87,12 @@ func (gui *Gui) scrollDownView(view *gocui.View) error {
func (gui *Gui) linesToScrollDown(view *gocui.View) int {
_, oy := view.Origin()
y := oy
- canScrollPastBottom := gui.UserConfig.Gui.ScrollPastBottom
+ canScrollPastBottom := gui.c.UserConfig.Gui.ScrollPastBottom
if !canScrollPastBottom {
_, sy := view.Size()
y += sy
}
- scrollHeight := gui.UserConfig.Gui.ScrollHeight
+ scrollHeight := gui.c.UserConfig.Gui.ScrollHeight
scrollableLines := view.ViewLinesHeight() - y
if scrollableLines < 0 {
return 0
@@ -177,7 +177,7 @@ func (gui *Gui) scrollDownConfirmationPanel() error {
}
func (gui *Gui) handleRefresh() error {
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
}
func (gui *Gui) handleMouseDownMain() error {
@@ -190,9 +190,9 @@ func (gui *Gui) handleMouseDownMain() error {
// set filename, set primary/secondary selected, set line number, then switch context
// I'll need to know it was changed though.
// Could I pass something along to the context change?
- return gui.enterFile(OnFocusOpts{ClickedViewName: "main", ClickedViewLineIdx: gui.Views.Main.SelectedLineIdx()})
+ return gui.Controllers.Files.EnterFile(types.OnFocusOpts{ClickedViewName: "main", ClickedViewLineIdx: gui.Views.Main.SelectedLineIdx()})
case gui.State.Contexts.CommitFiles:
- return gui.enterCommitFile(OnFocusOpts{ClickedViewName: "main", ClickedViewLineIdx: gui.Views.Main.SelectedLineIdx()})
+ return gui.enterCommitFile(types.OnFocusOpts{ClickedViewName: "main", ClickedViewLineIdx: gui.Views.Main.SelectedLineIdx()})
}
return nil
@@ -205,35 +205,29 @@ func (gui *Gui) handleMouseDownSecondary() error {
switch gui.g.CurrentView() {
case gui.Views.Files:
- return gui.enterFile(OnFocusOpts{ClickedViewName: "secondary", ClickedViewLineIdx: gui.Views.Secondary.SelectedLineIdx()})
+ return gui.Controllers.Files.EnterFile(types.OnFocusOpts{ClickedViewName: "secondary", ClickedViewLineIdx: gui.Views.Secondary.SelectedLineIdx()})
}
return nil
}
func (gui *Gui) fetch() (err error) {
- gui.Mutexes.FetchMutex.Lock()
- defer gui.Mutexes.FetchMutex.Unlock()
-
- gui.logAction("Fetch")
- err = gui.Git.Sync.Fetch(git_commands.FetchOptions{})
+ gui.c.LogAction("Fetch")
+ err = gui.git.Sync.Fetch(git_commands.FetchOptions{})
if err != nil && strings.Contains(err.Error(), "exit status 128") {
- _ = gui.PopupHandler.ErrorMsg(gui.Tr.PassUnameWrong)
+ _ = gui.c.ErrorMsg(gui.c.Tr.PassUnameWrong)
}
- _ = gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
+ _ = gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
return err
}
func (gui *Gui) backgroundFetch() (err error) {
- gui.Mutexes.FetchMutex.Lock()
- defer gui.Mutexes.FetchMutex.Unlock()
+ err = gui.git.Sync.Fetch(git_commands.FetchOptions{Background: true})
- err = gui.Git.Sync.Fetch(git_commands.FetchOptions{Background: true})
-
- _ = gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
+ _ = gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
return err
}
@@ -246,14 +240,14 @@ func (gui *Gui) handleCopySelectedSideContextItemToClipboard() error {
return nil
}
- gui.logAction(gui.Tr.Actions.CopyToClipboard)
+ gui.c.LogAction(gui.c.Tr.Actions.CopyToClipboard)
if err := gui.OSCommand.CopyToClipboard(itemId); err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
truncatedItemId := utils.TruncateWithEllipsis(strings.Replace(itemId, "\n", " ", -1), 50)
- gui.raiseToast(fmt.Sprintf("'%s' %s", truncatedItemId, gui.Tr.LcCopiedToClipboard))
+ gui.c.Toast(fmt.Sprintf("'%s' %s", truncatedItemId, gui.c.Tr.LcCopiedToClipboard))
return nil
}
diff --git a/pkg/gui/gpg.go b/pkg/gui/gpg.go
index 1384055bc..a469b2a61 100644
--- a/pkg/gui/gpg.go
+++ b/pkg/gui/gpg.go
@@ -14,9 +14,9 @@ import (
// we don't need to see a loading status if we're in a subprocess.
// TODO: work out if we actually need to use a shell command here
func (gui *Gui) withGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
- gui.logCommand(cmdObj.ToString(), true)
+ gui.LogCommand(cmdObj.ToString(), true)
- useSubprocess := gui.Git.Config.UsingGpg()
+ useSubprocess := gui.git.Config.UsingGpg()
if useSubprocess {
success, err := gui.runSubprocessWithSuspense(gui.OSCommand.Cmd.NewShell(cmdObj.ToString()))
if success && onSuccess != nil {
@@ -24,7 +24,7 @@ func (gui *Gui) withGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string,
return err
}
}
- if err := gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
+ if err := gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
return err
}
@@ -35,7 +35,7 @@ func (gui *Gui) withGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string,
}
func (gui *Gui) RunAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
- return gui.PopupHandler.WithWaitingStatus(waitingStatus, func() error {
+ return gui.c.WithWaitingStatus(waitingStatus, func() error {
cmdObj := gui.OSCommand.Cmd.NewShell(cmdObj.ToString())
cmdObj.AddEnvVars("TERM=dumb")
cmdWriter := gui.getCmdWriter()
@@ -45,12 +45,12 @@ func (gui *Gui) RunAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, on
if err := cmd.Run(); err != nil {
if _, err := cmd.Stdout.Write([]byte(fmt.Sprintf("%s\n", style.FgRed.Sprint(err.Error())))); err != nil {
- gui.Log.Error(err)
+ gui.c.Log.Error(err)
}
- _ = gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
- return gui.PopupHandler.Error(
+ _ = gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
+ return gui.c.Error(
fmt.Errorf(
- gui.Tr.GitCommandFailed, gui.UserConfig.Keybinding.Universal.ExtrasMenu,
+ gui.c.Tr.GitCommandFailed, gui.c.UserConfig.Keybinding.Universal.ExtrasMenu,
),
)
}
@@ -61,6 +61,6 @@ func (gui *Gui) RunAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, on
}
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
})
}
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index 2bfd7f2ac..45040e086 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -18,6 +18,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
+ "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/controllers"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/lbl"
@@ -25,6 +26,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
"github.com/jesseduffield/lazygit/pkg/gui/modes/filtering"
+ "github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/authors"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/graph"
@@ -55,13 +57,13 @@ const StartupPopupVersion = 5
var OverlappingEdges = false
type ContextManager struct {
- ContextStack []Context
+ ContextStack []types.Context
sync.RWMutex
}
-func NewContextManager(initialContext Context) ContextManager {
+func NewContextManager(initialContext types.Context) ContextManager {
return ContextManager{
- ContextStack: []Context{initialContext},
+ ContextStack: []types.Context{initialContext},
RWMutex: sync.RWMutex{},
}
}
@@ -72,7 +74,7 @@ type Repo string
type Gui struct {
*common.Common
g *gocui.Gui
- Git *commands.GitCommand
+ git *commands.GitCommand
OSCommand *oscommands.OSCommand
// this is the state of the GUI for the current repo
@@ -126,6 +128,7 @@ type Gui struct {
IsNewRepo bool
+ // controllers define keybindings for a given context
Controllers Controllers
// flag as to whether or not the diff view should ignore whitespace
@@ -133,10 +136,19 @@ type Gui struct {
// if this is true, we'll load our commits using `git log --all`
ShowWholeGitGraph bool
+
+ // we use this to decide whether we'll return to the original directory that
+ // lazygit was opened in, or if we'll retain the one we're currently in.
RetainOriginalDir bool
PrevLayout PrevLayout
+ c *controllers.ControllerCommon
+ refHelper *RefHelper
+ suggestionsHelper *SuggestionsHelper
+ fileHelper *FileHelper
+ workingTreeHelper *WorkingTreeHelper
+
// this is the initial dir we are in upon opening lazygit. We hold onto this
// in case we want to restore it before quitting for users who have set up
// the feature for changing directory upon quit.
@@ -182,7 +194,7 @@ type GuiRepoState struct {
Updating bool
Panels *panelStates
SplitMainPanel bool
- MainContext ContextKey // used to keep the main and secondary views' contexts in sync
+ MainContext types.ContextKey // used to keep the main and secondary views' contexts in sync
IsRefreshingFiles bool
Searching searchingState
@@ -192,9 +204,9 @@ type GuiRepoState struct {
Modes Modes
ContextManager ContextManager
- Contexts ContextTree
- ViewContextMap map[string]Context
- ViewTabContextMap map[string][]tabContext
+ Contexts context.ContextTree
+ ViewContextMap map[string]types.Context
+ ViewTabContextMap map[string][]context.TabContext
// WindowViewNameMap is a mapping of windows to the current view of that window.
// Some views move between windows for example the commitFiles view and when cycling through
@@ -212,12 +224,19 @@ type GuiRepoState struct {
// this is the message of the last failed commit attempt
failedCommitMessage string
- // TODO: move these into the gui struct
ScreenMode WindowMaximisation
}
type Controllers struct {
- Submodules *controllers.SubmodulesController
+ Submodules *controllers.SubmodulesController
+ Tags *controllers.TagsController
+ LocalCommits *controllers.LocalCommitsController
+ Files *controllers.FilesController
+ Remotes *controllers.RemotesController
+ Menu *controllers.MenuController
+ Bisect *controllers.BisectController
+ Undo *controllers.UndoController
+ Sync *controllers.SyncController
}
type listPanelState struct {
@@ -373,13 +392,15 @@ type Modes struct {
Diffing diffing.Diffing
}
+// if you add a new mutex here be sure to instantiate it. We're using pointers to
+// mutexes so that we can pass the mutexes to controllers.
type guiMutexes struct {
- RefreshingFilesMutex sync.Mutex
- RefreshingStatusMutex sync.Mutex
- FetchMutex sync.Mutex
- BranchCommitsMutex sync.Mutex
- LineByLinePanelMutex sync.Mutex
- SubprocessMutex sync.Mutex
+ RefreshingFilesMutex *sync.Mutex
+ RefreshingStatusMutex *sync.Mutex
+ FetchMutex *sync.Mutex
+ BranchCommitsMutex *sync.Mutex
+ LineByLinePanelMutex *sync.Mutex
+ SubprocessMutex *sync.Mutex
}
// reuseState determines if we pull the repo state from our repo state map or
@@ -402,7 +423,7 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
return
}
} else {
- gui.Log.Error(err)
+ gui.c.Log.Error(err)
}
}
@@ -424,6 +445,7 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
FilteredReflogCommits: make([]*models.Commit, 0),
ReflogCommits: make([]*models.Commit, 0),
StashEntries: make([]*models.StashEntry, 0),
+ BisectInfo: git_commands.NewNullBisectInfo(),
Panels: &panelStates{
// TODO: work out why some of these are -1 and some are 0. Last time I checked there was a good reason but I'm less certain now
Files: &filePanelState{listPanelState{SelectedLineIdx: -1}},
@@ -450,8 +472,8 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
CherryPicking: cherrypicking.New(),
Diffing: diffing.New(),
},
- ViewContextMap: contexts.initialViewContextMap(),
- ViewTabContextMap: contexts.initialViewTabContextMap(),
+ ViewContextMap: contexts.InitialViewContextMap(),
+ ViewTabContextMap: contexts.InitialViewTabContextMap(),
ScreenMode: screenMode,
// TODO: put contexts in the context manager
ContextManager: NewContextManager(initialContext),
@@ -462,21 +484,6 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
gui.RepoStateMap[Repo(currentDir)] = gui.State
}
-type guiCommon struct {
- gui *Gui
- popup.IPopupHandler
-}
-
-var _ controllers.IGuiCommon = &guiCommon{}
-
-func (self *guiCommon) LogAction(msg string) {
- self.gui.logAction(msg)
-}
-
-func (self *guiCommon) Refresh(opts types.RefreshOptions) error {
- return self.gui.refreshSidePanels(opts)
-}
-
// for now the split view will always be on
// NewGui builds a new gui handler
func NewGui(
@@ -504,13 +511,20 @@ func NewGui(
// but now we do it via state. So we need to still support the config for the
// sake of backwards compatibility. We're making use of short circuiting here
ShowExtrasWindow: cmn.UserConfig.Gui.ShowCommandLog && !config.GetAppState().HideCommandLog,
-
+ Mutexes: guiMutexes{
+ RefreshingFilesMutex: &sync.Mutex{},
+ RefreshingStatusMutex: &sync.Mutex{},
+ FetchMutex: &sync.Mutex{},
+ BranchCommitsMutex: &sync.Mutex{},
+ LineByLinePanelMutex: &sync.Mutex{},
+ SubprocessMutex: &sync.Mutex{},
+ },
InitialDir: initialDir,
}
guiIO := oscommands.NewGuiIO(
cmn.Log,
- gui.logCommand,
+ gui.LogCommand,
gui.getCmdWriter,
gui.promptUserForCredential,
)
@@ -519,42 +533,151 @@ func NewGui(
gui.OSCommand = osCommand
var err error
- gui.Git, err = commands.NewGitCommand(
+ gui.git, err = commands.NewGitCommand(
cmn,
osCommand,
gitConfig,
+ gui.Mutexes.FetchMutex,
)
if err != nil {
return nil, err
}
- gui.resetState(filterPath, false)
-
gui.watchFilesForChanges()
gui.PopupHandler = popup.NewPopupHandler(
cmn,
gui.createPopupPanel,
- func() error { return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC}) },
+ func() error { return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) },
func() error { return gui.closeConfirmationPrompt(false) },
gui.createMenu,
gui.withWaitingStatus,
+ gui.toast,
+ func() string { return gui.Views.Confirmation.TextArea.GetContent() },
)
- authors.SetCustomAuthors(gui.UserConfig.Gui.AuthorColors)
- presentation.SetCustomBranches(gui.UserConfig.Gui.BranchColors)
-
guiCommon := &guiCommon{gui: gui, IPopupHandler: gui.PopupHandler}
controllerCommon := &controllers.ControllerCommon{IGuiCommon: guiCommon, Common: cmn}
+ // storing this stuff on the gui for now to ease refactoring
+ // TODO: reset these controllers upon changing repos due to state changing
+ gui.c = controllerCommon
+
+ gui.resetState(filterPath, false)
+ authors.SetCustomAuthors(gui.UserConfig.Gui.AuthorColors)
+ presentation.SetCustomBranches(gui.UserConfig.Gui.BranchColors)
+
+ refHelper := NewRefHelper(
+ controllerCommon,
+ gui.git,
+ gui.State,
+ )
+ gui.refHelper = refHelper
+ gui.suggestionsHelper = NewSuggestionsHelper(controllerCommon, gui.State, gui.refreshSuggestions)
+ gui.fileHelper = NewFileHelper(controllerCommon, gui.git, osCommand)
+ gui.workingTreeHelper = NewWorkingTreeHelper(gui.State.FileTreeViewModel)
+
+ tagsController := controllers.NewTagsController(
+ controllerCommon,
+ gui.State.Contexts.Tags,
+ gui.git,
+ gui.State.Contexts,
+ refHelper,
+ gui.suggestionsHelper,
+ gui.getSelectedTag,
+ gui.switchToSubCommitsContext,
+ )
+
+ syncController := controllers.NewSyncController(
+ controllerCommon,
+ gui.git,
+ gui.getCheckedOutBranch,
+ gui.suggestionsHelper,
+ gui.getSuggestedRemote,
+ gui.checkMergeOrRebase,
+ )
+
gui.Controllers = Controllers{
Submodules: controllers.NewSubmodulesController(
controllerCommon,
+ gui.State.Contexts.Submodules,
+ gui.git,
gui.enterSubmodule,
- gui.Git,
- gui.State.Submodules,
gui.getSelectedSubmodule,
),
+ Files: controllers.NewFilesController(
+ controllerCommon,
+ gui.State.Contexts.Files,
+ gui.git,
+ osCommand,
+ gui.getSelectedFileNode,
+ gui.State.Contexts,
+ gui.State.FileTreeViewModel,
+ gui.enterSubmodule,
+ func() []*models.SubmoduleConfig { return gui.State.Submodules },
+ gui.getSetTextareaTextFn(gui.Views.CommitMessage),
+ gui.withGpgHandling,
+ func() string { return gui.State.failedCommitMessage },
+ func() []*models.Commit { return gui.State.Commits },
+ gui.getSelectedPath,
+ gui.switchToMerge,
+ gui.suggestionsHelper,
+ gui.refHelper,
+ gui.fileHelper,
+ gui.workingTreeHelper,
+ ),
+ Tags: tagsController,
+
+ LocalCommits: controllers.NewLocalCommitsController(
+ controllerCommon,
+ gui.State.Contexts.BranchCommits,
+ osCommand,
+ gui.git,
+ refHelper,
+ gui.getSelectedLocalCommit,
+ func() []*models.Commit { return gui.State.Commits },
+ func() int { return gui.State.Panels.Commits.SelectedLineIdx },
+ gui.checkMergeOrRebase,
+ syncController.HandlePull,
+ tagsController.CreateTagMenu,
+ gui.getHostingServiceMgr,
+ gui.SwitchToCommitFilesContext,
+ gui.handleOpenSearch,
+ func() bool { return gui.State.Panels.Commits.LimitCommits },
+ func(value bool) { gui.State.Panels.Commits.LimitCommits = value },
+ func() bool { return gui.ShowWholeGitGraph },
+ func(value bool) { gui.ShowWholeGitGraph = value },
+ ),
+
+ Remotes: controllers.NewRemotesController(
+ controllerCommon,
+ gui.State.Contexts.Remotes,
+ gui.git,
+ gui.State.Contexts,
+ gui.getSelectedRemote,
+ func(branches []*models.RemoteBranch) { gui.State.RemoteBranches = branches },
+ gui.Mutexes.FetchMutex,
+ ),
+ Menu: controllers.NewMenuController(
+ controllerCommon,
+ gui.State.Contexts.Menu,
+ gui.getSelectedMenuItem,
+ ),
+ Bisect: controllers.NewBisectController(
+ controllerCommon,
+ gui.State.Contexts.BranchCommits,
+ gui.git,
+ gui.getSelectedLocalCommit,
+ func() []*models.Commit { return gui.State.Commits },
+ ),
+ Undo: controllers.NewUndoController(
+ controllerCommon,
+ gui.git,
+ refHelper,
+ gui.workingTreeHelper,
+ func() []*models.Commit { return gui.State.FilteredReflogCommits },
+ ),
+ Sync: syncController,
}
return gui, nil
@@ -621,7 +744,7 @@ func (gui *Gui) Run() error {
}
gui.waitForIntro.Add(1)
- if gui.UserConfig.Git.AutoFetch {
+ if gui.c.UserConfig.Git.AutoFetch {
go utils.Safe(gui.startBackgroundFetch)
}
@@ -629,7 +752,7 @@ func (gui *Gui) Run() error {
g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout()))
- gui.Log.Info("starting main loop")
+ gui.c.Log.Info("starting main loop")
err = g.MainLoop()
return err
@@ -684,7 +807,7 @@ func (gui *Gui) runSubprocessWithSuspenseAndRefresh(subprocess oscommands.ICmdOb
return err
}
- if err := gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
+ if err := gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
return err
}
@@ -705,7 +828,7 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool,
}
if err := gui.g.Suspend(); err != nil {
- return false, gui.PopupHandler.Error(err)
+ return false, gui.c.Error(err)
}
gui.PauseBackgroundThreads = true
@@ -719,14 +842,14 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool,
gui.PauseBackgroundThreads = false
if cmdErr != nil {
- return false, gui.PopupHandler.Error(cmdErr)
+ return false, gui.c.Error(cmdErr)
}
return true, nil
}
func (gui *Gui) runSubprocess(cmdObj oscommands.ICmdObj) error { //nolint:unparam
- gui.logCommand(cmdObj.ToString(), true)
+ gui.LogCommand(cmdObj.ToString(), true)
subprocess := cmdObj.GetCmd()
subprocess.Stdout = os.Stdout
@@ -754,7 +877,7 @@ func (gui *Gui) loadNewRepo() error {
return err
}
- if err := gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
+ if err := gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
return err
}
@@ -774,7 +897,7 @@ func (gui *Gui) showInitialPopups(tasks []func(chan struct{}) error) {
task := task
go utils.Safe(func() {
if err := task(done); err != nil {
- _ = gui.PopupHandler.Error(err)
+ _ = gui.c.Error(err)
}
})
@@ -787,13 +910,13 @@ func (gui *Gui) showInitialPopups(tasks []func(chan struct{}) error) {
func (gui *Gui) showIntroPopupMessage(done chan struct{}) error {
onConfirm := func() error {
done <- struct{}{}
- gui.Config.GetAppState().StartupPopupVersion = StartupPopupVersion
- return gui.Config.SaveAppState()
+ gui.c.GetAppState().StartupPopupVersion = StartupPopupVersion
+ return gui.c.SaveAppState()
}
- return gui.PopupHandler.Ask(popup.AskOpts{
+ return gui.c.Ask(popup.AskOpts{
Title: "",
- Prompt: gui.Tr.IntroPopupMessage,
+ Prompt: gui.c.Tr.IntroPopupMessage,
HandleConfirm: onConfirm,
HandleClose: onConfirm,
})
@@ -826,9 +949,9 @@ func (gui *Gui) startBackgroundFetch() {
}
err := gui.backgroundFetch()
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
- _ = gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.NoAutomaticGitFetchTitle,
- Prompt: gui.Tr.NoAutomaticGitFetchBody,
+ _ = gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.NoAutomaticGitFetchTitle,
+ Prompt: gui.c.Tr.NoAutomaticGitFetchBody,
})
} else {
gui.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), gui.stopChan, func() error {
diff --git a/pkg/gui/gui_common.go b/pkg/gui/gui_common.go
new file mode 100644
index 000000000..8db398489
--- /dev/null
+++ b/pkg/gui/gui_common.go
@@ -0,0 +1,53 @@
+package gui
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
+ "github.com/jesseduffield/lazygit/pkg/config"
+ "github.com/jesseduffield/lazygit/pkg/gui/controllers"
+ "github.com/jesseduffield/lazygit/pkg/gui/popup"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+)
+
+// hacking this by including the gui struct for now until we split more things out
+type guiCommon struct {
+ gui *Gui
+ popup.IPopupHandler
+}
+
+var _ controllers.IGuiCommon = &guiCommon{}
+
+func (self *guiCommon) LogAction(msg string) {
+ self.gui.LogAction(msg)
+}
+
+func (self *guiCommon) LogCommand(cmdStr string, isCommandLine bool) {
+ self.gui.LogCommand(cmdStr, isCommandLine)
+}
+
+func (self *guiCommon) Refresh(opts types.RefreshOptions) error {
+ return self.gui.Refresh(opts)
+}
+
+func (self *guiCommon) PostRefreshUpdate(context types.Context) error {
+ return self.gui.postRefreshUpdate(context)
+}
+
+func (self *guiCommon) RunSubprocessAndRefresh(cmdObj oscommands.ICmdObj) error {
+ return self.gui.runSubprocessWithSuspenseAndRefresh(cmdObj)
+}
+
+func (self *guiCommon) PushContext(context types.Context, opts ...types.OnFocusOpts) error {
+ return self.gui.pushContext(context, opts...)
+}
+
+func (self *guiCommon) PopContext() error {
+ return self.gui.returnFromContext()
+}
+
+func (self *guiCommon) GetAppState() *config.AppState {
+ return self.gui.Config.GetAppState()
+}
+
+func (self *guiCommon) SaveAppState() error {
+ return self.gui.Config.SaveAppState()
+}
diff --git a/pkg/gui/information_panel.go b/pkg/gui/information_panel.go
index d09f495c5..07e64fb42 100644
--- a/pkg/gui/information_panel.go
+++ b/pkg/gui/information_panel.go
@@ -15,8 +15,8 @@ func (gui *Gui) informationStr() string {
}
if gui.g.Mouse {
- donate := style.FgMagenta.SetUnderline().Sprint(gui.Tr.Donate)
- askQuestion := style.FgYellow.SetUnderline().Sprint(gui.Tr.AskQuestion)
+ donate := style.FgMagenta.SetUnderline().Sprint(gui.c.Tr.Donate)
+ askQuestion := style.FgYellow.SetUnderline().Sprint(gui.c.Tr.AskQuestion)
return fmt.Sprintf("%s %s %s", donate, askQuestion, gui.Config.GetVersion())
} else {
return gui.Config.GetVersion()
@@ -35,7 +35,7 @@ func (gui *Gui) handleInfoClick() error {
for _, mode := range gui.modeStatuses() {
if mode.isActive() {
- if width-cx > len(gui.Tr.ResetInParentheses) {
+ if width-cx > len(gui.c.Tr.ResetInParentheses) {
return nil
}
return mode.reset()
@@ -43,9 +43,9 @@ func (gui *Gui) handleInfoClick() error {
}
// if we're not in an active mode we show the donate button
- if cx <= len(gui.Tr.Donate) {
+ if cx <= len(gui.c.Tr.Donate) {
return gui.OSCommand.OpenLink(constants.Links.Donate)
- } else if cx <= len(gui.Tr.Donate)+1+len(gui.Tr.AskQuestion) {
+ } else if cx <= len(gui.c.Tr.Donate)+1+len(gui.c.Tr.AskQuestion) {
return gui.OSCommand.OpenLink(constants.Links.Discussions)
}
return nil
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index 644e4560b..2f7e478b3 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -183,9 +183,24 @@ func (gui *Gui) getKey(key string) interface{} {
return nil
}
+func (gui *Gui) noPopupPanel(f func() error) func() error {
+ return func() error {
+ if gui.popupPanelFocused() {
+ return nil
+ }
+
+ return f()
+ }
+}
+
// GetInitialKeybindings is a function.
func (gui *Gui) GetInitialKeybindings() []*types.Binding {
- config := gui.UserConfig.Keybinding
+ config := gui.c.UserConfig.Keybinding
+
+ guards := types.KeybindingGuards{
+ OutsideFilterMode: gui.outsideFilterMode,
+ NoPopupPanel: gui.noPopupPanel,
+ }
bindings := []*types.Binding{
{
@@ -217,21 +232,21 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Key: gui.getKey(config.Universal.OpenRecentRepos),
Handler: gui.handleCreateRecentReposMenu,
Alternative: "",
- Description: gui.Tr.SwitchRepo,
+ Description: gui.c.Tr.SwitchRepo,
},
{
ViewName: "",
Key: gui.getKey(config.Universal.ScrollUpMain),
Handler: gui.scrollUpMain,
Alternative: "fn+up",
- Description: gui.Tr.LcScrollUpMainPanel,
+ Description: gui.c.Tr.LcScrollUpMainPanel,
},
{
ViewName: "",
Key: gui.getKey(config.Universal.ScrollDownMain),
Handler: gui.scrollDownMain,
Alternative: "fn+down",
- Description: gui.Tr.LcScrollDownMainPanel,
+ Description: gui.c.Tr.LcScrollDownMainPanel,
},
{
ViewName: "",
@@ -261,39 +276,27 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
ViewName: "",
Key: gui.getKey(config.Universal.CreateRebaseOptionsMenu),
Handler: gui.handleCreateRebaseOptionsMenu,
- Description: gui.Tr.ViewMergeRebaseOptions,
+ Description: gui.c.Tr.ViewMergeRebaseOptions,
OpensMenu: true,
},
{
ViewName: "",
Key: gui.getKey(config.Universal.CreatePatchOptionsMenu),
Handler: gui.handleCreatePatchOptionsMenu,
- Description: gui.Tr.ViewPatchOptions,
+ Description: gui.c.Tr.ViewPatchOptions,
OpensMenu: true,
},
- {
- ViewName: "",
- Key: gui.getKey(config.Universal.PushFiles),
- Handler: gui.pushFiles,
- Description: gui.Tr.LcPush,
- },
- {
- ViewName: "",
- Key: gui.getKey(config.Universal.PullFiles),
- Handler: gui.handlePullFiles,
- Description: gui.Tr.LcPull,
- },
{
ViewName: "",
Key: gui.getKey(config.Universal.Refresh),
Handler: gui.handleRefresh,
- Description: gui.Tr.LcRefresh,
+ Description: gui.c.Tr.LcRefresh,
},
{
ViewName: "",
Key: gui.getKey(config.Universal.OptionMenu),
Handler: gui.handleCreateOptionsMenu,
- Description: gui.Tr.LcOpenMenu,
+ Description: gui.c.Tr.LcOpenMenu,
OpensMenu: true,
},
{
@@ -308,236 +311,98 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Modifier: gocui.ModNone,
Handler: gui.handleCreateOptionsMenu,
},
- {
- ViewName: "",
- Key: gui.getKey(config.Universal.Undo),
- Handler: gui.reflogUndo,
- Description: gui.Tr.LcUndoReflog,
- },
- {
- ViewName: "",
- Key: gui.getKey(config.Universal.Redo),
- Handler: gui.reflogRedo,
- Description: gui.Tr.LcRedoReflog,
- },
{
ViewName: "status",
Key: gui.getKey(config.Universal.Edit),
Handler: gui.handleEditConfig,
- Description: gui.Tr.EditConfig,
+ Description: gui.c.Tr.EditConfig,
},
{
ViewName: "",
Key: gui.getKey(config.Universal.NextScreenMode),
Handler: gui.nextScreenMode,
- Description: gui.Tr.LcNextScreenMode,
+ Description: gui.c.Tr.LcNextScreenMode,
},
{
ViewName: "",
Key: gui.getKey(config.Universal.PrevScreenMode),
Handler: gui.prevScreenMode,
- Description: gui.Tr.LcPrevScreenMode,
+ Description: gui.c.Tr.LcPrevScreenMode,
},
{
ViewName: "status",
Key: gui.getKey(config.Universal.OpenFile),
Handler: gui.handleOpenConfig,
- Description: gui.Tr.OpenConfig,
+ Description: gui.c.Tr.OpenConfig,
},
{
ViewName: "status",
Key: gui.getKey(config.Status.CheckForUpdate),
Handler: gui.handleCheckForUpdate,
- Description: gui.Tr.LcCheckForUpdate,
+ Description: gui.c.Tr.LcCheckForUpdate,
},
{
ViewName: "status",
Key: gui.getKey(config.Status.RecentRepos),
Handler: gui.handleCreateRecentReposMenu,
- Description: gui.Tr.SwitchRepo,
+ Description: gui.c.Tr.SwitchRepo,
},
{
ViewName: "status",
Key: gui.getKey(config.Status.AllBranchesLogGraph),
Handler: gui.handleShowAllBranchLogs,
- Description: gui.Tr.LcAllBranchesLogGraph,
- },
- {
- ViewName: "files",
- Key: gui.getKey(""),
- Handler: gui.handleStatusFilterPressed,
- Description: gui.Tr.LcCommitFileFilter,
- },
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Files.CommitChanges),
- Handler: gui.handleCommitPress,
- Description: gui.Tr.CommitChanges,
- },
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Files.CommitChangesWithoutHook),
- Handler: gui.handleWIPCommitPress,
- Description: gui.Tr.LcCommitChangesWithoutHook,
- },
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Files.AmendLastCommit),
- Handler: gui.handleAmendCommitPress,
- Description: gui.Tr.AmendLastCommit,
- },
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Files.CommitChangesWithEditor),
- Handler: gui.handleCommitEditorPress,
- Description: gui.Tr.CommitChangesWithEditor,
- },
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Universal.Select),
- Handler: gui.handleFilePress,
- Description: gui.Tr.LcToggleStaged,
+ Description: gui.c.Tr.LcAllBranchesLogGraph,
},
{
ViewName: "files",
Contexts: []string{string(FILES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Remove),
Handler: gui.handleCreateDiscardMenu,
- Description: gui.Tr.LcViewDiscardOptions,
+ Description: gui.c.Tr.LcViewDiscardOptions,
OpensMenu: true,
},
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Universal.Edit),
- Handler: gui.handleFileEdit,
- Description: gui.Tr.LcEditFile,
- },
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Universal.OpenFile),
- Handler: gui.handleFileOpen,
- Description: gui.Tr.LcOpenFile,
- },
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Files.IgnoreFile),
- Handler: gui.handleIgnoreFile,
- Description: gui.Tr.LcIgnoreFile,
- },
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Files.RefreshFiles),
- Handler: gui.handleRefreshFiles,
- Description: gui.Tr.LcRefreshFiles,
- },
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Files.StashAllChanges),
- Handler: gui.handleStashChanges,
- Description: gui.Tr.LcStashAllChanges,
- },
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Files.ViewStashOptions),
- Handler: gui.handleCreateStashMenu,
- Description: gui.Tr.LcViewStashOptions,
- OpensMenu: true,
- },
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Files.ToggleStagedAll),
- Handler: gui.handleStageAll,
- Description: gui.Tr.LcToggleStagedAll,
- },
{
ViewName: "files",
Contexts: []string{string(FILES_CONTEXT_KEY)},
Key: gui.getKey(config.Files.ViewResetOptions),
Handler: gui.handleCreateResetMenu,
- Description: gui.Tr.LcViewResetOptions,
+ Description: gui.c.Tr.LcViewResetOptions,
OpensMenu: true,
},
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Universal.GoInto),
- Handler: gui.handleEnterFile,
- Description: gui.Tr.FileEnter,
- },
{
ViewName: "files",
Contexts: []string{string(FILES_CONTEXT_KEY)},
Key: gui.getKey(config.Files.Fetch),
Handler: gui.handleGitFetch,
- Description: gui.Tr.LcFetch,
+ Description: gui.c.Tr.LcFetch,
},
{
ViewName: "files",
Contexts: []string{string(FILES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.CopyToClipboard),
Handler: gui.handleCopySelectedSideContextItemToClipboard,
- Description: gui.Tr.LcCopyFileNameToClipboard,
- },
- {
- ViewName: "",
- Key: gui.getKey(config.Universal.ExecuteCustomCommand),
- Handler: gui.handleCustomCommand,
- Description: gui.Tr.LcExecuteCustomCommand,
- },
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.ViewResetOptions),
- Handler: gui.handleCreateResetToUpstreamMenu,
- Description: gui.Tr.LcViewResetToUpstreamOptions,
- OpensMenu: true,
- },
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Files.ToggleTreeView),
- Handler: gui.handleToggleFileTreeView,
- Description: gui.Tr.LcToggleTreeView,
- },
- {
- ViewName: "files",
- Contexts: []string{string(FILES_CONTEXT_KEY)},
- Key: gui.getKey(config.Files.OpenMergeTool),
- Handler: gui.handleOpenMergeTool,
- Description: gui.Tr.LcOpenMergeTool,
+ Description: gui.c.Tr.LcCopyFileNameToClipboard,
},
{
ViewName: "branches",
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Select),
Handler: gui.handleBranchPress,
- Description: gui.Tr.LcCheckout,
+ Description: gui.c.Tr.LcCheckout,
},
{
ViewName: "branches",
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Branches.CreatePullRequest),
Handler: gui.handleCreatePullRequestPress,
- Description: gui.Tr.LcCreatePullRequest,
+ Description: gui.c.Tr.LcCreatePullRequest,
},
{
ViewName: "branches",
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Branches.ViewPullRequestOptions),
Handler: gui.handleCreatePullRequestMenu,
- Description: gui.Tr.LcCreatePullRequestOptions,
+ Description: gui.c.Tr.LcCreatePullRequestOptions,
OpensMenu: true,
},
{
@@ -545,56 +410,56 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Branches.CopyPullRequestURL),
Handler: gui.handleCopyPullRequestURLPress,
- Description: gui.Tr.LcCopyPullRequestURL,
+ Description: gui.c.Tr.LcCopyPullRequestURL,
},
{
ViewName: "branches",
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Branches.CheckoutBranchByName),
Handler: gui.handleCheckoutByName,
- Description: gui.Tr.LcCheckoutByName,
+ Description: gui.c.Tr.LcCheckoutByName,
},
{
ViewName: "branches",
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Branches.ForceCheckoutBranch),
Handler: gui.handleForceCheckout,
- Description: gui.Tr.LcForceCheckout,
+ Description: gui.c.Tr.LcForceCheckout,
},
{
ViewName: "branches",
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.New),
Handler: gui.handleNewBranchOffCurrentItem,
- Description: gui.Tr.LcNewBranch,
+ Description: gui.c.Tr.LcNewBranch,
},
{
ViewName: "branches",
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Remove),
Handler: gui.handleDeleteBranch,
- Description: gui.Tr.LcDeleteBranch,
+ Description: gui.c.Tr.LcDeleteBranch,
},
{
ViewName: "branches",
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Branches.RebaseBranch),
- Handler: gui.handleRebaseOntoLocalBranch,
- Description: gui.Tr.LcRebaseBranch,
+ Handler: guards.OutsideFilterMode(gui.handleRebaseOntoLocalBranch),
+ Description: gui.c.Tr.LcRebaseBranch,
},
{
ViewName: "branches",
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Branches.MergeIntoCurrentBranch),
- Handler: gui.handleMerge,
- Description: gui.Tr.LcMergeIntoCurrentBranch,
+ Handler: guards.OutsideFilterMode(gui.handleMerge),
+ Description: gui.c.Tr.LcMergeIntoCurrentBranch,
},
{
ViewName: "branches",
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Branches.ViewGitFlowOptions),
Handler: gui.handleCreateGitFlowMenu,
- Description: gui.Tr.LcGitFlowOptions,
+ Description: gui.c.Tr.LcGitFlowOptions,
OpensMenu: true,
},
{
@@ -602,14 +467,14 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Branches.FastForward),
Handler: gui.handleFastForward,
- Description: gui.Tr.FastForward,
+ Description: gui.c.Tr.FastForward,
},
{
ViewName: "branches",
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.ViewResetOptions),
Handler: gui.handleCreateResetToBranchMenu,
- Description: gui.Tr.LcViewResetOptions,
+ Description: gui.c.Tr.LcViewResetOptions,
OpensMenu: true,
},
{
@@ -617,78 +482,35 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Branches.RenameBranch),
Handler: gui.handleRenameBranch,
- Description: gui.Tr.LcRenameBranch,
+ Description: gui.c.Tr.LcRenameBranch,
},
{
ViewName: "branches",
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.CopyToClipboard),
Handler: gui.handleCopySelectedSideContextItemToClipboard,
- Description: gui.Tr.LcCopyBranchNameToClipboard,
+ Description: gui.c.Tr.LcCopyBranchNameToClipboard,
},
{
ViewName: "branches",
Contexts: []string{string(LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.GoInto),
Handler: gui.handleSwitchToSubCommits,
- Description: gui.Tr.LcViewCommits,
- },
- {
- ViewName: "branches",
- Contexts: []string{string(TAGS_CONTEXT_KEY)},
- Key: gui.getKey(config.Universal.Select),
- Handler: gui.withSelectedTag(gui.handleCheckoutTag),
- Description: gui.Tr.LcCheckout,
- },
- {
- ViewName: "branches",
- Contexts: []string{string(TAGS_CONTEXT_KEY)},
- Key: gui.getKey(config.Universal.Remove),
- Handler: gui.withSelectedTag(gui.handleDeleteTag),
- Description: gui.Tr.LcDeleteTag,
- },
- {
- ViewName: "branches",
- Contexts: []string{string(TAGS_CONTEXT_KEY)},
- Key: gui.getKey(config.Branches.PushTag),
- Handler: gui.withSelectedTag(gui.handlePushTag),
- Description: gui.Tr.LcPushTag,
- },
- {
- ViewName: "branches",
- Contexts: []string{string(TAGS_CONTEXT_KEY)},
- Key: gui.getKey(config.Universal.New),
- Handler: gui.handleCreateTag,
- Description: gui.Tr.LcCreateTag,
- },
- {
- ViewName: "branches",
- Contexts: []string{string(TAGS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.ViewResetOptions),
- Handler: gui.withSelectedTag(gui.handleCreateResetToTagMenu),
- Description: gui.Tr.LcViewResetOptions,
- OpensMenu: true,
- },
- {
- ViewName: "branches",
- Contexts: []string{string(TAGS_CONTEXT_KEY)},
- Key: gui.getKey(config.Universal.GoInto),
- Handler: gui.handleSwitchToSubCommits,
- Description: gui.Tr.LcViewCommits,
+ Description: gui.c.Tr.LcViewCommits,
},
{
ViewName: "branches",
Contexts: []string{string(REMOTE_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Return),
Handler: gui.handleRemoteBranchesEscape,
- Description: gui.Tr.ReturnToRemotesList,
+ Description: gui.c.Tr.ReturnToRemotesList,
},
{
ViewName: "branches",
Contexts: []string{string(REMOTE_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.ViewResetOptions),
Handler: gui.handleCreateResetToRemoteBranchMenu,
- Description: gui.Tr.LcViewResetOptions,
+ Description: gui.c.Tr.LcViewResetOptions,
OpensMenu: true,
},
{
@@ -696,162 +518,35 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(REMOTE_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.GoInto),
Handler: gui.handleSwitchToSubCommits,
- Description: gui.Tr.LcViewCommits,
- },
- {
- ViewName: "branches",
- Contexts: []string{string(REMOTES_CONTEXT_KEY)},
- Key: gui.getKey(config.Branches.FetchRemote),
- Handler: gui.handleFetchRemote,
- Description: gui.Tr.LcFetchRemote,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.OpenLogMenu),
- Handler: gui.handleOpenLogMenu,
- Description: gui.Tr.LcOpenLogMenu,
- OpensMenu: true,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.SquashDown),
- Handler: gui.handleCommitSquashDown,
- Description: gui.Tr.LcSquashDown,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.RenameCommit),
- Handler: gui.handleRewordCommit,
- Description: gui.Tr.LcRewordCommit,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.RenameCommitWithEditor),
- Handler: gui.handleRewordCommitEditor,
- Description: gui.Tr.LcRenameCommitEditor,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.ViewResetOptions),
- Handler: gui.handleCreateCommitResetMenu,
- Description: gui.Tr.LcResetToThisCommit,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.MarkCommitAsFixup),
- Handler: gui.handleCommitFixup,
- Description: gui.Tr.LcFixupCommit,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.CreateFixupCommit),
- Handler: gui.handleCreateFixupCommit,
- Description: gui.Tr.LcCreateFixupCommit,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.SquashAboveCommits),
- Handler: gui.handleSquashAllAboveFixupCommits,
- Description: gui.Tr.LcSquashAboveCommits,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Universal.Remove),
- Handler: gui.handleCommitDelete,
- Description: gui.Tr.LcDeleteCommit,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.MoveDownCommit),
- Handler: gui.handleCommitMoveDown,
- Description: gui.Tr.LcMoveDownCommit,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.MoveUpCommit),
- Handler: gui.handleCommitMoveUp,
- Description: gui.Tr.LcMoveUpCommit,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Universal.Edit),
- Handler: gui.handleCommitEdit,
- Description: gui.Tr.LcEditCommit,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.AmendToCommit),
- Handler: gui.handleCommitAmendTo,
- Description: gui.Tr.LcAmendToCommit,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.PickCommit),
- Handler: gui.handleCommitPick,
- Description: gui.Tr.LcPickCommit,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.RevertCommit),
- Handler: gui.handleCommitRevert,
- Description: gui.Tr.LcRevertCommit,
+ Description: gui.c.Tr.LcViewCommits,
},
{
ViewName: "commits",
Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopy),
Handler: gui.handleCopyCommit,
- Description: gui.Tr.LcCherryPickCopy,
+ Description: gui.c.Tr.LcCherryPickCopy,
},
{
ViewName: "commits",
Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.CopyToClipboard),
Handler: gui.handleCopySelectedSideContextItemToClipboard,
- Description: gui.Tr.LcCopyCommitShaToClipboard,
+ Description: gui.c.Tr.LcCopyCommitShaToClipboard,
},
{
ViewName: "commits",
Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopyRange),
Handler: gui.handleCopyCommitRange,
- Description: gui.Tr.LcCherryPickCopyRange,
+ Description: gui.c.Tr.LcCherryPickCopyRange,
},
{
ViewName: "commits",
Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.PasteCommits),
- Handler: gui.HandlePasteCommits,
- Description: gui.Tr.LcPasteCommits,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Universal.GoInto),
- Handler: gui.handleViewCommitFiles,
- Description: gui.Tr.LcViewCommitFiles,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.CheckoutCommit),
- Handler: gui.handleCheckoutCommit,
- Description: gui.Tr.LcCheckoutCommit,
+ Handler: guards.OutsideFilterMode(gui.HandlePasteCommits),
+ Description: gui.c.Tr.LcPasteCommits,
},
{
ViewName: "commits",
@@ -859,114 +554,85 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Key: gui.getKey(config.Universal.New),
Modifier: gocui.ModNone,
Handler: gui.handleNewBranchOffCurrentItem,
- Description: gui.Tr.LcCreateNewBranchFromCommit,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.TagCommit),
- Handler: gui.handleTagCommit,
- Description: gui.Tr.LcTagCommit,
+ Description: gui.c.Tr.LcCreateNewBranchFromCommit,
},
{
ViewName: "commits",
Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.ResetCherryPick),
Handler: gui.exitCherryPickingMode,
- Description: gui.Tr.LcResetCherryPick,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.CopyCommitMessageToClipboard),
- Handler: gui.handleCopySelectedCommitMessageToClipboard,
- Description: gui.Tr.LcCopyCommitMessageToClipboard,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.OpenInBrowser),
- Handler: gui.handleOpenCommitInBrowser,
- Description: gui.Tr.LcOpenCommitInBrowser,
- },
- {
- ViewName: "commits",
- Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
- Key: gui.getKey(config.Commits.ViewBisectOptions),
- Handler: gui.handleOpenBisectMenu,
- Description: gui.Tr.LcViewBisectOptions,
- OpensMenu: true,
+ Description: gui.c.Tr.LcResetCherryPick,
},
{
ViewName: "commits",
Contexts: []string{string(REFLOG_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.GoInto),
Handler: gui.handleViewReflogCommitFiles,
- Description: gui.Tr.LcViewCommitFiles,
+ Description: gui.c.Tr.LcViewCommitFiles,
},
{
ViewName: "commits",
Contexts: []string{string(REFLOG_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Select),
- Handler: gui.handleCheckoutReflogCommit,
- Description: gui.Tr.LcCheckoutCommit,
+ Handler: gui.CheckoutReflogCommit,
+ Description: gui.c.Tr.LcCheckoutCommit,
},
{
ViewName: "commits",
Contexts: []string{string(REFLOG_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.ViewResetOptions),
Handler: gui.handleCreateReflogResetMenu,
- Description: gui.Tr.LcViewResetOptions,
+ Description: gui.c.Tr.LcViewResetOptions,
OpensMenu: true,
},
{
ViewName: "commits",
Contexts: []string{string(REFLOG_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopy),
- Handler: gui.handleCopyCommit,
- Description: gui.Tr.LcCherryPickCopy,
+ Handler: guards.OutsideFilterMode(gui.handleCopyCommit),
+ Description: gui.c.Tr.LcCherryPickCopy,
},
{
ViewName: "commits",
Contexts: []string{string(REFLOG_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopyRange),
- Handler: gui.handleCopyCommitRange,
- Description: gui.Tr.LcCherryPickCopyRange,
+ Handler: guards.OutsideFilterMode(gui.handleCopyCommitRange),
+ Description: gui.c.Tr.LcCherryPickCopyRange,
},
{
ViewName: "commits",
Contexts: []string{string(REFLOG_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.ResetCherryPick),
Handler: gui.exitCherryPickingMode,
- Description: gui.Tr.LcResetCherryPick,
+ Description: gui.c.Tr.LcResetCherryPick,
},
{
ViewName: "commits",
Contexts: []string{string(REFLOG_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.CopyToClipboard),
Handler: gui.handleCopySelectedSideContextItemToClipboard,
- Description: gui.Tr.LcCopyCommitShaToClipboard,
+ Description: gui.c.Tr.LcCopyCommitShaToClipboard,
},
{
ViewName: "branches",
Contexts: []string{string(SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.GoInto),
Handler: gui.handleViewSubCommitFiles,
- Description: gui.Tr.LcViewCommitFiles,
+ Description: gui.c.Tr.LcViewCommitFiles,
},
{
ViewName: "branches",
Contexts: []string{string(SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Select),
Handler: gui.handleCheckoutSubCommit,
- Description: gui.Tr.LcCheckoutCommit,
+ Description: gui.c.Tr.LcCheckoutCommit,
},
{
ViewName: "branches",
Contexts: []string{string(SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.ViewResetOptions),
Handler: gui.handleCreateSubCommitResetMenu,
- Description: gui.Tr.LcViewResetOptions,
+ Description: gui.c.Tr.LcViewResetOptions,
OpensMenu: true,
},
{
@@ -974,65 +640,65 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.New),
Handler: gui.handleNewBranchOffCurrentItem,
- Description: gui.Tr.LcNewBranch,
+ Description: gui.c.Tr.LcNewBranch,
},
{
ViewName: "branches",
Contexts: []string{string(SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopy),
Handler: gui.handleCopyCommit,
- Description: gui.Tr.LcCherryPickCopy,
+ Description: gui.c.Tr.LcCherryPickCopy,
},
{
ViewName: "branches",
Contexts: []string{string(SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopyRange),
Handler: gui.handleCopyCommitRange,
- Description: gui.Tr.LcCherryPickCopyRange,
+ Description: gui.c.Tr.LcCherryPickCopyRange,
},
{
ViewName: "branches",
Contexts: []string{string(SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.ResetCherryPick),
Handler: gui.exitCherryPickingMode,
- Description: gui.Tr.LcResetCherryPick,
+ Description: gui.c.Tr.LcResetCherryPick,
},
{
ViewName: "branches",
Contexts: []string{string(SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.CopyToClipboard),
Handler: gui.handleCopySelectedSideContextItemToClipboard,
- Description: gui.Tr.LcCopyCommitShaToClipboard,
+ Description: gui.c.Tr.LcCopyCommitShaToClipboard,
},
{
ViewName: "stash",
Key: gui.getKey(config.Universal.GoInto),
Handler: gui.handleViewStashFiles,
- Description: gui.Tr.LcViewStashFiles,
+ Description: gui.c.Tr.LcViewStashFiles,
},
{
ViewName: "stash",
Key: gui.getKey(config.Universal.Select),
Handler: gui.handleStashApply,
- Description: gui.Tr.LcApply,
+ Description: gui.c.Tr.LcApply,
},
{
ViewName: "stash",
Key: gui.getKey(config.Stash.PopStash),
Handler: gui.handleStashPop,
- Description: gui.Tr.LcPop,
+ Description: gui.c.Tr.LcPop,
},
{
ViewName: "stash",
Key: gui.getKey(config.Universal.Remove),
Handler: gui.handleStashDrop,
- Description: gui.Tr.LcDrop,
+ Description: gui.c.Tr.LcDrop,
},
{
ViewName: "stash",
Key: gui.getKey(config.Universal.New),
Handler: gui.handleNewBranchOffCurrentItem,
- Description: gui.Tr.LcNewBranch,
+ Description: gui.c.Tr.LcNewBranch,
},
{
ViewName: "commitMessage",
@@ -1062,7 +728,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
ViewName: "menu",
Key: gui.getKey(config.Universal.Return),
Handler: gui.handleMenuClose,
- Description: gui.Tr.LcCloseMenu,
+ Description: gui.c.Tr.LcCloseMenu,
},
{
ViewName: "information",
@@ -1074,76 +740,76 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
ViewName: "commitFiles",
Key: gui.getKey(config.Universal.CopyToClipboard),
Handler: gui.handleCopySelectedSideContextItemToClipboard,
- Description: gui.Tr.LcCopyCommitFileNameToClipboard,
+ Description: gui.c.Tr.LcCopyCommitFileNameToClipboard,
},
{
ViewName: "commitFiles",
Key: gui.getKey(config.CommitFiles.CheckoutCommitFile),
Handler: gui.handleCheckoutCommitFile,
- Description: gui.Tr.LcCheckoutCommitFile,
+ Description: gui.c.Tr.LcCheckoutCommitFile,
},
{
ViewName: "commitFiles",
Key: gui.getKey(config.Universal.Remove),
Handler: gui.handleDiscardOldFileChange,
- Description: gui.Tr.LcDiscardOldFileChange,
+ Description: gui.c.Tr.LcDiscardOldFileChange,
},
{
ViewName: "commitFiles",
Key: gui.getKey(config.Universal.OpenFile),
Handler: gui.handleOpenOldCommitFile,
- Description: gui.Tr.LcOpenFile,
+ Description: gui.c.Tr.LcOpenFile,
},
{
ViewName: "commitFiles",
Key: gui.getKey(config.Universal.Edit),
Handler: gui.handleEditCommitFile,
- Description: gui.Tr.LcEditFile,
+ Description: gui.c.Tr.LcEditFile,
},
{
ViewName: "commitFiles",
Key: gui.getKey(config.Universal.Select),
Handler: gui.handleToggleFileForPatch,
- Description: gui.Tr.LcToggleAddToPatch,
+ Description: gui.c.Tr.LcToggleAddToPatch,
},
{
ViewName: "commitFiles",
Key: gui.getKey(config.Universal.GoInto),
Handler: gui.handleEnterCommitFile,
- Description: gui.Tr.LcEnterFile,
+ Description: gui.c.Tr.LcEnterFile,
},
{
ViewName: "commitFiles",
Key: gui.getKey(config.Files.ToggleTreeView),
Handler: gui.handleToggleCommitFileTreeView,
- Description: gui.Tr.LcToggleTreeView,
+ Description: gui.c.Tr.LcToggleTreeView,
},
{
ViewName: "",
Key: gui.getKey(config.Universal.FilteringMenu),
Handler: gui.handleCreateFilteringMenuPanel,
- Description: gui.Tr.LcOpenFilteringMenu,
+ Description: gui.c.Tr.LcOpenFilteringMenu,
OpensMenu: true,
},
{
ViewName: "",
Key: gui.getKey(config.Universal.DiffingMenu),
Handler: gui.handleCreateDiffingMenuPanel,
- Description: gui.Tr.LcOpenDiffingMenu,
+ Description: gui.c.Tr.LcOpenDiffingMenu,
OpensMenu: true,
},
{
ViewName: "",
Key: gui.getKey(config.Universal.DiffingMenuAlt),
Handler: gui.handleCreateDiffingMenuPanel,
- Description: gui.Tr.LcOpenDiffingMenu,
+ Description: gui.c.Tr.LcOpenDiffingMenu,
OpensMenu: true,
},
{
ViewName: "",
Key: gui.getKey(config.Universal.ExtrasMenu),
Handler: gui.handleCreateExtrasMenuPanel,
- Description: gui.Tr.LcOpenExtrasMenu,
+ Description: gui.c.Tr.LcOpenExtrasMenu,
OpensMenu: true,
},
{
@@ -1170,7 +836,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(MAIN_NORMAL_CONTEXT_KEY)},
Key: gocui.MouseWheelDown,
Handler: gui.scrollDownMain,
- Description: gui.Tr.ScrollDown,
+ Description: gui.c.Tr.ScrollDown,
Alternative: "fn+up",
},
{
@@ -1178,7 +844,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(MAIN_NORMAL_CONTEXT_KEY)},
Key: gocui.MouseWheelUp,
Handler: gui.scrollUpMain,
- Description: gui.Tr.ScrollUp,
+ Description: gui.c.Tr.ScrollUp,
Alternative: "fn+down",
},
{
@@ -1200,56 +866,56 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Return),
Handler: gui.handleStagingEscape,
- Description: gui.Tr.ReturnToFilesPanel,
+ Description: gui.c.Tr.ReturnToFilesPanel,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Select),
Handler: gui.handleToggleStagedSelection,
- Description: gui.Tr.StageSelection,
+ Description: gui.c.Tr.StageSelection,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Remove),
Handler: gui.handleResetSelection,
- Description: gui.Tr.ResetSelection,
+ Description: gui.c.Tr.ResetSelection,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.TogglePanel),
Handler: gui.handleTogglePanel,
- Description: gui.Tr.TogglePanel,
+ Description: gui.c.Tr.TogglePanel,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_PATCH_BUILDING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Return),
Handler: gui.handleEscapePatchBuildingPanel,
- Description: gui.Tr.ExitLineByLineMode,
+ Description: gui.c.Tr.ExitLineByLineMode,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_PATCH_BUILDING_CONTEXT_KEY), string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.OpenFile),
Handler: gui.handleOpenFileAtLine,
- Description: gui.Tr.LcOpenFile,
+ Description: gui.c.Tr.LcOpenFile,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_PATCH_BUILDING_CONTEXT_KEY), string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.PrevItem),
Handler: gui.handleSelectPrevLine,
- Description: gui.Tr.PrevLine,
+ Description: gui.c.Tr.PrevLine,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_PATCH_BUILDING_CONTEXT_KEY), string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.NextItem),
Handler: gui.handleSelectNextLine,
- Description: gui.Tr.NextLine,
+ Description: gui.c.Tr.NextLine,
},
{
ViewName: "main",
@@ -1284,7 +950,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(MAIN_PATCH_BUILDING_CONTEXT_KEY), string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.PrevBlock),
Handler: gui.handleSelectPrevHunk,
- Description: gui.Tr.PrevHunk,
+ Description: gui.c.Tr.PrevHunk,
},
{
ViewName: "main",
@@ -1298,7 +964,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(MAIN_PATCH_BUILDING_CONTEXT_KEY), string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.NextBlock),
Handler: gui.handleSelectNextHunk,
- Description: gui.Tr.NextHunk,
+ Description: gui.c.Tr.NextHunk,
},
{
ViewName: "main",
@@ -1313,21 +979,21 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Key: gui.getKey(config.Universal.CopyToClipboard),
Modifier: gocui.ModNone,
Handler: gui.copySelectedToClipboard,
- Description: gui.Tr.LcCopySelectedTexToClipboard,
+ Description: gui.c.Tr.LcCopySelectedTexToClipboard,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Edit),
Handler: gui.handleLineByLineEdit,
- Description: gui.Tr.LcEditFile,
+ Description: gui.c.Tr.LcEditFile,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.OpenFile),
- Handler: gui.handleFileOpen,
- Description: gui.Tr.LcOpenFile,
+ Handler: gui.Controllers.Files.Open,
+ Description: gui.c.Tr.LcOpenFile,
},
{
ViewName: "main",
@@ -1335,7 +1001,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Key: gui.getKey(config.Universal.NextPage),
Modifier: gocui.ModNone,
Handler: gui.handleLineByLineNextPage,
- Description: gui.Tr.LcNextPage,
+ Description: gui.c.Tr.LcNextPage,
Tag: "navigation",
},
{
@@ -1344,7 +1010,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Key: gui.getKey(config.Universal.PrevPage),
Modifier: gocui.ModNone,
Handler: gui.handleLineByLinePrevPage,
- Description: gui.Tr.LcPrevPage,
+ Description: gui.c.Tr.LcPrevPage,
Tag: "navigation",
},
{
@@ -1353,7 +1019,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Key: gui.getKey(config.Universal.GotoTop),
Modifier: gocui.ModNone,
Handler: gui.handleLineByLineGotoTop,
- Description: gui.Tr.LcGotoTop,
+ Description: gui.c.Tr.LcGotoTop,
Tag: "navigation",
},
{
@@ -1362,7 +1028,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Key: gui.getKey(config.Universal.GotoBottom),
Modifier: gocui.ModNone,
Handler: gui.handleLineByLineGotoBottom,
- Description: gui.Tr.LcGotoBottom,
+ Description: gui.c.Tr.LcGotoBottom,
Tag: "navigation",
},
{
@@ -1370,7 +1036,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(MAIN_PATCH_BUILDING_CONTEXT_KEY), string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.StartSearch),
Handler: func() error { return gui.handleOpenSearch("main") },
- Description: gui.Tr.LcStartSearch,
+ Description: gui.c.Tr.LcStartSearch,
Tag: "navigation",
},
{
@@ -1378,14 +1044,14 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(MAIN_PATCH_BUILDING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Select),
Handler: gui.handleToggleSelectionForPatch,
- Description: gui.Tr.ToggleSelectionForPatch,
+ Description: gui.c.Tr.ToggleSelectionForPatch,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_PATCH_BUILDING_CONTEXT_KEY), string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Main.ToggleDragSelect),
Handler: gui.handleToggleSelectRange,
- Description: gui.Tr.ToggleDragSelect,
+ Description: gui.c.Tr.ToggleDragSelect,
},
// Alias 'V' -> 'v'
{
@@ -1393,14 +1059,14 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(MAIN_PATCH_BUILDING_CONTEXT_KEY), string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Main.ToggleDragSelectAlt),
Handler: gui.handleToggleSelectRange,
- Description: gui.Tr.ToggleDragSelect,
+ Description: gui.c.Tr.ToggleDragSelect,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_PATCH_BUILDING_CONTEXT_KEY), string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Main.ToggleSelectHunk),
Handler: gui.handleToggleSelectHunk,
- Description: gui.Tr.ToggleSelectHunk,
+ Description: gui.c.Tr.ToggleSelectHunk,
},
{
ViewName: "main",
@@ -1435,91 +1101,91 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(MAIN_PATCH_BUILDING_CONTEXT_KEY), string(MAIN_STAGING_CONTEXT_KEY), string(MAIN_MERGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.ScrollLeft),
Handler: gui.scrollLeftMain,
- Description: gui.Tr.LcScrollLeft,
+ Description: gui.c.Tr.LcScrollLeft,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_PATCH_BUILDING_CONTEXT_KEY), string(MAIN_STAGING_CONTEXT_KEY), string(MAIN_MERGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.ScrollRight),
Handler: gui.scrollRightMain,
- Description: gui.Tr.LcScrollRight,
+ Description: gui.c.Tr.LcScrollRight,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Files.CommitChanges),
- Handler: gui.handleCommitPress,
- Description: gui.Tr.CommitChanges,
+ Handler: gui.Controllers.Files.HandleCommitPress,
+ Description: gui.c.Tr.CommitChanges,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Files.CommitChangesWithoutHook),
- Handler: gui.handleWIPCommitPress,
- Description: gui.Tr.LcCommitChangesWithoutHook,
+ Handler: gui.Controllers.Files.HandleWIPCommitPress,
+ Description: gui.c.Tr.LcCommitChangesWithoutHook,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_STAGING_CONTEXT_KEY)},
Key: gui.getKey(config.Files.CommitChangesWithEditor),
- Handler: gui.handleCommitEditorPress,
- Description: gui.Tr.CommitChangesWithEditor,
+ Handler: gui.Controllers.Files.HandleCommitEditorPress,
+ Description: gui.c.Tr.CommitChangesWithEditor,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Return),
Handler: gui.handleEscapeMerge,
- Description: gui.Tr.ReturnToFilesPanel,
+ Description: gui.c.Tr.ReturnToFilesPanel,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
Key: gui.getKey(config.Files.OpenMergeTool),
- Handler: gui.handleOpenMergeTool,
- Description: gui.Tr.LcOpenMergeTool,
+ Handler: gui.Controllers.Files.OpenMergeTool,
+ Description: gui.c.Tr.LcOpenMergeTool,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Select),
Handler: gui.handlePickHunk,
- Description: gui.Tr.PickHunk,
+ Description: gui.c.Tr.PickHunk,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
Key: gui.getKey(config.Main.PickBothHunks),
Handler: gui.handlePickAllHunks,
- Description: gui.Tr.PickAllHunks,
+ Description: gui.c.Tr.PickAllHunks,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.PrevBlock),
Handler: gui.handleSelectPrevConflict,
- Description: gui.Tr.PrevConflict,
+ Description: gui.c.Tr.PrevConflict,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.NextBlock),
Handler: gui.handleSelectNextConflict,
- Description: gui.Tr.NextConflict,
+ Description: gui.c.Tr.NextConflict,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.PrevItem),
Handler: gui.handleSelectPrevConflictHunk,
- Description: gui.Tr.SelectPrevHunk,
+ Description: gui.c.Tr.SelectPrevHunk,
},
{
ViewName: "main",
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.NextItem),
Handler: gui.handleSelectNextConflictHunk,
- Description: gui.Tr.SelectNextHunk,
+ Description: gui.c.Tr.SelectNextHunk,
},
{
ViewName: "main",
@@ -1554,35 +1220,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Undo),
Handler: gui.handleMergeConflictUndo,
- Description: gui.Tr.LcUndo,
- },
- {
- ViewName: "branches",
- Contexts: []string{string(REMOTES_CONTEXT_KEY)},
- Key: gui.getKey(config.Universal.GoInto),
- Modifier: gocui.ModNone,
- Handler: gui.handleRemoteEnter,
- },
- {
- ViewName: "branches",
- Contexts: []string{string(REMOTES_CONTEXT_KEY)},
- Key: gui.getKey(config.Universal.New),
- Handler: gui.handleAddRemote,
- Description: gui.Tr.LcAddNewRemote,
- },
- {
- ViewName: "branches",
- Contexts: []string{string(REMOTES_CONTEXT_KEY)},
- Key: gui.getKey(config.Universal.Remove),
- Handler: gui.handleRemoveRemote,
- Description: gui.Tr.LcRemoveRemote,
- },
- {
- ViewName: "branches",
- Contexts: []string{string(REMOTES_CONTEXT_KEY)},
- Key: gui.getKey(config.Universal.Edit),
- Handler: gui.handleEditRemote,
- Description: gui.Tr.LcEditRemote,
+ Description: gui.c.Tr.LcUndo,
},
{
ViewName: "branches",
@@ -1590,42 +1228,42 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Key: gui.getKey(config.Universal.Select),
// gonna use the exact same handler as the 'n' keybinding because everybody wants this to happen when they checkout a remote branch
Handler: gui.handleNewBranchOffCurrentItem,
- Description: gui.Tr.LcCheckout,
+ Description: gui.c.Tr.LcCheckout,
},
{
ViewName: "branches",
Contexts: []string{string(REMOTE_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.New),
Handler: gui.handleNewBranchOffCurrentItem,
- Description: gui.Tr.LcNewBranch,
+ Description: gui.c.Tr.LcNewBranch,
},
{
ViewName: "branches",
Contexts: []string{string(REMOTE_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Branches.MergeIntoCurrentBranch),
- Handler: gui.handleMergeRemoteBranch,
- Description: gui.Tr.LcMergeIntoCurrentBranch,
+ Handler: guards.OutsideFilterMode(gui.handleMergeRemoteBranch),
+ Description: gui.c.Tr.LcMergeIntoCurrentBranch,
},
{
ViewName: "branches",
Contexts: []string{string(REMOTE_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Remove),
Handler: gui.handleDeleteRemoteBranch,
- Description: gui.Tr.LcDeleteBranch,
+ Description: gui.c.Tr.LcDeleteBranch,
},
{
ViewName: "branches",
Contexts: []string{string(REMOTE_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Branches.RebaseBranch),
- Handler: gui.handleRebaseOntoRemoteBranch,
- Description: gui.Tr.LcRebaseBranch,
+ Handler: guards.OutsideFilterMode(gui.handleRebaseOntoRemoteBranch),
+ Description: gui.c.Tr.LcRebaseBranch,
},
{
ViewName: "branches",
Contexts: []string{string(REMOTE_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Branches.SetUpstream),
Handler: gui.handleSetBranchUpstream,
- Description: gui.Tr.LcSetUpstream,
+ Description: gui.c.Tr.LcSetUpstream,
},
{
ViewName: "status",
@@ -1669,49 +1307,31 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Modifier: gocui.ModNone,
Handler: gui.scrollDownConfirmationPanel,
},
- {
- ViewName: "menu",
- Key: gui.getKey(config.Universal.Select),
- Modifier: gocui.ModNone,
- Handler: gui.onMenuPress,
- },
- {
- ViewName: "menu",
- Key: gui.getKey(config.Universal.Confirm),
- Modifier: gocui.ModNone,
- Handler: gui.onMenuPress,
- },
- {
- ViewName: "menu",
- Key: gui.getKey(config.Universal.ConfirmAlt1),
- Modifier: gocui.ModNone,
- Handler: gui.onMenuPress,
- },
{
ViewName: "files",
Contexts: []string{string(SUBMODULES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.CopyToClipboard),
Handler: gui.handleCopySelectedSideContextItemToClipboard,
- Description: gui.Tr.LcCopySubmoduleNameToClipboard,
+ Description: gui.c.Tr.LcCopySubmoduleNameToClipboard,
},
{
ViewName: "files",
Contexts: []string{string(FILES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.ToggleWhitespaceInDiffView),
Handler: gui.toggleWhitespaceInDiffView,
- Description: gui.Tr.ToggleWhitespaceInDiffView,
+ Description: gui.c.Tr.ToggleWhitespaceInDiffView,
},
{
ViewName: "",
Key: gui.getKey(config.Universal.IncreaseContextInDiffView),
Handler: gui.IncreaseContextInDiffView,
- Description: gui.Tr.IncreaseContextInDiffView,
+ Description: gui.c.Tr.IncreaseContextInDiffView,
},
{
ViewName: "",
Key: gui.getKey(config.Universal.DecreaseContextInDiffView),
Handler: gui.DecreaseContextInDiffView,
- Description: gui.Tr.DecreaseContextInDiffView,
+ Description: gui.c.Tr.DecreaseContextInDiffView,
},
{
ViewName: "extras",
@@ -1727,7 +1347,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
ViewName: "extras",
Key: gui.getKey(config.Universal.ExtrasMenu),
Handler: gui.handleCreateExtrasMenuPanel,
- Description: gui.Tr.LcOpenExtrasMenu,
+ Description: gui.c.Tr.LcOpenExtrasMenu,
OpensMenu: true,
},
{
@@ -1771,22 +1391,49 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
},
}
- type ContextKeybindings struct {
- contextKey ContextKey
- viewName string
- bindings []*types.Binding
+ for _, controller := range []types.IController{
+ gui.Controllers.LocalCommits,
+ gui.Controllers.Submodules,
+ gui.Controllers.Files,
+ gui.Controllers.Remotes,
+ gui.Controllers.Menu,
+ gui.Controllers.Bisect,
+ gui.Controllers.Undo,
+ gui.Controllers.Sync,
+ } {
+ context := controller.Context()
+ viewName := ""
+ var contextKeys []string
+ // nil context means global keybinding
+ if context != nil {
+ viewName = context.GetViewName()
+ contextKeys = []string{string(context.GetKey())}
+ }
+
+ for _, binding := range controller.Keybindings(gui.getKey, config, guards) {
+ binding.Contexts = contextKeys
+ binding.ViewName = viewName
+ bindings = append(bindings, binding)
+ }
}
- for _, contextKeybindings := range []ContextKeybindings{
- {
- contextKey: SUBMODULES_CONTEXT_KEY,
- viewName: "files",
- bindings: gui.Controllers.Submodules.Keybindings(gui.getKey, config),
- },
+ // while migrating we'll continue providing keybindings from the list contexts themselves.
+ // for each controller we add above we need to remove the corresponding list context from here.
+ for _, listContext := range []types.IListContext{
+ gui.State.Contexts.Branches,
+ gui.State.Contexts.RemoteBranches,
+ gui.State.Contexts.Tags,
+ gui.State.Contexts.ReflogCommits,
+ gui.State.Contexts.SubCommits,
+ gui.State.Contexts.Stash,
+ gui.State.Contexts.CommitFiles,
+ gui.State.Contexts.Suggestions,
} {
- for _, binding := range contextKeybindings.bindings {
- binding.Contexts = []string{string(contextKeybindings.contextKey)}
- binding.ViewName = contextKeybindings.viewName
+ viewName := listContext.GetViewName()
+ contextKey := listContext.GetKey()
+ for _, binding := range listContext.Keybindings(gui.getKey, config, guards) {
+ binding.Contexts = []string{string(contextKey)}
+ binding.ViewName = viewName
bindings = append(bindings, binding)
}
}
@@ -1817,27 +1464,25 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
}
}
- for viewName := range gui.State.Contexts.initialViewTabContextMap() {
+ for viewName := range gui.State.Contexts.InitialViewTabContextMap() {
bindings = append(bindings, []*types.Binding{
{
ViewName: viewName,
Key: gui.getKey(config.Universal.NextTab),
Handler: gui.handleNextTab,
- Description: gui.Tr.LcNextTab,
+ Description: gui.c.Tr.LcNextTab,
Tag: "navigation",
},
{
ViewName: viewName,
Key: gui.getKey(config.Universal.PrevTab),
Handler: gui.handlePrevTab,
- Description: gui.Tr.LcPrevTab,
+ Description: gui.c.Tr.LcPrevTab,
Tag: "navigation",
},
}...)
}
- bindings = append(bindings, gui.getListContextKeyBindings()...)
-
return bindings
}
@@ -1852,7 +1497,7 @@ func (gui *Gui) keybindings() error {
}
}
- for viewName := range gui.State.Contexts.initialViewTabContextMap() {
+ for viewName := range gui.State.Contexts.InitialViewTabContextMap() {
viewName := viewName
tabClickCallback := func(tabIndex int) error { return gui.onViewTabClick(viewName, tabIndex) }
diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go
index 3af9ea9af..aa2878250 100644
--- a/pkg/gui/layout.go
+++ b/pkg/gui/layout.go
@@ -2,6 +2,7 @@ package gui
import (
"github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
)
@@ -50,36 +51,36 @@ func (gui *Gui) createAllViews() error {
gui.Views.SearchPrefix.Frame = false
gui.setViewContent(gui.Views.SearchPrefix, SEARCH_PREFIX)
- gui.Views.Stash.Title = gui.Tr.StashTitle
+ gui.Views.Stash.Title = gui.c.Tr.StashTitle
gui.Views.Stash.FgColor = theme.GocuiDefaultTextColor
- gui.Views.Commits.Title = gui.Tr.CommitsTitle
+ gui.Views.Commits.Title = gui.c.Tr.CommitsTitle
gui.Views.Commits.FgColor = theme.GocuiDefaultTextColor
- gui.Views.CommitFiles.Title = gui.Tr.CommitFiles
+ gui.Views.CommitFiles.Title = gui.c.Tr.CommitFiles
gui.Views.CommitFiles.FgColor = theme.GocuiDefaultTextColor
- gui.Views.Branches.Title = gui.Tr.BranchesTitle
+ gui.Views.Branches.Title = gui.c.Tr.BranchesTitle
gui.Views.Branches.FgColor = theme.GocuiDefaultTextColor
gui.Views.Files.Highlight = true
- gui.Views.Files.Title = gui.Tr.FilesTitle
+ gui.Views.Files.Title = gui.c.Tr.FilesTitle
gui.Views.Files.FgColor = theme.GocuiDefaultTextColor
- gui.Views.Secondary.Title = gui.Tr.DiffTitle
+ gui.Views.Secondary.Title = gui.c.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.Title = gui.c.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.Title = gui.c.Tr.NotEnoughSpace
gui.Views.Limit.Wrap = true
- gui.Views.Status.Title = gui.Tr.StatusTitle
+ gui.Views.Status.Title = gui.c.Tr.StatusTitle
gui.Views.Status.FgColor = theme.GocuiDefaultTextColor
gui.Views.Search.BgColor = gocui.ColorDefault
@@ -93,7 +94,7 @@ func (gui *Gui) createAllViews() error {
gui.Views.AppStatus.Visible = false
gui.Views.CommitMessage.Visible = false
- gui.Views.CommitMessage.Title = gui.Tr.CommitMessage
+ gui.Views.CommitMessage.Title = gui.c.Tr.CommitMessage
gui.Views.CommitMessage.FgColor = theme.GocuiDefaultTextColor
gui.Views.CommitMessage.Editable = true
gui.Views.CommitMessage.Editor = gocui.EditorFunc(gui.commitMessageEditor)
@@ -101,7 +102,7 @@ func (gui *Gui) createAllViews() error {
gui.Views.Confirmation.Visible = false
gui.Views.Credentials.Visible = false
- gui.Views.Credentials.Title = gui.Tr.CredentialsUsername
+ gui.Views.Credentials.Title = gui.c.Tr.CredentialsUsername
gui.Views.Credentials.FgColor = theme.GocuiDefaultTextColor
gui.Views.Credentials.Editable = true
@@ -113,7 +114,7 @@ func (gui *Gui) createAllViews() error {
gui.Views.Information.FgColor = gocui.ColorGreen
gui.Views.Information.Frame = false
- gui.Views.Extras.Title = gui.Tr.CommandLog
+ gui.Views.Extras.Title = gui.c.Tr.CommandLog
gui.Views.Extras.FgColor = theme.GocuiDefaultTextColor
gui.Views.Extras.Autoscroll = true
gui.Views.Extras.Wrap = true
@@ -262,7 +263,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
// ignore contexts whose view is owned by another context right now
- if ContextKey(view.Context) != listContext.GetKey() {
+ if types.ContextKey(view.Context) != listContext.GetKey() {
continue
}
@@ -271,7 +272,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
view.SelBgColor = theme.GocuiSelectedLineBgColor
// I doubt this is expensive though it's admittedly redundant after the first render
- view.SetOnSelectItem(gui.onSelectItemWrapper(listContext.onSearchSelect))
+ view.SetOnSelectItem(gui.onSelectItemWrapper(listContext.OnSearchSelect))
}
gui.Views.Main.SetOnSelectItem(gui.onSelectItemWrapper(gui.handlelineByLineNavigateTo))
@@ -288,7 +289,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
// here is a good place log some stuff
// if you run `lazygit --logs`
// this will let you see these branches as prettified json
- // gui.Log.Info(utils.AsJson(gui.State.Branches[0:4]))
+ // gui.c.Log.Info(utils.AsJson(gui.State.Branches[0:4]))
return gui.resizeCurrentPopupPanel()
}
@@ -310,7 +311,7 @@ func (gui *Gui) onInitialViewsCreationForRepo() error {
}
initialContext := gui.currentSideContext()
- if err := gui.pushContext(initialContext); err != nil {
+ if err := gui.c.PushContext(initialContext); err != nil {
return err
}
@@ -372,9 +373,9 @@ func (gui *Gui) onInitialViewsCreation() error {
return err
}
- if !gui.UserConfig.DisableStartupPopups {
+ if !gui.c.UserConfig.DisableStartupPopups {
popupTasks := []func(chan struct{}) error{}
- storedPopupVersion := gui.Config.GetAppState().StartupPopupVersion
+ storedPopupVersion := gui.c.GetAppState().StartupPopupVersion
if storedPopupVersion < StartupPopupVersion {
popupTasks = append(popupTasks, gui.showIntroPopupMessage)
}
diff --git a/pkg/gui/line_by_line_panel.go b/pkg/gui/line_by_line_panel.go
index e851bc821..aec581a14 100644
--- a/pkg/gui/line_by_line_panel.go
+++ b/pkg/gui/line_by_line_panel.go
@@ -87,9 +87,9 @@ func (gui *Gui) copySelectedToClipboard() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
selected := state.PlainRenderSelected()
- gui.logAction(gui.Tr.Actions.CopySelectedTextToClipboard)
+ gui.c.LogAction(gui.c.Tr.Actions.CopySelectedTextToClipboard)
if err := gui.OSCommand.CopyToClipboard(selected); err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
return nil
@@ -141,7 +141,7 @@ func (gui *Gui) refreshMainViewForLineByLine(state *LblPanelState) error {
if gui.currentContext().GetKey() == gui.State.Contexts.PatchBuilding.GetKey() {
filename := gui.getSelectedCommitFileName()
var err error
- includedLineIndices, err = gui.Git.Patch.PatchManager.GetFileIncLineIndices(filename)
+ includedLineIndices, err = gui.git.Patch.PatchManager.GetFileIncLineIndices(filename)
if err != nil {
return err
}
@@ -285,5 +285,5 @@ func (gui *Gui) handleLineByLineEdit() error {
}
lineNumber := gui.State.Panels.LineByLine.CurrentLineNumber()
- return gui.editFileAtLine(file.Name, lineNumber)
+ return gui.fileHelper.EditFileAtLine(file.Name, lineNumber)
}
diff --git a/pkg/gui/list_context.go b/pkg/gui/list_context.go
index 9f0d86372..3d779cabd 100644
--- a/pkg/gui/list_context.go
+++ b/pkg/gui/list_context.go
@@ -4,19 +4,20 @@ import (
"fmt"
"github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/config"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
)
type ListContext struct {
- GetItemsLength func() int
- GetDisplayStrings func(startIdx int, length int) [][]string
- OnFocus func(...OnFocusOpts) error
- OnRenderToMain func(...OnFocusOpts) error
- OnFocusLost func() error
- OnClickSelectedItem func() error
+ GetItemsLength func() int
+ GetDisplayStrings func(startIdx int, length int) [][]string
+ OnFocus func(...types.OnFocusOpts) error
+ OnRenderToMain func(...types.OnFocusOpts) error
+ OnFocusLost func() error
// the boolean here tells us whether the item is nil. This is needed because you can't work it out on the calling end once the pointer is wrapped in an interface (unless you want to use reflection)
- SelectedItem func() (ListItem, bool)
- OnGetPanelState func() IListPanelState
+ SelectedItem func() (types.ListItem, bool)
+ OnGetPanelState func() types.IListPanelState
// if this is true, we'll call GetDisplayStrings for just the visible part of the
// view and re-render that. This is useful when you need to render different
// content based on the selection (e.g. for showing the selected commit)
@@ -27,45 +28,12 @@ type ListContext struct {
*BasicContext
}
-type IListContext interface {
- GetSelectedItem() (ListItem, bool)
- GetSelectedItemId() string
- handlePrevLine() error
- handleNextLine() error
- handleScrollLeft() error
- handleScrollRight() error
- handleLineChange(change int) error
- handleNextPage() error
- handleGotoTop() error
- handleGotoBottom() error
- handlePrevPage() error
- handleClick() error
- onSearchSelect(selectedLineIdx int) error
- FocusLine()
- HandleRenderToMain() error
+var _ types.IListContext = &ListContext{}
- GetPanelState() IListPanelState
-
- Context
-}
-
-func (self *ListContext) GetPanelState() IListPanelState {
+func (self *ListContext) GetPanelState() types.IListPanelState {
return self.OnGetPanelState()
}
-type IListPanelState interface {
- SetSelectedLineIdx(int)
- GetSelectedLineIdx() int
-}
-
-type ListItem interface {
- // ID is a SHA when the item is a commit, a filename when the item is a file, 'stash@{4}' when it's a stash entry, 'my_branch' when it's a branch
- ID() string
-
- // Description is something we would show in a message e.g. '123as14: push blah' for a commit
- Description() string
-}
-
func (self *ListContext) FocusLine() {
view, err := self.Gui.g.View(self.ViewName)
if err != nil {
@@ -87,7 +55,7 @@ func formatListFooter(selectedLineIdx int, length int) string {
return fmt.Sprintf("%d of %d", selectedLineIdx+1, length)
}
-func (self *ListContext) GetSelectedItem() (ListItem, bool) {
+func (self *ListContext) GetSelectedItem() (types.ListItem, bool) {
return self.SelectedItem()
}
@@ -132,7 +100,7 @@ func (self *ListContext) HandleFocusLost() error {
return nil
}
-func (self *ListContext) HandleFocus(opts ...OnFocusOpts) error {
+func (self *ListContext) HandleFocus(opts ...types.OnFocusOpts) error {
if self.Gui.popupPanelFocused() {
return nil
}
@@ -158,19 +126,19 @@ func (self *ListContext) HandleFocus(opts ...OnFocusOpts) error {
return nil
}
-func (self *ListContext) handlePrevLine() error {
+func (self *ListContext) HandlePrevLine() error {
return self.handleLineChange(-1)
}
-func (self *ListContext) handleNextLine() error {
+func (self *ListContext) HandleNextLine() error {
return self.handleLineChange(1)
}
-func (self *ListContext) handleScrollLeft() error {
+func (self *ListContext) HandleScrollLeft() error {
return self.scroll(self.Gui.scrollLeft)
}
-func (self *ListContext) handleScrollRight() error {
+func (self *ListContext) HandleScrollRight() error {
return self.scroll(self.Gui.scrollRight)
}
@@ -209,7 +177,7 @@ func (self *ListContext) handleLineChange(change int) error {
return self.HandleFocus()
}
-func (self *ListContext) handleNextPage() error {
+func (self *ListContext) HandleNextPage() error {
view, err := self.Gui.g.View(self.ViewName)
if err != nil {
return nil
@@ -219,15 +187,15 @@ func (self *ListContext) handleNextPage() error {
return self.handleLineChange(delta)
}
-func (self *ListContext) handleGotoTop() error {
+func (self *ListContext) HandleGotoTop() error {
return self.handleLineChange(-self.GetItemsLength())
}
-func (self *ListContext) handleGotoBottom() error {
+func (self *ListContext) HandleGotoBottom() error {
return self.handleLineChange(self.GetItemsLength())
}
-func (self *ListContext) handlePrevPage() error {
+func (self *ListContext) HandlePrevPage() error {
view, err := self.Gui.g.View(self.ViewName)
if err != nil {
return nil
@@ -238,7 +206,7 @@ func (self *ListContext) handlePrevPage() error {
return self.handleLineChange(-delta)
}
-func (self *ListContext) handleClick() error {
+func (self *ListContext) HandleClick(onClick func() error) error {
if self.ignoreKeybinding() {
return nil
}
@@ -252,7 +220,7 @@ func (self *ListContext) handleClick() error {
newSelectedLineIdx := view.SelectedLineIdx()
// we need to focus the view
- if err := self.Gui.pushContext(self); err != nil {
+ if err := self.Gui.c.PushContext(self); err != nil {
return err
}
@@ -263,13 +231,13 @@ func (self *ListContext) handleClick() error {
self.GetPanelState().SetSelectedLineIdx(newSelectedLineIdx)
prevViewName := self.Gui.currentViewName()
- if prevSelectedLineIdx == newSelectedLineIdx && prevViewName == self.ViewName && self.OnClickSelectedItem != nil {
- return self.OnClickSelectedItem()
+ if prevSelectedLineIdx == newSelectedLineIdx && prevViewName == self.ViewName && onClick != nil {
+ return onClick()
}
return self.HandleFocus()
}
-func (self *ListContext) onSearchSelect(selectedLineIdx int) error {
+func (self *ListContext) OnSearchSelect(selectedLineIdx int) error {
self.GetPanelState().SetSelectedLineIdx(selectedLineIdx)
return self.HandleFocus()
}
@@ -281,3 +249,35 @@ func (self *ListContext) HandleRenderToMain() error {
return nil
}
+
+func (self *ListContext) Keybindings(
+ getKey func(key string) interface{},
+ config config.KeybindingConfig,
+ guards types.KeybindingGuards,
+) []*types.Binding {
+ return []*types.Binding{
+ {Tag: "navigation", Key: getKey(config.Universal.PrevItemAlt), Modifier: gocui.ModNone, Handler: self.HandlePrevLine},
+ {Tag: "navigation", Key: getKey(config.Universal.PrevItem), Modifier: gocui.ModNone, Handler: self.HandlePrevLine},
+ {Tag: "navigation", Key: gocui.MouseWheelUp, Modifier: gocui.ModNone, Handler: self.HandlePrevLine},
+ {Tag: "navigation", Key: getKey(config.Universal.NextItemAlt), Modifier: gocui.ModNone, Handler: self.HandleNextLine},
+ {Tag: "navigation", Key: getKey(config.Universal.NextItem), Modifier: gocui.ModNone, Handler: self.HandleNextLine},
+ {Tag: "navigation", Key: getKey(config.Universal.PrevPage), Modifier: gocui.ModNone, Handler: self.HandlePrevPage, Description: self.Gui.c.Tr.LcPrevPage},
+ {Tag: "navigation", Key: getKey(config.Universal.NextPage), Modifier: gocui.ModNone, Handler: self.HandleNextPage, Description: self.Gui.c.Tr.LcNextPage},
+ {Tag: "navigation", Key: getKey(config.Universal.GotoTop), Modifier: gocui.ModNone, Handler: self.HandleGotoTop, Description: self.Gui.c.Tr.LcGotoTop},
+ {Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: func() error { return self.HandleClick(nil) }},
+ {Tag: "navigation", Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: self.HandleNextLine},
+ {Tag: "navigation", Key: getKey(config.Universal.ScrollLeft), Modifier: gocui.ModNone, Handler: self.HandleScrollLeft},
+ {Tag: "navigation", Key: getKey(config.Universal.ScrollRight), Modifier: gocui.ModNone, Handler: self.HandleScrollRight},
+ {
+ Key: getKey(config.Universal.StartSearch),
+ Handler: func() error { return self.Gui.handleOpenSearch(self.GetViewName()) },
+ Description: self.Gui.c.Tr.LcStartSearch,
+ Tag: "navigation",
+ },
+ {
+ Key: getKey(config.Universal.GotoBottom),
+ Description: self.Gui.c.Tr.LcGotoBottom,
+ Tag: "navigation",
+ },
+ }
+}
diff --git a/pkg/gui/list_context_config.go b/pkg/gui/list_context_config.go
index c40cade2c..9b1ebdc49 100644
--- a/pkg/gui/list_context_config.go
+++ b/pkg/gui/list_context_config.go
@@ -3,44 +3,41 @@ package gui
import (
"log"
- "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
-func (gui *Gui) menuListContext() IListContext {
+func (gui *Gui) menuListContext() types.IListContext {
return &ListContext{
BasicContext: &BasicContext{
ViewName: "menu",
Key: "menu",
- Kind: PERSISTENT_POPUP,
+ Kind: types.PERSISTENT_POPUP,
OnGetOptionsMap: gui.getMenuOptions,
},
- GetItemsLength: func() int { return gui.Views.Menu.LinesHeight() },
- OnGetPanelState: func() IListPanelState { return gui.State.Panels.Menu },
- OnClickSelectedItem: gui.onMenuPress,
- Gui: gui,
+ GetItemsLength: func() int { return gui.Views.Menu.LinesHeight() },
+ OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Menu },
+ Gui: gui,
// no GetDisplayStrings field because we do a custom render on menu creation
}
}
-func (gui *Gui) filesListContext() IListContext {
+func (gui *Gui) filesListContext() types.IListContext {
return &ListContext{
BasicContext: &BasicContext{
ViewName: "files",
WindowName: "files",
Key: FILES_CONTEXT_KEY,
- Kind: SIDE_CONTEXT,
+ Kind: types.SIDE_CONTEXT,
},
- GetItemsLength: func() int { return gui.State.FileTreeViewModel.GetItemsLength() },
- OnGetPanelState: func() IListPanelState { return gui.State.Panels.Files },
- OnFocus: OnFocusWrapper(gui.onFocusFile),
- OnRenderToMain: OnFocusWrapper(gui.filesRenderToMain),
- OnClickSelectedItem: gui.handleFilePress,
- Gui: gui,
+ GetItemsLength: func() int { return gui.State.FileTreeViewModel.GetItemsLength() },
+ OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Files },
+ OnFocus: OnFocusWrapper(gui.onFocusFile),
+ OnRenderToMain: OnFocusWrapper(gui.filesRenderToMain),
+ Gui: gui,
GetDisplayStrings: func(startIdx int, length int) [][]string {
lines := presentation.RenderFileTree(gui.State.FileTreeViewModel, gui.State.Modes.Diffing.Ref, gui.State.Submodules)
mappedLines := make([][]string, len(lines))
@@ -50,117 +47,115 @@ func (gui *Gui) filesListContext() IListContext {
return mappedLines
},
- SelectedItem: func() (ListItem, bool) {
+ SelectedItem: func() (types.ListItem, bool) {
item := gui.getSelectedFileNode()
return item, item != nil
},
}
}
-func (gui *Gui) branchesListContext() IListContext {
+func (gui *Gui) branchesListContext() types.IListContext {
return &ListContext{
BasicContext: &BasicContext{
ViewName: "branches",
WindowName: "branches",
Key: LOCAL_BRANCHES_CONTEXT_KEY,
- Kind: SIDE_CONTEXT,
+ Kind: types.SIDE_CONTEXT,
},
GetItemsLength: func() int { return len(gui.State.Branches) },
- OnGetPanelState: func() IListPanelState { return gui.State.Panels.Branches },
+ OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Branches },
OnRenderToMain: OnFocusWrapper(gui.branchesRenderToMain),
Gui: gui,
GetDisplayStrings: func(startIdx int, length int) [][]string {
return presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.State.ScreenMode != SCREEN_NORMAL, gui.State.Modes.Diffing.Ref)
},
- SelectedItem: func() (ListItem, bool) {
+ SelectedItem: func() (types.ListItem, bool) {
item := gui.getSelectedBranch()
return item, item != nil
},
}
}
-func (gui *Gui) remotesListContext() IListContext {
+func (gui *Gui) remotesListContext() types.IListContext {
return &ListContext{
BasicContext: &BasicContext{
ViewName: "branches",
WindowName: "branches",
Key: REMOTES_CONTEXT_KEY,
- Kind: SIDE_CONTEXT,
+ Kind: types.SIDE_CONTEXT,
},
- GetItemsLength: func() int { return len(gui.State.Remotes) },
- OnGetPanelState: func() IListPanelState { return gui.State.Panels.Remotes },
- OnRenderToMain: OnFocusWrapper(gui.remotesRenderToMain),
- OnClickSelectedItem: gui.handleRemoteEnter,
- Gui: gui,
+ GetItemsLength: func() int { return len(gui.State.Remotes) },
+ OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Remotes },
+ OnRenderToMain: OnFocusWrapper(gui.remotesRenderToMain),
+ Gui: gui,
GetDisplayStrings: func(startIdx int, length int) [][]string {
return presentation.GetRemoteListDisplayStrings(gui.State.Remotes, gui.State.Modes.Diffing.Ref)
},
- SelectedItem: func() (ListItem, bool) {
+ SelectedItem: func() (types.ListItem, bool) {
item := gui.getSelectedRemote()
return item, item != nil
},
}
}
-func (gui *Gui) remoteBranchesListContext() IListContext {
+func (gui *Gui) remoteBranchesListContext() types.IListContext {
return &ListContext{
BasicContext: &BasicContext{
ViewName: "branches",
WindowName: "branches",
Key: REMOTE_BRANCHES_CONTEXT_KEY,
- Kind: SIDE_CONTEXT,
+ Kind: types.SIDE_CONTEXT,
},
GetItemsLength: func() int { return len(gui.State.RemoteBranches) },
- OnGetPanelState: func() IListPanelState { return gui.State.Panels.RemoteBranches },
+ OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.RemoteBranches },
OnRenderToMain: OnFocusWrapper(gui.remoteBranchesRenderToMain),
Gui: gui,
GetDisplayStrings: func(startIdx int, length int) [][]string {
return presentation.GetRemoteBranchListDisplayStrings(gui.State.RemoteBranches, gui.State.Modes.Diffing.Ref)
},
- SelectedItem: func() (ListItem, bool) {
+ SelectedItem: func() (types.ListItem, bool) {
item := gui.getSelectedRemoteBranch()
return item, item != nil
},
}
}
-func (gui *Gui) tagsListContext() IListContext {
+func (gui *Gui) tagsListContext() types.IListContext {
return &ListContext{
BasicContext: &BasicContext{
ViewName: "branches",
WindowName: "branches",
Key: TAGS_CONTEXT_KEY,
- Kind: SIDE_CONTEXT,
+ Kind: types.SIDE_CONTEXT,
},
GetItemsLength: func() int { return len(gui.State.Tags) },
- OnGetPanelState: func() IListPanelState { return gui.State.Panels.Tags },
+ OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Tags },
OnRenderToMain: OnFocusWrapper(gui.tagsRenderToMain),
Gui: gui,
GetDisplayStrings: func(startIdx int, length int) [][]string {
return presentation.GetTagListDisplayStrings(gui.State.Tags, gui.State.Modes.Diffing.Ref)
},
- SelectedItem: func() (ListItem, bool) {
+ SelectedItem: func() (types.ListItem, bool) {
item := gui.getSelectedTag()
return item, item != nil
},
}
}
-func (gui *Gui) branchCommitsListContext() IListContext {
- parseEmoji := gui.UserConfig.Git.ParseEmoji
+func (gui *Gui) branchCommitsListContext() types.IListContext {
+ parseEmoji := gui.c.UserConfig.Git.ParseEmoji
return &ListContext{
BasicContext: &BasicContext{
ViewName: "commits",
WindowName: "commits",
Key: BRANCH_COMMITS_CONTEXT_KEY,
- Kind: SIDE_CONTEXT,
+ Kind: types.SIDE_CONTEXT,
},
- GetItemsLength: func() int { return len(gui.State.Commits) },
- OnGetPanelState: func() IListPanelState { return gui.State.Panels.Commits },
- OnFocus: OnFocusWrapper(gui.onCommitFocus),
- OnRenderToMain: OnFocusWrapper(gui.branchCommitsRenderToMain),
- OnClickSelectedItem: gui.handleViewCommitFiles,
- Gui: gui,
+ GetItemsLength: func() int { return len(gui.State.Commits) },
+ OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Commits },
+ OnFocus: OnFocusWrapper(gui.onCommitFocus),
+ OnRenderToMain: OnFocusWrapper(gui.branchCommitsRenderToMain),
+ Gui: gui,
GetDisplayStrings: func(startIdx int, length int) [][]string {
selectedCommitSha := ""
if gui.currentContext().GetKey() == BRANCH_COMMITS_CONTEXT_KEY {
@@ -182,7 +177,7 @@ func (gui *Gui) branchCommitsListContext() IListContext {
gui.State.BisectInfo,
)
},
- SelectedItem: func() (ListItem, bool) {
+ SelectedItem: func() (types.ListItem, bool) {
item := gui.getSelectedLocalCommit()
return item, item != nil
},
@@ -190,17 +185,17 @@ func (gui *Gui) branchCommitsListContext() IListContext {
}
}
-func (gui *Gui) subCommitsListContext() IListContext {
- parseEmoji := gui.UserConfig.Git.ParseEmoji
+func (gui *Gui) subCommitsListContext() types.IListContext {
+ parseEmoji := gui.c.UserConfig.Git.ParseEmoji
return &ListContext{
BasicContext: &BasicContext{
ViewName: "branches",
WindowName: "branches",
Key: SUB_COMMITS_CONTEXT_KEY,
- Kind: SIDE_CONTEXT,
+ Kind: types.SIDE_CONTEXT,
},
GetItemsLength: func() int { return len(gui.State.SubCommits) },
- OnGetPanelState: func() IListPanelState { return gui.State.Panels.SubCommits },
+ OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.SubCommits },
OnRenderToMain: OnFocusWrapper(gui.subCommitsRenderToMain),
Gui: gui,
GetDisplayStrings: func(startIdx int, length int) [][]string {
@@ -224,7 +219,7 @@ func (gui *Gui) subCommitsListContext() IListContext {
git_commands.NewNullBisectInfo(),
)
},
- SelectedItem: func() (ListItem, bool) {
+ SelectedItem: func() (types.ListItem, bool) {
item := gui.getSelectedSubCommit()
return item, item != nil
},
@@ -237,7 +232,7 @@ func (gui *Gui) shouldShowGraph() bool {
return false
}
- value := gui.UserConfig.Git.Log.ShowGraph
+ value := gui.c.UserConfig.Git.Log.ShowGraph
switch value {
case "always":
return true
@@ -251,17 +246,17 @@ func (gui *Gui) shouldShowGraph() bool {
return false
}
-func (gui *Gui) reflogCommitsListContext() IListContext {
- parseEmoji := gui.UserConfig.Git.ParseEmoji
+func (gui *Gui) reflogCommitsListContext() types.IListContext {
+ parseEmoji := gui.c.UserConfig.Git.ParseEmoji
return &ListContext{
BasicContext: &BasicContext{
ViewName: "commits",
WindowName: "commits",
Key: REFLOG_COMMITS_CONTEXT_KEY,
- Kind: SIDE_CONTEXT,
+ Kind: types.SIDE_CONTEXT,
},
GetItemsLength: func() int { return len(gui.State.FilteredReflogCommits) },
- OnGetPanelState: func() IListPanelState { return gui.State.Panels.ReflogCommits },
+ OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.ReflogCommits },
OnRenderToMain: OnFocusWrapper(gui.reflogCommitsRenderToMain),
Gui: gui,
GetDisplayStrings: func(startIdx int, length int) [][]string {
@@ -273,45 +268,45 @@ func (gui *Gui) reflogCommitsListContext() IListContext {
parseEmoji,
)
},
- SelectedItem: func() (ListItem, bool) {
+ SelectedItem: func() (types.ListItem, bool) {
item := gui.getSelectedReflogCommit()
return item, item != nil
},
}
}
-func (gui *Gui) stashListContext() IListContext {
+func (gui *Gui) stashListContext() types.IListContext {
return &ListContext{
BasicContext: &BasicContext{
ViewName: "stash",
WindowName: "stash",
Key: STASH_CONTEXT_KEY,
- Kind: SIDE_CONTEXT,
+ Kind: types.SIDE_CONTEXT,
},
GetItemsLength: func() int { return len(gui.State.StashEntries) },
- OnGetPanelState: func() IListPanelState { return gui.State.Panels.Stash },
+ OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Stash },
OnRenderToMain: OnFocusWrapper(gui.stashRenderToMain),
Gui: gui,
GetDisplayStrings: func(startIdx int, length int) [][]string {
return presentation.GetStashEntryListDisplayStrings(gui.State.StashEntries, gui.State.Modes.Diffing.Ref)
},
- SelectedItem: func() (ListItem, bool) {
+ SelectedItem: func() (types.ListItem, bool) {
item := gui.getSelectedStashEntry()
return item, item != nil
},
}
}
-func (gui *Gui) commitFilesListContext() IListContext {
+func (gui *Gui) commitFilesListContext() types.IListContext {
return &ListContext{
BasicContext: &BasicContext{
ViewName: "commitFiles",
WindowName: "commits",
Key: COMMIT_FILES_CONTEXT_KEY,
- Kind: SIDE_CONTEXT,
+ Kind: types.SIDE_CONTEXT,
},
GetItemsLength: func() int { return gui.State.CommitFileTreeViewModel.GetItemsLength() },
- OnGetPanelState: func() IListPanelState { return gui.State.Panels.CommitFiles },
+ OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.CommitFiles },
OnFocus: OnFocusWrapper(gui.onCommitFileFocus),
OnRenderToMain: OnFocusWrapper(gui.commitFilesRenderToMain),
Gui: gui,
@@ -320,7 +315,7 @@ func (gui *Gui) commitFilesListContext() IListContext {
return [][]string{{style.FgRed.Sprint("(none)")}}
}
- lines := presentation.RenderCommitFileTree(gui.State.CommitFileTreeViewModel, gui.State.Modes.Diffing.Ref, gui.Git.Patch.PatchManager)
+ lines := presentation.RenderCommitFileTree(gui.State.CommitFileTreeViewModel, gui.State.Modes.Diffing.Ref, gui.git.Patch.PatchManager)
mappedLines := make([][]string, len(lines))
for i, line := range lines {
mappedLines[i] = []string{line}
@@ -328,45 +323,45 @@ func (gui *Gui) commitFilesListContext() IListContext {
return mappedLines
},
- SelectedItem: func() (ListItem, bool) {
+ SelectedItem: func() (types.ListItem, bool) {
item := gui.getSelectedCommitFileNode()
return item, item != nil
},
}
}
-func (gui *Gui) submodulesListContext() IListContext {
+func (gui *Gui) submodulesListContext() types.IListContext {
return &ListContext{
BasicContext: &BasicContext{
ViewName: "files",
WindowName: "files",
Key: SUBMODULES_CONTEXT_KEY,
- Kind: SIDE_CONTEXT,
+ Kind: types.SIDE_CONTEXT,
},
GetItemsLength: func() int { return len(gui.State.Submodules) },
- OnGetPanelState: func() IListPanelState { return gui.State.Panels.Submodules },
+ OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Submodules },
OnRenderToMain: OnFocusWrapper(gui.submodulesRenderToMain),
Gui: gui,
GetDisplayStrings: func(startIdx int, length int) [][]string {
return presentation.GetSubmoduleListDisplayStrings(gui.State.Submodules)
},
- SelectedItem: func() (ListItem, bool) {
+ SelectedItem: func() (types.ListItem, bool) {
item := gui.getSelectedSubmodule()
return item, item != nil
},
}
}
-func (gui *Gui) suggestionsListContext() IListContext {
+func (gui *Gui) suggestionsListContext() types.IListContext {
return &ListContext{
BasicContext: &BasicContext{
ViewName: "suggestions",
WindowName: "suggestions",
Key: SUGGESTIONS_CONTEXT_KEY,
- Kind: PERSISTENT_POPUP,
+ Kind: types.PERSISTENT_POPUP,
},
GetItemsLength: func() int { return len(gui.State.Suggestions) },
- OnGetPanelState: func() IListPanelState { return gui.State.Panels.Suggestions },
+ OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Suggestions },
Gui: gui,
GetDisplayStrings: func(startIdx int, length int) [][]string {
return presentation.GetSuggestionListDisplayStrings(gui.State.Suggestions)
@@ -374,8 +369,8 @@ func (gui *Gui) suggestionsListContext() IListContext {
}
}
-func (gui *Gui) getListContexts() []IListContext {
- return []IListContext{
+func (gui *Gui) getListContexts() []types.IListContext {
+ return []types.IListContext{
gui.State.Contexts.Menu,
gui.State.Contexts.Files,
gui.State.Contexts.Branches,
@@ -391,58 +386,3 @@ func (gui *Gui) getListContexts() []IListContext {
gui.State.Contexts.Suggestions,
}
}
-
-func (gui *Gui) getListContextKeyBindings() []*types.Binding {
- bindings := make([]*types.Binding, 0)
-
- keybindingConfig := gui.UserConfig.Keybinding
-
- for _, listContext := range gui.getListContexts() {
- listContext := listContext
-
- bindings = append(bindings, []*types.Binding{
- {ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gui.getKey(keybindingConfig.Universal.PrevItemAlt), Modifier: gocui.ModNone, Handler: listContext.handlePrevLine},
- {ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gui.getKey(keybindingConfig.Universal.PrevItem), Modifier: gocui.ModNone, Handler: listContext.handlePrevLine},
- {ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gocui.MouseWheelUp, Modifier: gocui.ModNone, Handler: listContext.handlePrevLine},
- {ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gui.getKey(keybindingConfig.Universal.NextItemAlt), Modifier: gocui.ModNone, Handler: listContext.handleNextLine},
- {ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gui.getKey(keybindingConfig.Universal.NextItem), Modifier: gocui.ModNone, Handler: listContext.handleNextLine},
- {ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gui.getKey(keybindingConfig.Universal.PrevPage), Modifier: gocui.ModNone, Handler: listContext.handlePrevPage, Description: gui.Tr.LcPrevPage},
- {ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gui.getKey(keybindingConfig.Universal.NextPage), Modifier: gocui.ModNone, Handler: listContext.handleNextPage, Description: gui.Tr.LcNextPage},
- {ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gui.getKey(keybindingConfig.Universal.GotoTop), Modifier: gocui.ModNone, Handler: listContext.handleGotoTop, Description: gui.Tr.LcGotoTop},
- {ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: listContext.handleNextLine},
- {ViewName: listContext.GetViewName(), Contexts: []string{string(listContext.GetKey())}, Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: listContext.handleClick},
- {ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gui.getKey(keybindingConfig.Universal.ScrollLeft), Modifier: gocui.ModNone, Handler: listContext.handleScrollLeft},
- {ViewName: listContext.GetViewName(), Tag: "navigation", Contexts: []string{string(listContext.GetKey())}, Key: gui.getKey(keybindingConfig.Universal.ScrollRight), Modifier: gocui.ModNone, Handler: listContext.handleScrollRight},
- }...)
-
- openSearchHandler := gui.handleOpenSearch
- gotoBottomHandler := listContext.handleGotoBottom
-
- // the branch commits context needs to lazyload things so it has a couple of its own handlers
- if listContext.GetKey() == BRANCH_COMMITS_CONTEXT_KEY {
- openSearchHandler = gui.handleOpenSearchForCommitsPanel
- gotoBottomHandler = gui.handleGotoBottomForCommitsPanel
- }
-
- bindings = append(bindings, []*types.Binding{
- {
- ViewName: listContext.GetViewName(),
- Contexts: []string{string(listContext.GetKey())},
- Key: gui.getKey(keybindingConfig.Universal.StartSearch),
- Handler: func() error { return openSearchHandler(listContext.GetViewName()) },
- Description: gui.Tr.LcStartSearch,
- Tag: "navigation",
- },
- {
- ViewName: listContext.GetViewName(),
- Contexts: []string{string(listContext.GetKey())},
- Key: gui.getKey(keybindingConfig.Universal.GotoBottom),
- Handler: gotoBottomHandler,
- Description: gui.Tr.LcGotoBottom,
- Tag: "navigation",
- },
- }...)
- }
-
- return bindings
-}
diff --git a/pkg/gui/main_panels.go b/pkg/gui/main_panels.go
index 72af94b1d..1ecbd82e1 100644
--- a/pkg/gui/main_panels.go
+++ b/pkg/gui/main_panels.go
@@ -124,7 +124,7 @@ func (gui *Gui) refreshMainView(opts *viewUpdateOpts, view *gocui.View) error {
view.Highlight = opts.highlight
if err := gui.runTaskForView(view, opts.task); err != nil {
- gui.Log.Error(err)
+ gui.c.Log.Error(err)
return nil
}
diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go
index f1bc558c4..fc7f70329 100644
--- a/pkg/gui/menu_panel.go
+++ b/pkg/gui/menu_panel.go
@@ -10,12 +10,12 @@ import (
)
func (gui *Gui) getMenuOptions() map[string]string {
- keybindingConfig := gui.UserConfig.Keybinding
+ keybindingConfig := gui.c.UserConfig.Keybinding
return map[string]string{
- gui.getKeyDisplay(keybindingConfig.Universal.Return): gui.Tr.LcClose,
- fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.Tr.LcNavigate,
- gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.Tr.LcExecute,
+ gui.getKeyDisplay(keybindingConfig.Universal.Return): gui.c.Tr.LcClose,
+ fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
+ gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.c.Tr.LcExecute,
}
}
@@ -28,7 +28,7 @@ func (gui *Gui) createMenu(opts popup.CreateMenuOptions) error {
if !opts.HideCancel {
// this is mutative but I'm okay with that for now
opts.Items = append(opts.Items, &popup.MenuItem{
- DisplayStrings: []string{gui.Tr.LcCancel},
+ DisplayStrings: []string{gui.c.Tr.LcCancel},
OnPress: func() error {
return nil
},
@@ -66,18 +66,13 @@ func (gui *Gui) createMenu(opts popup.CreateMenuOptions) error {
menuView.SetContent(list)
gui.State.Panels.Menu.SelectedLineIdx = 0
- return gui.pushContext(gui.State.Contexts.Menu)
+ return gui.c.PushContext(gui.State.Contexts.Menu)
}
-func (gui *Gui) onMenuPress() error {
- selectedLine := gui.State.Panels.Menu.SelectedLineIdx
- if err := gui.returnFromContext(); err != nil {
- return err
+func (gui *Gui) getSelectedMenuItem() *popup.MenuItem {
+ if len(gui.State.MenuItems) == 0 {
+ return nil
}
- if err := gui.State.MenuItems[selectedLine].OnPress(); err != nil {
- return err
- }
-
- return nil
+ return gui.State.MenuItems[gui.State.Panels.Menu.SelectedLineIdx]
}
diff --git a/pkg/gui/merge_panel.go b/pkg/gui/merge_panel.go
index 9f46c177c..eb154d57d 100644
--- a/pkg/gui/merge_panel.go
+++ b/pkg/gui/merge_panel.go
@@ -52,8 +52,8 @@ func (gui *Gui) handleMergeConflictUndo() error {
return nil
}
- gui.logAction("Restoring file to previous state")
- gui.logCommand("Undoing last conflict resolution", false)
+ gui.c.LogAction("Restoring file to previous state")
+ gui.LogCommand("Undoing last conflict resolution", false)
if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0644); err != nil {
return err
}
@@ -124,8 +124,8 @@ func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error
case mergeconflicts.ALL:
logStr = "Picking all hunks"
}
- gui.logAction("Resolve merge conflict")
- gui.logCommand(logStr, false)
+ gui.c.LogAction("Resolve merge conflict")
+ gui.LogCommand(logStr, false)
state.PushContent(content)
return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0644)
}
@@ -153,7 +153,7 @@ func (gui *Gui) renderConflicts(hasFocus bool) error {
return gui.refreshMainViews(refreshMainOpts{
main: &viewUpdateOpts{
- title: gui.Tr.MergeConflictsTitle,
+ title: gui.c.Tr.MergeConflictsTitle,
task: NewRenderStringWithoutScrollTask(content),
noWrap: true,
},
@@ -178,19 +178,19 @@ func (gui *Gui) centerYPos(view *gocui.View, y int) {
}
func (gui *Gui) getMergingOptions() map[string]string {
- keybindingConfig := gui.UserConfig.Keybinding
+ keybindingConfig := gui.c.UserConfig.Keybinding
return map[string]string{
- fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.Tr.LcSelectHunk,
- fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevBlock), gui.getKeyDisplay(keybindingConfig.Universal.NextBlock)): gui.Tr.LcNavigateConflicts,
- gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.Tr.LcPickHunk,
- gui.getKeyDisplay(keybindingConfig.Main.PickBothHunks): gui.Tr.LcPickAllHunks,
- gui.getKeyDisplay(keybindingConfig.Universal.Undo): gui.Tr.LcUndo,
+ fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcSelectHunk,
+ fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevBlock), gui.getKeyDisplay(keybindingConfig.Universal.NextBlock)): gui.c.Tr.LcNavigateConflicts,
+ gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.c.Tr.LcPickHunk,
+ gui.getKeyDisplay(keybindingConfig.Main.PickBothHunks): gui.c.Tr.LcPickAllHunks,
+ gui.getKeyDisplay(keybindingConfig.Universal.Undo): gui.c.Tr.LcUndo,
}
}
func (gui *Gui) handleEscapeMerge() error {
- if err := gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
+ if err := gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
return err
}
@@ -200,7 +200,7 @@ func (gui *Gui) handleEscapeMerge() error {
func (gui *Gui) onLastConflictResolved() error {
// as part of refreshing files, we handle the situation where a file has had
// its merge conflicts resolved.
- return gui.refreshSidePanels(types.RefreshOptions{mode: types.ASYNC, scope: []types.RefreshableView{types.FILES}})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
}
func (gui *Gui) resetMergeState() {
@@ -209,7 +209,7 @@ func (gui *Gui) resetMergeState() {
}
func (gui *Gui) setMergeState(path string) (bool, error) {
- content, err := gui.Git.File.Cat(path)
+ content, err := gui.git.File.Cat(path)
if err != nil {
return false, err
}
@@ -269,7 +269,6 @@ func (gui *Gui) setConflictsAndRender(path string, hasFocus bool) (bool, error)
return false, err
}
- // if we don't have conflicts we'll fall through and show the diff
if hasConflicts {
return true, gui.renderConflicts(hasFocus)
}
@@ -294,7 +293,7 @@ func (gui *Gui) refreshMergeState() error {
hasConflicts, err := gui.setConflictsAndRender(gui.State.Panels.Merging.GetPath(), true)
if err != nil {
- return gui.surfaceError(err)
+ return gui.c.Error(err)
}
if !hasConflicts {
@@ -303,3 +302,19 @@ func (gui *Gui) refreshMergeState() error {
return nil
}
+
+func (gui *Gui) switchToMerge(path string) error {
+ gui.takeOverMergeConflictScrolling()
+
+ if gui.State.Panels.Merging.GetPath() != path {
+ hasConflicts, err := gui.setMergeStateWithLock(path)
+ if err != nil {
+ return err
+ }
+ if !hasConflicts {
+ return nil
+ }
+ }
+
+ return gui.c.PushContext(gui.State.Contexts.Merging)
+}
diff --git a/pkg/gui/misc.go b/pkg/gui/misc.go
new file mode 100644
index 000000000..f3e19961e
--- /dev/null
+++ b/pkg/gui/misc.go
@@ -0,0 +1,19 @@
+package gui
+
+// this file is to put things where it's not obvious where they belong while this refactor takes place
+
+func (gui *Gui) getSuggestedRemote() string {
+ remotes := gui.State.Remotes
+
+ if len(remotes) == 0 {
+ return "origin"
+ }
+
+ for _, remote := range remotes {
+ if remote.Name == "origin" {
+ return remote.Name
+ }
+ }
+
+ return remotes[0].Name
+}
diff --git a/pkg/gui/modes.go b/pkg/gui/modes.go
index b60fafc8a..3f90f312d 100644
--- a/pkg/gui/modes.go
+++ b/pkg/gui/modes.go
@@ -21,7 +21,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
return gui.withResetButton(
fmt.Sprintf(
"%s %s",
- gui.Tr.LcShowingGitDiff,
+ gui.c.Tr.LcShowingGitDiff,
"git diff "+gui.diffStr(),
),
style.FgMagenta,
@@ -30,9 +30,9 @@ func (gui *Gui) modeStatuses() []modeStatus {
reset: gui.exitDiffMode,
},
{
- isActive: gui.Git.Patch.PatchManager.Active,
+ isActive: gui.git.Patch.PatchManager.Active,
description: func() string {
- return gui.withResetButton(gui.Tr.LcBuildingPatch, style.FgYellow.SetBold())
+ return gui.withResetButton(gui.c.Tr.LcBuildingPatch, style.FgYellow.SetBold())
},
reset: gui.handleResetPatch,
},
@@ -42,7 +42,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
return gui.withResetButton(
fmt.Sprintf(
"%s '%s'",
- gui.Tr.LcFilteringBy,
+ gui.c.Tr.LcFilteringBy,
gui.State.Modes.Filtering.GetPath(),
),
style.FgRed,
@@ -65,10 +65,10 @@ func (gui *Gui) modeStatuses() []modeStatus {
},
{
isActive: func() bool {
- return gui.Git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE
+ return gui.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE
},
description: func() string {
- workingTreeState := gui.Git.Status.WorkingTreeState()
+ workingTreeState := gui.git.Status.WorkingTreeState()
return gui.withResetButton(
formatWorkingTreeState(workingTreeState), style.FgYellow,
)
@@ -82,7 +82,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
description: func() string {
return gui.withResetButton("bisecting", style.FgGreen)
},
- reset: gui.resetBisect,
+ reset: gui.Controllers.Bisect.Reset,
},
}
}
@@ -91,6 +91,6 @@ func (gui *Gui) withResetButton(content string, textStyle style.TextStyle) strin
return textStyle.Sprintf(
"%s %s",
content,
- style.AttrUnderline.Sprint(gui.Tr.ResetInParentheses),
+ style.AttrUnderline.Sprint(gui.c.Tr.ResetInParentheses),
)
}
diff --git a/pkg/gui/options_menu_panel.go b/pkg/gui/options_menu_panel.go
index f02f96a95..2623e6df0 100644
--- a/pkg/gui/options_menu_panel.go
+++ b/pkg/gui/options_menu_panel.go
@@ -74,8 +74,8 @@ func (gui *Gui) handleCreateOptionsMenu() error {
}
}
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{
- Title: strings.Title(gui.Tr.LcMenu),
+ return gui.c.Menu(popup.CreateMenuOptions{
+ Title: strings.Title(gui.c.Tr.LcMenu),
Items: menuItems,
HideCancel: true,
})
diff --git a/pkg/gui/patch_building_panel.go b/pkg/gui/patch_building_panel.go
index eb7728100..865e1162c 100644
--- a/pkg/gui/patch_building_panel.go
+++ b/pkg/gui/patch_building_panel.go
@@ -18,7 +18,7 @@ func (gui *Gui) getFromAndReverseArgsForDiff(to string) (string, bool) {
}
func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
- if !gui.Git.Patch.PatchManager.Active() {
+ if !gui.git.Patch.PatchManager.Active() {
return gui.handleEscapePatchBuildingPanel()
}
@@ -33,12 +33,12 @@ func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
to := gui.State.CommitFileTreeViewModel.GetParent()
from, reverse := gui.getFromAndReverseArgsForDiff(to)
- diff, err := gui.Git.WorkingTree.ShowFileDiff(from, to, reverse, node.GetPath(), true)
+ diff, err := gui.git.WorkingTree.ShowFileDiff(from, to, reverse, node.GetPath(), true)
if err != nil {
return err
}
- secondaryDiff := gui.Git.Patch.PatchManager.RenderPatchForFile(node.GetPath(), true, false, true)
+ secondaryDiff := gui.git.Patch.PatchManager.RenderPatchForFile(node.GetPath(), true, false, true)
if err != nil {
return err
}
@@ -75,15 +75,15 @@ func (gui *Gui) onPatchBuildingFocus(selectedLineIdx int) error {
func (gui *Gui) handleToggleSelectionForPatch() error {
err := gui.withLBLActiveCheck(func(state *LblPanelState) error {
- toggleFunc := gui.Git.Patch.PatchManager.AddFileLineRange
+ toggleFunc := gui.git.Patch.PatchManager.AddFileLineRange
filename := gui.getSelectedCommitFileName()
- includedLineIndices, err := gui.Git.Patch.PatchManager.GetFileIncLineIndices(filename)
+ includedLineIndices, err := gui.git.Patch.PatchManager.GetFileIncLineIndices(filename)
if err != nil {
return err
}
currentLineIsStaged := utils.IncludesInt(includedLineIndices, state.GetSelectedLineIdx())
if currentLineIsStaged {
- toggleFunc = gui.Git.Patch.PatchManager.RemoveFileLineRange
+ toggleFunc = gui.git.Patch.PatchManager.RemoveFileLineRange
}
// add range of lines to those set for the file
@@ -96,7 +96,7 @@ func (gui *Gui) handleToggleSelectionForPatch() error {
if err := toggleFunc(node.GetPath(), firstLineIdx, lastLineIdx); err != nil {
// might actually want to return an error here
- gui.Log.Error(err)
+ gui.c.Log.Error(err)
}
return nil
@@ -116,12 +116,12 @@ func (gui *Gui) handleToggleSelectionForPatch() error {
func (gui *Gui) handleEscapePatchBuildingPanel() error {
gui.escapeLineByLinePanel()
- if gui.Git.Patch.PatchManager.IsEmpty() {
- gui.Git.Patch.PatchManager.Reset()
+ if gui.git.Patch.PatchManager.IsEmpty() {
+ gui.git.Patch.PatchManager.Reset()
}
if gui.currentContext().GetKey() == gui.State.Contexts.PatchBuilding.GetKey() {
- return gui.pushContext(gui.State.Contexts.CommitFiles)
+ return gui.c.PushContext(gui.State.Contexts.CommitFiles)
} else {
// need to re-focus in case the secondary view should now be hidden
return gui.currentContext().HandleFocus()
@@ -129,8 +129,8 @@ func (gui *Gui) handleEscapePatchBuildingPanel() error {
}
func (gui *Gui) secondaryPatchPanelUpdateOpts() *viewUpdateOpts {
- if gui.Git.Patch.PatchManager.Active() {
- patch := gui.Git.Patch.PatchManager.RenderAggregatedPatchColored(false)
+ if gui.git.Patch.PatchManager.Active() {
+ patch := gui.git.Patch.PatchManager.RenderAggregatedPatchColored(false)
return &viewUpdateOpts{
title: "Custom Patch",
diff --git a/pkg/gui/patch_options_panel.go b/pkg/gui/patch_options_panel.go
index 915572c16..5c321819a 100644
--- a/pkg/gui/patch_options_panel.go
+++ b/pkg/gui/patch_options_panel.go
@@ -9,8 +9,8 @@ import (
)
func (gui *Gui) handleCreatePatchOptionsMenu() error {
- if !gui.Git.Patch.PatchManager.Active() {
- return gui.PopupHandler.ErrorMsg(gui.Tr.NoPatchError)
+ if !gui.git.Patch.PatchManager.Active() {
+ return gui.c.ErrorMsg(gui.c.Tr.NoPatchError)
}
menuItems := []*popup.MenuItem{
@@ -28,10 +28,10 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error {
},
}
- if gui.Git.Patch.PatchManager.CanRebase && gui.Git.Status.WorkingTreeState() == enums.REBASE_MODE_NONE {
+ if gui.git.Patch.PatchManager.CanRebase && gui.git.Status.WorkingTreeState() == enums.REBASE_MODE_NONE {
menuItems = append(menuItems, []*popup.MenuItem{
{
- DisplayString: fmt.Sprintf("remove patch from original commit (%s)", gui.Git.Patch.PatchManager.To),
+ DisplayString: fmt.Sprintf("remove patch from original commit (%s)", gui.git.Patch.PatchManager.To),
OnPress: gui.handleDeletePatchFromCommit,
},
{
@@ -46,7 +46,7 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error {
if gui.currentContext().GetKey() == gui.State.Contexts.BranchCommits.GetKey() {
selectedCommit := gui.getSelectedLocalCommit()
- if selectedCommit != nil && gui.Git.Patch.PatchManager.To != selectedCommit.Sha {
+ if selectedCommit != nil && gui.git.Patch.PatchManager.To != selectedCommit.Sha {
// adding this option to index 1
menuItems = append(
menuItems[:1],
@@ -63,12 +63,12 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error {
}
}
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: gui.Tr.PatchOptionsTitle, Items: menuItems})
+ return gui.c.Menu(popup.CreateMenuOptions{Title: gui.c.Tr.PatchOptionsTitle, Items: menuItems})
}
func (gui *Gui) getPatchCommitIndex() int {
for index, commit := range gui.State.Commits {
- if commit.Sha == gui.Git.Patch.PatchManager.To {
+ if commit.Sha == gui.git.Patch.PatchManager.To {
return index
}
}
@@ -76,8 +76,8 @@ func (gui *Gui) getPatchCommitIndex() int {
}
func (gui *Gui) validateNormalWorkingTreeState() (bool, error) {
- if gui.Git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
- return false, gui.PopupHandler.ErrorMsg(gui.Tr.CantPatchWhileRebasingError)
+ if gui.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
+ return false, gui.c.ErrorMsg(gui.c.Tr.CantPatchWhileRebasingError)
}
return true, nil
}
@@ -98,11 +98,11 @@ func (gui *Gui) handleDeletePatchFromCommit() error {
return err
}
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
+ return gui.c.WithWaitingStatus(gui.c.Tr.RebasingStatus, func() error {
commitIndex := gui.getPatchCommitIndex()
- gui.logAction(gui.Tr.Actions.RemovePatchFromCommit)
- err := gui.Git.Patch.DeletePatchesFromCommit(gui.State.Commits, commitIndex)
- return gui.handleGenericMergeCommandResult(err)
+ gui.c.LogAction(gui.c.Tr.Actions.RemovePatchFromCommit)
+ err := gui.git.Patch.DeletePatchesFromCommit(gui.State.Commits, commitIndex)
+ return gui.checkMergeOrRebase(err)
})
}
@@ -115,11 +115,11 @@ func (gui *Gui) handleMovePatchToSelectedCommit() error {
return err
}
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
+ return gui.c.WithWaitingStatus(gui.c.Tr.RebasingStatus, func() error {
commitIndex := gui.getPatchCommitIndex()
- gui.logAction(gui.Tr.Actions.MovePatchToSelectedCommit)
- err := gui.Git.Patch.MovePatchToSelectedCommit(gui.State.Commits, commitIndex, gui.State.Panels.Commits.SelectedLineIdx)
- return gui.handleGenericMergeCommandResult(err)
+ gui.c.LogAction(gui.c.Tr.Actions.MovePatchToSelectedCommit)
+ err := gui.git.Patch.MovePatchToSelectedCommit(gui.State.Commits, commitIndex, gui.State.Panels.Commits.SelectedLineIdx)
+ return gui.checkMergeOrRebase(err)
})
}
@@ -133,18 +133,18 @@ func (gui *Gui) handleMovePatchIntoWorkingTree() error {
}
pull := func(stash bool) error {
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
+ return gui.c.WithWaitingStatus(gui.c.Tr.RebasingStatus, func() error {
commitIndex := gui.getPatchCommitIndex()
- gui.logAction(gui.Tr.Actions.MovePatchIntoIndex)
- err := gui.Git.Patch.MovePatchIntoIndex(gui.State.Commits, commitIndex, stash)
- return gui.handleGenericMergeCommandResult(err)
+ gui.c.LogAction(gui.c.Tr.Actions.MovePatchIntoIndex)
+ err := gui.git.Patch.MovePatchIntoIndex(gui.State.Commits, commitIndex, stash)
+ return gui.checkMergeOrRebase(err)
})
}
- if len(gui.trackedFiles()) > 0 {
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.MustStashTitle,
- Prompt: gui.Tr.MustStashWarning,
+ if gui.workingTreeHelper.IsWorkingTreeDirty() {
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.MustStashTitle,
+ Prompt: gui.c.Tr.MustStashWarning,
HandleConfirm: func() error {
return pull(true)
},
@@ -163,11 +163,11 @@ func (gui *Gui) handlePullPatchIntoNewCommit() error {
return err
}
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
+ return gui.c.WithWaitingStatus(gui.c.Tr.RebasingStatus, func() error {
commitIndex := gui.getPatchCommitIndex()
- gui.logAction(gui.Tr.Actions.MovePatchIntoNewCommit)
- err := gui.Git.Patch.PullPatchIntoNewCommit(gui.State.Commits, commitIndex)
- return gui.handleGenericMergeCommandResult(err)
+ gui.c.LogAction(gui.c.Tr.Actions.MovePatchIntoNewCommit)
+ err := gui.git.Patch.PullPatchIntoNewCommit(gui.State.Commits, commitIndex)
+ return gui.checkMergeOrRebase(err)
})
}
@@ -176,21 +176,21 @@ func (gui *Gui) handleApplyPatch(reverse bool) error {
return err
}
- action := gui.Tr.Actions.ApplyPatch
+ action := gui.c.Tr.Actions.ApplyPatch
if reverse {
action = "Apply patch in reverse"
}
- gui.logAction(action)
- if err := gui.Git.Patch.PatchManager.ApplyPatches(reverse); err != nil {
- return gui.PopupHandler.Error(err)
+ gui.c.LogAction(action)
+ if err := gui.git.Patch.PatchManager.ApplyPatches(reverse); err != nil {
+ return gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
}
func (gui *Gui) handleResetPatch() error {
- gui.Git.Patch.PatchManager.Reset()
+ gui.git.Patch.PatchManager.Reset()
if gui.currentContextKeyIgnoringPopups() == MAIN_PATCH_BUILDING_CONTEXT_KEY {
- if err := gui.pushContext(gui.State.Contexts.CommitFiles); err != nil {
+ if err := gui.c.PushContext(gui.State.Contexts.CommitFiles); err != nil {
return err
}
}
diff --git a/pkg/gui/popup/popup_handler.go b/pkg/gui/popup/popup_handler.go
index adc276d5f..bba0e52a8 100644
--- a/pkg/gui/popup/popup_handler.go
+++ b/pkg/gui/popup/popup_handler.go
@@ -19,6 +19,8 @@ type IPopupHandler interface {
WithLoaderPanel(message string, f func() error) error
WithWaitingStatus(message string, f func() error) error
Menu(opts CreateMenuOptions) error
+ Toast(message string)
+ GetPromptInput() string
}
type CreateMenuOptions struct {
@@ -74,6 +76,8 @@ type RealPopupHandler struct {
closePopupFn func() error
createMenuFn func(CreateMenuOptions) error
withWaitingStatusFn func(message string, f func() error) error
+ toastFn func(message string)
+ getPromptInputFn func() string
}
var _ IPopupHandler = &RealPopupHandler{}
@@ -85,6 +89,8 @@ func NewPopupHandler(
closePopupFn func() error,
createMenuFn func(CreateMenuOptions) error,
withWaitingStatusFn func(message string, f func() error) error,
+ toastFn func(message string),
+ getPromptInputFn func() string,
) *RealPopupHandler {
return &RealPopupHandler{
Common: common,
@@ -94,6 +100,8 @@ func NewPopupHandler(
closePopupFn: closePopupFn,
createMenuFn: createMenuFn,
withWaitingStatusFn: withWaitingStatusFn,
+ toastFn: toastFn,
+ getPromptInputFn: getPromptInputFn,
}
}
@@ -101,6 +109,10 @@ func (self *RealPopupHandler) Menu(opts CreateMenuOptions) error {
return self.createMenuFn(opts)
}
+func (self *RealPopupHandler) Toast(message string) {
+ self.toastFn(message)
+}
+
func (self *RealPopupHandler) WithWaitingStatus(message string, f func() error) error {
return self.withWaitingStatusFn(message, f)
}
@@ -188,6 +200,12 @@ func (self *RealPopupHandler) WithLoaderPanel(message string, f func() error) er
return nil
}
+// returns the content that has currently been typed into the prompt. Useful for
+// asyncronously updating the suggestions list under the prompt.
+func (self *RealPopupHandler) GetPromptInput() string {
+ return self.getPromptInputFn()
+}
+
type TestPopupHandler struct {
OnErrorMsg func(message string) error
OnAsk func(opts AskOpts) error
@@ -221,3 +239,11 @@ func (self *TestPopupHandler) WithWaitingStatus(message string, f func() error)
func (self *TestPopupHandler) Menu(opts CreateMenuOptions) error {
panic("not yet implemented")
}
+
+func (self *TestPopupHandler) Toast(message string) {
+ panic("not yet implemented")
+}
+
+func (self *TestPopupHandler) CurrentInput() string {
+ panic("not yet implemented")
+}
diff --git a/pkg/gui/pty.go b/pkg/gui/pty.go
index b6c3069f2..a183be880 100644
--- a/pkg/gui/pty.go
+++ b/pkg/gui/pty.go
@@ -41,7 +41,7 @@ func (gui *Gui) onResize() error {
// command.
func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error {
width, _ := gui.Views.Main.Size()
- pager := gui.Git.Config.GetPager(width)
+ pager := gui.git.Config.GetPager(width)
if pager == "" {
// if we're not using a custom pager we don't need to use a pty
@@ -60,7 +60,7 @@ func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error
start := func() (*exec.Cmd, io.Reader) {
ptmx, err := pty.StartWithSize(cmd, gui.desiredPtySize())
if err != nil {
- gui.Log.Error(err)
+ gui.c.Log.Error(err)
}
gui.State.Ptmx = ptmx
diff --git a/pkg/gui/pull_request_menu_panel.go b/pkg/gui/pull_request_menu_panel.go
index 5c2a3df8d..9973daeca 100644
--- a/pkg/gui/pull_request_menu_panel.go
+++ b/pkg/gui/pull_request_menu_panel.go
@@ -18,17 +18,17 @@ func (gui *Gui) createPullRequestMenu(selectedBranch *models.Branch, checkedOutB
menuItemsForBranch := func(branch *models.Branch) []*popup.MenuItem {
return []*popup.MenuItem{
{
- DisplayStrings: fromToDisplayStrings(branch.Name, gui.Tr.LcDefaultBranch),
+ DisplayStrings: fromToDisplayStrings(branch.Name, gui.c.Tr.LcDefaultBranch),
OnPress: func() error {
return gui.createPullRequest(branch.Name, "")
},
},
{
- DisplayStrings: fromToDisplayStrings(branch.Name, gui.Tr.LcSelectBranch),
+ DisplayStrings: fromToDisplayStrings(branch.Name, gui.c.Tr.LcSelectBranch),
OnPress: func() error {
- return gui.PopupHandler.Prompt(popup.PromptOpts{
+ return gui.c.Prompt(popup.PromptOpts{
Title: branch.Name + " →",
- FindSuggestionsFunc: gui.getBranchNameSuggestionsFunc(),
+ FindSuggestionsFunc: gui.suggestionsHelper.GetBranchNameSuggestionsFunc(),
HandleConfirm: func(targetBranchName string) error {
return gui.createPullRequest(branch.Name, targetBranchName)
}},
@@ -52,27 +52,27 @@ func (gui *Gui) createPullRequestMenu(selectedBranch *models.Branch, checkedOutB
menuItems = append(menuItems, menuItemsForBranch(selectedBranch)...)
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: fmt.Sprintf(gui.Tr.CreatePullRequestOptions), Items: menuItems})
+ return gui.c.Menu(popup.CreateMenuOptions{Title: fmt.Sprintf(gui.c.Tr.CreatePullRequestOptions), Items: menuItems})
}
func (gui *Gui) createPullRequest(from string, to string) error {
hostingServiceMgr := gui.getHostingServiceMgr()
url, err := hostingServiceMgr.GetPullRequestURL(from, to)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
- gui.logAction(gui.Tr.Actions.OpenPullRequest)
+ gui.c.LogAction(gui.c.Tr.Actions.OpenPullRequest)
if err := gui.OSCommand.OpenLink(url); err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
return nil
}
func (gui *Gui) getHostingServiceMgr() *hosting_service.HostingServiceMgr {
- remoteUrl := gui.Git.Config.GetRemoteURL()
- configServices := gui.UserConfig.Services
+ remoteUrl := gui.git.Config.GetRemoteURL()
+ configServices := gui.c.UserConfig.Services
return hosting_service.NewHostingServiceMgr(gui.Log, gui.Tr, remoteUrl, configServices)
}
diff --git a/pkg/gui/quitting.go b/pkg/gui/quitting.go
index 4e9242640..edf402159 100644
--- a/pkg/gui/quitting.go
+++ b/pkg/gui/quitting.go
@@ -44,7 +44,7 @@ func (gui *Gui) handleTopLevelReturn() error {
parentContext, hasParent := currentContext.GetParentContext()
if hasParent && currentContext != nil && parentContext != nil {
// TODO: think about whether this should be marked as a return rather than adding to the stack
- return gui.pushContext(parentContext)
+ return gui.c.PushContext(parentContext)
}
for _, mode := range gui.modeStatuses() {
@@ -60,7 +60,7 @@ func (gui *Gui) handleTopLevelReturn() error {
return gui.dispatchSwitchToRepo(path, true)
}
- if gui.UserConfig.QuitOnTopLevelReturn {
+ if gui.c.UserConfig.QuitOnTopLevelReturn {
return gui.handleQuit()
}
@@ -72,10 +72,10 @@ func (gui *Gui) quit() error {
return gui.createUpdateQuitConfirmation()
}
- if gui.UserConfig.ConfirmOnQuit {
- return gui.PopupHandler.Ask(popup.AskOpts{
+ if gui.c.UserConfig.ConfirmOnQuit {
+ return gui.c.Ask(popup.AskOpts{
Title: "",
- Prompt: gui.Tr.ConfirmQuit,
+ Prompt: gui.c.Tr.ConfirmQuit,
HandleConfirm: func() error {
return gocui.ErrQuit
},
diff --git a/pkg/gui/rebase_options_panel.go b/pkg/gui/rebase_options_panel.go
index b4a37e956..467665f84 100644
--- a/pkg/gui/rebase_options_panel.go
+++ b/pkg/gui/rebase_options_panel.go
@@ -20,7 +20,7 @@ const (
func (gui *Gui) handleCreateRebaseOptionsMenu() error {
options := []string{REBASE_OPTION_CONTINUE, REBASE_OPTION_ABORT}
- if gui.Git.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
+ if gui.git.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
options = append(options, REBASE_OPTION_SKIP)
}
@@ -37,23 +37,23 @@ func (gui *Gui) handleCreateRebaseOptionsMenu() error {
}
var title string
- if gui.Git.Status.WorkingTreeState() == enums.REBASE_MODE_MERGING {
- title = gui.Tr.MergeOptionsTitle
+ if gui.git.Status.WorkingTreeState() == enums.REBASE_MODE_MERGING {
+ title = gui.c.Tr.MergeOptionsTitle
} else {
- title = gui.Tr.RebaseOptionsTitle
+ title = gui.c.Tr.RebaseOptionsTitle
}
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: title, Items: menuItems})
+ return gui.c.Menu(popup.CreateMenuOptions{Title: title, Items: menuItems})
}
func (gui *Gui) genericMergeCommand(command string) error {
- status := gui.Git.Status.WorkingTreeState()
+ status := gui.git.Status.WorkingTreeState()
if status != enums.REBASE_MODE_MERGING && status != enums.REBASE_MODE_REBASING {
- return gui.PopupHandler.ErrorMsg(gui.Tr.NotMergingOrRebasing)
+ return gui.c.ErrorMsg(gui.c.Tr.NotMergingOrRebasing)
}
- gui.logAction(fmt.Sprintf("Merge/Rebase: %s", command))
+ gui.c.LogAction(fmt.Sprintf("Merge/Rebase: %s", command))
commandType := ""
switch status {
@@ -68,14 +68,14 @@ func (gui *Gui) genericMergeCommand(command string) error {
// we should end up with a command like 'git merge --continue'
// it's impossible for a rebase to require a commit so we'll use a subprocess only if it's a merge
- if status == enums.REBASE_MODE_MERGING && command != REBASE_OPTION_ABORT && gui.UserConfig.Git.Merging.ManualCommit {
+ if status == enums.REBASE_MODE_MERGING && command != REBASE_OPTION_ABORT && gui.c.UserConfig.Git.Merging.ManualCommit {
// TODO: see if we should be calling more of the code from gui.Git.Rebase.GenericMergeOrRebaseAction
return gui.runSubprocessWithSuspenseAndRefresh(
- gui.Git.Rebase.GenericMergeOrRebaseActionCmdObj(commandType, command),
+ gui.git.Rebase.GenericMergeOrRebaseActionCmdObj(commandType, command),
)
}
- result := gui.Git.Rebase.GenericMergeOrRebaseAction(commandType, command)
- if err := gui.handleGenericMergeCommandResult(result); err != nil {
+ result := gui.git.Rebase.GenericMergeOrRebaseAction(commandType, command)
+ if err := gui.checkMergeOrRebase(result); err != nil {
return err
}
return nil
@@ -98,8 +98,8 @@ func isMergeConflictErr(errStr string) bool {
return false
}
-func (gui *Gui) handleGenericMergeCommandResult(result error) error {
- if err := gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
+func (gui *Gui) checkMergeOrRebase(result error) error {
+ if err := gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
return err
}
if result == nil {
@@ -112,12 +112,12 @@ func (gui *Gui) handleGenericMergeCommandResult(result error) error {
// assume in this case that we're already done
return nil
} else if isMergeConflictErr(result.Error()) {
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.FoundConflictsTitle,
- Prompt: gui.Tr.FoundConflicts,
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.FoundConflictsTitle,
+ Prompt: gui.c.Tr.FoundConflicts,
HandlersManageFocus: true,
HandleConfirm: func() error {
- return gui.pushContext(gui.State.Contexts.Files)
+ return gui.c.PushContext(gui.State.Contexts.Files)
},
HandleClose: func() error {
if err := gui.returnFromContext(); err != nil {
@@ -128,16 +128,16 @@ func (gui *Gui) handleGenericMergeCommandResult(result error) error {
},
})
} else {
- return gui.PopupHandler.ErrorMsg(result.Error())
+ return gui.c.ErrorMsg(result.Error())
}
}
func (gui *Gui) abortMergeOrRebaseWithConfirm() error {
// prompt user to confirm that they want to abort, then do it
mode := gui.workingTreeStateNoun()
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: fmt.Sprintf(gui.Tr.AbortTitle, mode),
- Prompt: fmt.Sprintf(gui.Tr.AbortPrompt, mode),
+ return gui.c.Ask(popup.AskOpts{
+ Title: fmt.Sprintf(gui.c.Tr.AbortTitle, mode),
+ Prompt: fmt.Sprintf(gui.c.Tr.AbortPrompt, mode),
HandleConfirm: func() error {
return gui.genericMergeCommand(REBASE_OPTION_ABORT)
},
@@ -145,7 +145,7 @@ func (gui *Gui) abortMergeOrRebaseWithConfirm() error {
}
func (gui *Gui) workingTreeStateNoun() string {
- workingTreeState := gui.Git.Status.WorkingTreeState()
+ workingTreeState := gui.git.Status.WorkingTreeState()
switch workingTreeState {
case enums.REBASE_MODE_NONE:
return ""
diff --git a/pkg/gui/recent_repos_panel.go b/pkg/gui/recent_repos_panel.go
index ec6a1ffc7..428faa9c9 100644
--- a/pkg/gui/recent_repos_panel.go
+++ b/pkg/gui/recent_repos_panel.go
@@ -13,7 +13,7 @@ import (
)
func (gui *Gui) handleCreateRecentReposMenu() error {
- recentRepoPaths := gui.Config.GetAppState().RecentRepos
+ recentRepoPaths := gui.c.GetAppState().RecentRepos
reposCount := utils.Min(len(recentRepoPaths), 20)
// we won't show the current repo hence the -1
@@ -34,11 +34,11 @@ func (gui *Gui) handleCreateRecentReposMenu() error {
}
}
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: gui.Tr.RecentRepos, Items: menuItems})
+ return gui.c.Menu(popup.CreateMenuOptions{Title: gui.c.Tr.RecentRepos, Items: menuItems})
}
func (gui *Gui) handleShowAllBranchLogs() error {
- cmdObj := gui.Git.Branch.AllBranchesLogCmdObj()
+ cmdObj := gui.git.Branch.AllBranchesLogCmdObj()
task := NewRunPtyTask(cmdObj.GetCmd())
return gui.refreshMainViews(refreshMainOpts{
@@ -58,7 +58,7 @@ func (gui *Gui) dispatchSwitchToRepo(path string, reuse bool) error {
if err := os.Chdir(path); err != nil {
if os.IsNotExist(err) {
- return gui.PopupHandler.ErrorMsg(gui.Tr.ErrRepositoryMovedOrDeleted)
+ return gui.c.ErrorMsg(gui.c.Tr.ErrRepositoryMovedOrDeleted)
}
return err
}
@@ -71,11 +71,16 @@ func (gui *Gui) dispatchSwitchToRepo(path string, reuse bool) error {
return err
}
- newGitCommand, err := commands.NewGitCommand(gui.Common, gui.OSCommand, git_config.NewStdCachedGitConfig(gui.Log))
+ newGitCommand, err := commands.NewGitCommand(
+ gui.Common,
+ gui.OSCommand,
+ git_config.NewStdCachedGitConfig(gui.Log),
+ gui.Mutexes.FetchMutex,
+ )
if err != nil {
return err
}
- gui.Git = newGitCommand
+ gui.git = newGitCommand
// these two mutexes are used by our background goroutines (triggered via `gui.goEvery`. We don't want to
// switch to a repo while one of these goroutines is in the process of updating something
@@ -97,23 +102,23 @@ func (gui *Gui) dispatchSwitchToRepo(path string, reuse bool) error {
// updateRecentRepoList registers the fact that we opened lazygit in this repo,
// so that we can open the same repo via the 'recent repos' menu
func (gui *Gui) updateRecentRepoList() error {
- if gui.Git.Status.IsBareRepo() {
+ if gui.git.Status.IsBareRepo() {
// we could totally do this but it would require storing both the git-dir and the
// worktree in our recent repos list, which is a change that would need to be
// backwards compatible
- gui.Log.Info("Not appending bare repo to recent repo list")
+ gui.c.Log.Info("Not appending bare repo to recent repo list")
return nil
}
- recentRepos := gui.Config.GetAppState().RecentRepos
+ recentRepos := gui.c.GetAppState().RecentRepos
currentRepo, err := os.Getwd()
if err != nil {
return err
}
known, recentRepos := newRecentReposList(recentRepos, currentRepo)
gui.IsNewRepo = known
- gui.Config.GetAppState().RecentRepos = recentRepos
- return gui.Config.SaveAppState()
+ gui.c.GetAppState().RecentRepos = recentRepos
+ return gui.c.SaveAppState()
}
// newRecentReposList returns a new repo list with a new entry but only when it doesn't exist yet
diff --git a/pkg/gui/ref_helper.go b/pkg/gui/ref_helper.go
new file mode 100644
index 000000000..c8cccb74a
--- /dev/null
+++ b/pkg/gui/ref_helper.go
@@ -0,0 +1,137 @@
+package gui
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/jesseduffield/lazygit/pkg/commands"
+ "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
+ "github.com/jesseduffield/lazygit/pkg/gui/controllers"
+ "github.com/jesseduffield/lazygit/pkg/gui/popup"
+ "github.com/jesseduffield/lazygit/pkg/gui/style"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+)
+
+type RefHelper struct {
+ c *controllers.ControllerCommon
+ git *commands.GitCommand
+
+ State *GuiRepoState
+}
+
+func NewRefHelper(
+ c *controllers.ControllerCommon,
+ git *commands.GitCommand,
+ state *GuiRepoState,
+) *RefHelper {
+ return &RefHelper{
+ c: c,
+ git: git,
+ State: state,
+ }
+}
+
+var _ controllers.IRefHelper = &RefHelper{}
+
+func (self *RefHelper) CheckoutRef(ref string, options types.CheckoutRefOptions) error {
+ waitingStatus := options.WaitingStatus
+ if waitingStatus == "" {
+ waitingStatus = self.c.Tr.CheckingOutStatus
+ }
+
+ cmdOptions := git_commands.CheckoutOptions{Force: false, EnvVars: options.EnvVars}
+
+ onSuccess := func() {
+ self.State.Panels.Branches.SelectedLineIdx = 0
+ self.State.Panels.Commits.SelectedLineIdx = 0
+ // loading a heap of commits is slow so we limit them whenever doing a reset
+ self.State.Panels.Commits.LimitCommits = true
+ }
+
+ return self.c.WithWaitingStatus(waitingStatus, func() error {
+ if err := self.git.Branch.Checkout(ref, cmdOptions); err != nil {
+ // note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option
+
+ if options.OnRefNotFound != nil && strings.Contains(err.Error(), "did not match any file(s) known to git") {
+ return options.OnRefNotFound(ref)
+ }
+
+ if strings.Contains(err.Error(), "Please commit your changes or stash them before you switch branch") {
+ // offer to autostash changes
+ return self.c.Ask(popup.AskOpts{
+
+ Title: self.c.Tr.AutoStashTitle,
+ Prompt: self.c.Tr.AutoStashPrompt,
+ HandleConfirm: func() error {
+ if err := self.git.Stash.Save(self.c.Tr.StashPrefix + ref); err != nil {
+ return self.c.Error(err)
+ }
+ if err := self.git.Branch.Checkout(ref, cmdOptions); err != nil {
+ return self.c.Error(err)
+ }
+
+ onSuccess()
+ if err := self.git.Stash.Pop(0); err != nil {
+ if err := self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI}); err != nil {
+ return err
+ }
+ return self.c.Error(err)
+ }
+ return self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI})
+ },
+ })
+ }
+
+ if err := self.c.Error(err); err != nil {
+ return err
+ }
+ }
+ onSuccess()
+
+ return self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI})
+ })
+}
+
+func (self *RefHelper) ResetToRef(ref string, strength string, envVars []string) error {
+ if err := self.git.Commit.ResetToCommit(ref, strength, envVars); err != nil {
+ return self.c.Error(err)
+ }
+
+ self.State.Panels.Commits.SelectedLineIdx = 0
+ self.State.Panels.ReflogCommits.SelectedLineIdx = 0
+ // loading a heap of commits is slow so we limit them whenever doing a reset
+ self.State.Panels.Commits.LimitCommits = true
+
+ if err := self.c.PushContext(self.State.Contexts.BranchCommits); err != nil {
+ return err
+ }
+
+ if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.BRANCHES, types.REFLOG, types.COMMITS}}); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (self *RefHelper) CreateGitResetMenu(ref string) error {
+ strengths := []string{"soft", "mixed", "hard"}
+ menuItems := make([]*popup.MenuItem, len(strengths))
+ for i, strength := range strengths {
+ strength := strength
+ menuItems[i] = &popup.MenuItem{
+ DisplayStrings: []string{
+ fmt.Sprintf("%s reset", strength),
+ style.FgRed.Sprintf("reset --%s %s", strength, ref),
+ },
+ OnPress: func() error {
+ self.c.LogAction("Reset")
+ return self.ResetToRef(ref, strength, []string{})
+ },
+ }
+ }
+
+ return self.c.Menu(popup.CreateMenuOptions{
+ Title: fmt.Sprintf("%s %s", self.c.Tr.LcResetTo, ref),
+ Items: menuItems,
+ })
+}
diff --git a/pkg/gui/reflog_panel.go b/pkg/gui/reflog_panel.go
index 3ccbdb7c8..f562036ad 100644
--- a/pkg/gui/reflog_panel.go
+++ b/pkg/gui/reflog_panel.go
@@ -2,7 +2,9 @@ package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/gui/controllers"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
)
// list panel functions
@@ -23,7 +25,7 @@ func (gui *Gui) reflogCommitsRenderToMain() error {
if commit == nil {
task = NewRenderStringTask("No reflog history")
} else {
- cmdObj := gui.Git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
+ cmdObj := gui.git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
task = NewRunPtyTask(cmdObj.GetCmd())
}
@@ -53,10 +55,10 @@ func (gui *Gui) refreshReflogCommits() error {
}
refresh := func(stateCommits *[]*models.Commit, filterPath string) error {
- commits, onlyObtainedNewReflogCommits, err := gui.Git.Loaders.ReflogCommits.
+ commits, onlyObtainedNewReflogCommits, err := gui.git.Loaders.ReflogCommits.
GetReflogCommits(lastReflogCommit, filterPath)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
if onlyObtainedNewReflogCommits {
@@ -79,21 +81,21 @@ func (gui *Gui) refreshReflogCommits() error {
state.FilteredReflogCommits = state.ReflogCommits
}
- return gui.postRefreshUpdate(gui.State.Contexts.ReflogCommits)
+ return gui.c.PostRefreshUpdate(gui.State.Contexts.ReflogCommits)
}
-func (gui *Gui) handleCheckoutReflogCommit() error {
+func (gui *Gui) CheckoutReflogCommit() error {
commit := gui.getSelectedReflogCommit()
if commit == nil {
return nil
}
- err := gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.LcCheckoutCommit,
- Prompt: gui.Tr.SureCheckoutThisCommit,
+ err := gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.LcCheckoutCommit,
+ Prompt: gui.c.Tr.SureCheckoutThisCommit,
HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.CheckoutReflogCommit)
- return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{})
+ gui.c.LogAction(gui.c.Tr.Actions.CheckoutReflogCommit)
+ return gui.refHelper.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
},
})
if err != nil {
@@ -108,7 +110,7 @@ func (gui *Gui) handleCheckoutReflogCommit() error {
func (gui *Gui) handleCreateReflogResetMenu() error {
commit := gui.getSelectedReflogCommit()
- return gui.createResetMenu(commit.Sha)
+ return gui.refHelper.CreateGitResetMenu(commit.Sha)
}
func (gui *Gui) handleViewReflogCommitFiles() error {
@@ -117,5 +119,10 @@ func (gui *Gui) handleViewReflogCommitFiles() error {
return nil
}
- return gui.switchToCommitFilesContext(commit.Sha, false, gui.State.Contexts.ReflogCommits, "commits")
+ return gui.SwitchToCommitFilesContext(controllers.SwitchToCommitFilesContextOpts{
+ RefName: commit.Sha,
+ CanRebase: false,
+ Context: gui.State.Contexts.ReflogCommits,
+ WindowName: "commits",
+ })
}
diff --git a/pkg/gui/remote_branches_panel.go b/pkg/gui/remote_branches_panel.go
index 1ee402097..29e2e2f03 100644
--- a/pkg/gui/remote_branches_panel.go
+++ b/pkg/gui/remote_branches_panel.go
@@ -26,7 +26,7 @@ func (gui *Gui) remoteBranchesRenderToMain() error {
if remoteBranch == nil {
task = NewRenderStringTask("No branches for this remote")
} else {
- cmdObj := gui.Git.Branch.GetGraphCmdObj(remoteBranch.FullName())
+ cmdObj := gui.git.Branch.GetGraphCmdObj(remoteBranch.FullName())
task = NewRunCommandTask(cmdObj.GetCmd())
}
@@ -39,7 +39,7 @@ func (gui *Gui) remoteBranchesRenderToMain() error {
}
func (gui *Gui) handleRemoteBranchesEscape() error {
- return gui.pushContext(gui.State.Contexts.Remotes)
+ return gui.c.PushContext(gui.State.Contexts.Remotes)
}
func (gui *Gui) handleMergeRemoteBranch() error {
@@ -52,20 +52,20 @@ func (gui *Gui) handleDeleteRemoteBranch() error {
if remoteBranch == nil {
return nil
}
- message := fmt.Sprintf("%s '%s'?", gui.Tr.DeleteRemoteBranchMessage, remoteBranch.FullName())
+ message := fmt.Sprintf("%s '%s'?", gui.c.Tr.DeleteRemoteBranchMessage, remoteBranch.FullName())
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.DeleteRemoteBranch,
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.DeleteRemoteBranch,
Prompt: message,
HandleConfirm: func() error {
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.DeletingStatus, func() error {
- gui.logAction(gui.Tr.Actions.DeleteRemoteBranch)
- err := gui.Git.Remote.DeleteRemoteBranch(remoteBranch.RemoteName, remoteBranch.Name)
+ return gui.c.WithWaitingStatus(gui.c.Tr.DeletingStatus, func() error {
+ gui.c.LogAction(gui.c.Tr.Actions.DeleteRemoteBranch)
+ err := gui.git.Remote.DeleteRemoteBranch(remoteBranch.RemoteName, remoteBranch.Name)
if err != nil {
- _ = gui.PopupHandler.Error(err)
+ _ = gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
+ return gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
})
},
})
@@ -81,23 +81,23 @@ func (gui *Gui) handleSetBranchUpstream() error {
checkedOutBranch := gui.getCheckedOutBranch()
message := utils.ResolvePlaceholderString(
- gui.Tr.SetUpstreamMessage,
+ gui.c.Tr.SetUpstreamMessage,
map[string]string{
"checkedOut": checkedOutBranch.Name,
"selected": selectedBranch.FullName(),
},
)
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.SetUpstreamTitle,
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.SetUpstreamTitle,
Prompt: message,
HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.SetBranchUpstream)
- if err := gui.Git.Branch.SetUpstream(selectedBranch.RemoteName, selectedBranch.Name, checkedOutBranch.Name); err != nil {
- return gui.PopupHandler.Error(err)
+ gui.c.LogAction(gui.c.Tr.Actions.SetBranchUpstream)
+ if err := gui.git.Branch.SetUpstream(selectedBranch.RemoteName, selectedBranch.Name, checkedOutBranch.Name); err != nil {
+ return gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
+ return gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
},
})
}
@@ -108,5 +108,5 @@ func (gui *Gui) handleCreateResetToRemoteBranchMenu() error {
return nil
}
- return gui.createResetMenu(selectedBranch.FullName())
+ return gui.refHelper.CreateGitResetMenu(selectedBranch.FullName())
}
diff --git a/pkg/gui/remotes_panel.go b/pkg/gui/remotes_panel.go
index bc06f77f0..1c086e2fc 100644
--- a/pkg/gui/remotes_panel.go
+++ b/pkg/gui/remotes_panel.go
@@ -5,10 +5,8 @@ import (
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/models"
- "github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
- "github.com/jesseduffield/lazygit/pkg/utils"
)
// list panel functions
@@ -42,9 +40,9 @@ func (gui *Gui) remotesRenderToMain() error {
func (gui *Gui) refreshRemotes() error {
prevSelectedRemote := gui.getSelectedRemote()
- remotes, err := gui.Git.Loaders.Remotes.GetRemotes()
+ remotes, err := gui.git.Loaders.Remotes.GetRemotes()
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
gui.State.Remotes = remotes
@@ -59,133 +57,5 @@ func (gui *Gui) refreshRemotes() error {
}
}
- return gui.postRefreshUpdate(gui.mustContextForContextKey(ContextKey(gui.Views.Branches.Context)))
-}
-
-func (gui *Gui) handleRemoteEnter() error {
- // naive implementation: get the branches and render them to the list, change the context
- remote := gui.getSelectedRemote()
- if remote == nil {
- return nil
- }
-
- gui.State.RemoteBranches = remote.Branches
-
- newSelectedLine := 0
- if len(remote.Branches) == 0 {
- newSelectedLine = -1
- }
- gui.State.Panels.RemoteBranches.SelectedLineIdx = newSelectedLine
-
- return gui.pushContext(gui.State.Contexts.RemoteBranches)
-}
-
-func (gui *Gui) handleAddRemote() error {
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- Title: gui.Tr.LcNewRemoteName,
- HandleConfirm: func(remoteName string) error {
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- Title: gui.Tr.LcNewRemoteUrl,
- HandleConfirm: func(remoteUrl string) error {
- gui.logAction(gui.Tr.Actions.AddRemote)
- if err := gui.Git.Remote.AddRemote(remoteName, remoteUrl); err != nil {
- return err
- }
- return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.REMOTES}})
- },
- })
- },
- })
-
-}
-
-func (gui *Gui) handleRemoveRemote() error {
- remote := gui.getSelectedRemote()
- if remote == nil {
- return nil
- }
-
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.LcRemoveRemote,
- Prompt: gui.Tr.LcRemoveRemotePrompt + " '" + remote.Name + "'?",
- HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.RemoveRemote)
- if err := gui.Git.Remote.RemoveRemote(remote.Name); err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
- },
- })
-}
-
-func (gui *Gui) handleEditRemote() error {
- remote := gui.getSelectedRemote()
- if remote == nil {
- return nil
- }
-
- editNameMessage := utils.ResolvePlaceholderString(
- gui.Tr.LcEditRemoteName,
- map[string]string{
- "remoteName": remote.Name,
- },
- )
-
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- Title: editNameMessage,
- InitialContent: remote.Name,
- HandleConfirm: func(updatedRemoteName string) error {
- if updatedRemoteName != remote.Name {
- gui.logAction(gui.Tr.Actions.UpdateRemote)
- if err := gui.Git.Remote.RenameRemote(remote.Name, updatedRemoteName); err != nil {
- return gui.PopupHandler.Error(err)
- }
- }
-
- editUrlMessage := utils.ResolvePlaceholderString(
- gui.Tr.LcEditRemoteUrl,
- map[string]string{
- "remoteName": updatedRemoteName,
- },
- )
-
- urls := remote.Urls
- url := ""
- if len(urls) > 0 {
- url = urls[0]
- }
-
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- Title: editUrlMessage,
- InitialContent: url,
- HandleConfirm: func(updatedRemoteUrl string) error {
- gui.logAction(gui.Tr.Actions.UpdateRemote)
- if err := gui.Git.Remote.UpdateRemoteUrl(updatedRemoteName, updatedRemoteUrl); err != nil {
- return gui.PopupHandler.Error(err)
- }
- return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
- },
- })
- },
- })
-}
-
-func (gui *Gui) handleFetchRemote() error {
- remote := gui.getSelectedRemote()
- if remote == nil {
- return nil
- }
-
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.FetchingRemoteStatus, func() error {
- gui.Mutexes.FetchMutex.Lock()
- defer gui.Mutexes.FetchMutex.Unlock()
-
- err := gui.Git.Sync.FetchRemote(remote.Name)
- if err != nil {
- _ = gui.PopupHandler.Error(err)
- }
-
- return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
- })
+ return gui.c.PostRefreshUpdate(gui.mustContextForContextKey(types.ContextKey(gui.Views.Branches.Context)))
}
diff --git a/pkg/gui/reset_menu_panel.go b/pkg/gui/reset_menu_panel.go
deleted file mode 100644
index d920765f9..000000000
--- a/pkg/gui/reset_menu_panel.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package gui
-
-import (
- "fmt"
-
- "github.com/jesseduffield/lazygit/pkg/gui/popup"
- "github.com/jesseduffield/lazygit/pkg/gui/style"
- "github.com/jesseduffield/lazygit/pkg/gui/types"
-)
-
-func (gui *Gui) resetToRef(ref string, strength string, envVars []string) error {
- if err := gui.Git.Commit.ResetToCommit(ref, strength, envVars); err != nil {
- return gui.PopupHandler.Error(err)
- }
-
- gui.State.Panels.Commits.SelectedLineIdx = 0
- gui.State.Panels.ReflogCommits.SelectedLineIdx = 0
- // loading a heap of commits is slow so we limit them whenever doing a reset
- gui.State.Panels.Commits.LimitCommits = true
-
- if err := gui.pushContext(gui.State.Contexts.BranchCommits); err != nil {
- return err
- }
-
- if err := gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.BRANCHES, types.REFLOG, types.COMMITS}}); err != nil {
- return err
- }
-
- return nil
-}
-
-func (gui *Gui) createResetMenu(ref string) error {
- strengths := []string{"soft", "mixed", "hard"}
- menuItems := make([]*popup.MenuItem, len(strengths))
- for i, strength := range strengths {
- strength := strength
- menuItems[i] = &popup.MenuItem{
- DisplayStrings: []string{
- fmt.Sprintf("%s reset", strength),
- style.FgRed.Sprintf("reset --%s %s", strength, ref),
- },
- OnPress: func() error {
- gui.logAction("Reset")
- return gui.resetToRef(ref, strength, []string{})
- },
- }
- }
-
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{
- Title: fmt.Sprintf("%s %s", gui.Tr.LcResetTo, ref),
- Items: menuItems,
- })
-}
diff --git a/pkg/gui/searching.go b/pkg/gui/searching.go
index dd7697363..25a3ff63a 100644
--- a/pkg/gui/searching.go
+++ b/pkg/gui/searching.go
@@ -17,7 +17,7 @@ func (gui *Gui) handleOpenSearch(viewName string) error {
gui.Views.Search.ClearTextArea()
- if err := gui.pushContext(gui.State.Contexts.Search); err != nil {
+ if err := gui.c.PushContext(gui.State.Contexts.Search); err != nil {
return err
}
@@ -43,7 +43,7 @@ func (gui *Gui) handleSearch() error {
}
func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, int) error {
- keybindingConfig := gui.UserConfig.Keybinding
+ keybindingConfig := gui.c.UserConfig.Keybinding
return func(y int, index int, total int) error {
if total == 0 {
diff --git a/pkg/gui/staging_panel.go b/pkg/gui/staging_panel.go
index f4cd98ea2..f04f1e3ff 100644
--- a/pkg/gui/staging_panel.go
+++ b/pkg/gui/staging_panel.go
@@ -28,16 +28,16 @@ func (gui *Gui) refreshStagingPanel(forceSecondaryFocused bool, selectedLineIdx
}
if secondaryFocused {
- gui.Views.Main.Title = gui.Tr.StagedChanges
- gui.Views.Secondary.Title = gui.Tr.UnstagedChanges
+ gui.Views.Main.Title = gui.c.Tr.StagedChanges
+ gui.Views.Secondary.Title = gui.c.Tr.UnstagedChanges
} else {
- gui.Views.Main.Title = gui.Tr.UnstagedChanges
- gui.Views.Secondary.Title = gui.Tr.StagedChanges
+ gui.Views.Main.Title = gui.c.Tr.UnstagedChanges
+ gui.Views.Secondary.Title = gui.c.Tr.StagedChanges
}
// note for custom diffs, we'll need to send a flag here saying not to use the custom diff
- diff := gui.Git.WorkingTree.WorktreeFileDiff(file, true, secondaryFocused, false)
- secondaryDiff := gui.Git.WorkingTree.WorktreeFileDiff(file, true, !secondaryFocused, false)
+ diff := gui.git.WorkingTree.WorktreeFileDiff(file, true, secondaryFocused, false)
+ secondaryDiff := gui.git.WorkingTree.WorktreeFileDiff(file, true, !secondaryFocused, false)
// 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
@@ -97,7 +97,7 @@ func (gui *Gui) handleTogglePanel() error {
func (gui *Gui) handleStagingEscape() error {
gui.escapeLineByLinePanel()
- return gui.pushContext(gui.State.Contexts.Files)
+ return gui.c.PushContext(gui.State.Contexts.Files)
}
func (gui *Gui) handleToggleStagedSelection() error {
@@ -113,10 +113,10 @@ func (gui *Gui) handleResetSelection() error {
return gui.applySelection(true, state)
}
- if !gui.UserConfig.Gui.SkipUnstageLineWarning {
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.UnstageLinesTitle,
- Prompt: gui.Tr.UnstageLinesPrompt,
+ if !gui.c.UserConfig.Gui.SkipUnstageLineWarning {
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.UnstageLinesTitle,
+ Prompt: gui.c.Tr.UnstageLinesPrompt,
HandleConfirm: func() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
return gui.applySelection(true, state)
@@ -148,17 +148,17 @@ func (gui *Gui) applySelection(reverse bool, state *LblPanelState) error {
if !reverse || state.SecondaryFocused {
applyFlags = append(applyFlags, "cached")
}
- gui.logAction(gui.Tr.Actions.ApplyPatch)
- err := gui.Git.WorkingTree.ApplyPatch(patch, applyFlags...)
+ gui.c.LogAction(gui.c.Tr.Actions.ApplyPatch)
+ err := gui.git.WorkingTree.ApplyPatch(patch, applyFlags...)
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
if state.SelectingRange() {
state.SetLineSelectMode()
}
- if err := gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
+ if err := gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
return err
}
if err := gui.refreshStagingPanel(false, -1); err != nil {
diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go
index f8121bf94..994548b0e 100644
--- a/pkg/gui/stash_panel.go
+++ b/pkg/gui/stash_panel.go
@@ -2,6 +2,7 @@ package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/gui/controllers"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -21,9 +22,9 @@ func (gui *Gui) stashRenderToMain() error {
var task updateTask
stashEntry := gui.getSelectedStashEntry()
if stashEntry == nil {
- task = NewRenderStringTask(gui.Tr.NoStashEntries)
+ task = NewRenderStringTask(gui.c.Tr.NoStashEntries)
} else {
- task = NewRunPtyTask(gui.Git.Stash.ShowStashEntryCmdObj(stashEntry.Index).GetCmd())
+ task = NewRunPtyTask(gui.git.Stash.ShowStashEntryCmdObj(stashEntry.Index).GetCmd())
}
return gui.refreshMainViews(refreshMainOpts{
@@ -35,7 +36,7 @@ func (gui *Gui) stashRenderToMain() error {
}
func (gui *Gui) refreshStashEntries() error {
- gui.State.StashEntries = gui.Git.Loaders.Stash.
+ gui.State.StashEntries = gui.git.Loaders.Stash.
GetStashEntries(gui.State.Modes.Filtering.GetPath())
return gui.postRefreshUpdate(gui.State.Contexts.Stash)
@@ -49,14 +50,14 @@ func (gui *Gui) handleStashApply() error {
return nil
}
- skipStashWarning := gui.UserConfig.Gui.SkipStashWarning
+ skipStashWarning := gui.c.UserConfig.Gui.SkipStashWarning
apply := func() error {
- gui.logAction(gui.Tr.Actions.Stash)
- err := gui.Git.Stash.Apply(stashEntry.Index)
+ gui.c.LogAction(gui.c.Tr.Actions.Stash)
+ err := gui.git.Stash.Apply(stashEntry.Index)
_ = gui.postStashRefresh()
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
return nil
}
@@ -65,9 +66,9 @@ func (gui *Gui) handleStashApply() error {
return apply()
}
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.StashApply,
- Prompt: gui.Tr.SureApplyStashEntry,
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.StashApply,
+ Prompt: gui.c.Tr.SureApplyStashEntry,
HandleConfirm: func() error {
return apply()
},
@@ -80,14 +81,14 @@ func (gui *Gui) handleStashPop() error {
return nil
}
- skipStashWarning := gui.UserConfig.Gui.SkipStashWarning
+ skipStashWarning := gui.c.UserConfig.Gui.SkipStashWarning
pop := func() error {
- gui.logAction(gui.Tr.Actions.Stash)
- err := gui.Git.Stash.Pop(stashEntry.Index)
+ gui.c.LogAction(gui.c.Tr.Actions.Stash)
+ err := gui.git.Stash.Pop(stashEntry.Index)
_ = gui.postStashRefresh()
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
return nil
}
@@ -96,9 +97,9 @@ func (gui *Gui) handleStashPop() error {
return pop()
}
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.StashPop,
- Prompt: gui.Tr.SurePopStashEntry,
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.StashPop,
+ Prompt: gui.c.Tr.SurePopStashEntry,
HandleConfirm: func() error {
return pop()
},
@@ -111,15 +112,15 @@ func (gui *Gui) handleStashDrop() error {
return nil
}
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.StashDrop,
- Prompt: gui.Tr.SureDropStashEntry,
+ return gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.StashDrop,
+ Prompt: gui.c.Tr.SureDropStashEntry,
HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.Stash)
- err := gui.Git.Stash.Drop(stashEntry.Index)
- _ = gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{STASH}})
+ gui.c.LogAction(gui.c.Tr.Actions.Stash)
+ err := gui.git.Stash.Drop(stashEntry.Index)
+ _ = gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}})
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
return nil
},
@@ -127,25 +128,7 @@ func (gui *Gui) handleStashDrop() error {
}
func (gui *Gui) postStashRefresh() error {
- return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}})
-}
-
-func (gui *Gui) handleStashSave(stashFunc func(message string) error) error {
- if len(gui.trackedFiles()) == 0 && len(gui.stagedFiles()) == 0 {
- return gui.PopupHandler.ErrorMsg(gui.Tr.NoTrackedStagedFilesStash)
- }
-
- return gui.prompt(promptOpts{
- title: gui.Tr.StashChanges,
- handleConfirm: func(stashComment string) error {
- err := stashFunc(stashComment)
- _ = gui.postStashRefresh()
- if err != nil {
- return gui.PopupHandler.Error(err)
- }
- return nil
- },
- })
+ return gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}})
}
func (gui *Gui) handleViewStashFiles() error {
@@ -154,5 +137,10 @@ func (gui *Gui) handleViewStashFiles() error {
return nil
}
- return gui.switchToCommitFilesContext(stashEntry.RefName(), false, gui.State.Contexts.Stash, "stash")
+ return gui.SwitchToCommitFilesContext(controllers.SwitchToCommitFilesContextOpts{
+ RefName: stashEntry.RefName(),
+ CanRebase: false,
+ Context: gui.State.Contexts.Stash,
+ WindowName: "stash",
+ })
}
diff --git a/pkg/gui/status_panel.go b/pkg/gui/status_panel.go
index d9fff2913..15c15987e 100644
--- a/pkg/gui/status_panel.go
+++ b/pkg/gui/status_panel.go
@@ -18,7 +18,7 @@ func (gui *Gui) refreshStatus() {
gui.Mutexes.RefreshingStatusMutex.Lock()
defer gui.Mutexes.RefreshingStatusMutex.Unlock()
- currentBranch := gui.currentBranch()
+ currentBranch := gui.getCheckedOutBranch()
if currentBranch == nil {
// need to wait for branches to refresh
return
@@ -29,7 +29,7 @@ func (gui *Gui) refreshStatus() {
status += presentation.ColoredBranchStatus(currentBranch) + " "
}
- workingTreeState := gui.Git.Status.WorkingTreeState()
+ workingTreeState := gui.git.Status.WorkingTreeState()
if workingTreeState != enums.REBASE_MODE_NONE {
status += style.FgYellow.Sprintf("(%s) ", formatWorkingTreeState(workingTreeState))
}
@@ -50,7 +50,7 @@ func cursorInSubstring(cx int, prefix string, substring string) bool {
}
func (gui *Gui) handleCheckForUpdate() error {
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.CheckingForUpdates, func() error {
+ return gui.c.WithWaitingStatus(gui.c.Tr.CheckingForUpdates, func() error {
gui.Updater.CheckForNewUpdate(gui.onUserUpdateCheckFinish, true)
return nil
})
@@ -62,20 +62,20 @@ func (gui *Gui) handleStatusClick() error {
return nil
}
- currentBranch := gui.currentBranch()
+ currentBranch := gui.getCheckedOutBranch()
if currentBranch == nil {
// need to wait for branches to refresh
return nil
}
- if err := gui.pushContext(gui.State.Contexts.Status); err != nil {
+ if err := gui.c.PushContext(gui.State.Contexts.Status); err != nil {
return err
}
cx, _ := gui.Views.Status.Cursor()
upstreamStatus := presentation.BranchStatus(currentBranch)
repoName := utils.GetCurrentRepoName()
- workingTreeState := gui.Git.Status.WorkingTreeState()
+ workingTreeState := gui.git.Status.WorkingTreeState()
switch workingTreeState {
case enums.REBASE_MODE_REBASING, enums.REBASE_MODE_MERGING:
workingTreeStatus := fmt.Sprintf("(%s)", formatWorkingTreeState(workingTreeState))
@@ -135,7 +135,7 @@ func (gui *Gui) askForConfigFile(action func(file string) error) error {
confPaths := gui.Config.GetUserConfigPaths()
switch len(confPaths) {
case 0:
- return errors.New(gui.Tr.NoConfigFileFoundErr)
+ return errors.New(gui.c.Tr.NoConfigFileFoundErr)
case 1:
return action(confPaths[0])
default:
@@ -149,8 +149,8 @@ func (gui *Gui) askForConfigFile(action func(file string) error) error {
},
}
}
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{
- Title: gui.Tr.SelectConfigFile,
+ return gui.c.Menu(popup.CreateMenuOptions{
+ Title: gui.c.Tr.SelectConfigFile,
Items: menuItems,
HideCancel: true,
})
@@ -158,11 +158,11 @@ func (gui *Gui) askForConfigFile(action func(file string) error) error {
}
func (gui *Gui) handleOpenConfig() error {
- return gui.askForConfigFile(gui.openFile)
+ return gui.askForConfigFile(gui.fileHelper.OpenFile)
}
func (gui *Gui) handleEditConfig() error {
- return gui.askForConfigFile(gui.editFile)
+ return gui.askForConfigFile(gui.fileHelper.EditFile)
}
func lazygitTitle() string {
diff --git a/pkg/gui/style/style_test.go b/pkg/gui/style/style_test.go
index 360ad00e6..c8157efd6 100644
--- a/pkg/gui/style/style_test.go
+++ b/pkg/gui/style/style_test.go
@@ -135,7 +135,7 @@ func TestMerge(t *testing.T) {
"\x1b[38;2;255;0;255;48;2;255;255;0;1;4mfoo\x1b[0m",
},
{
- "mix color-16 with rgb colors",
+ "mix color-16 (background) with rgb (foreground)",
[]TextStyle{New().SetFg(rgbYellow), BgRed},
TextStyle{
fg: &rgbYellow,
@@ -147,6 +147,19 @@ func TestMerge(t *testing.T) {
},
"\x1b[38;2;255;255;0;48;2;197;30;20mfoo\x1b[0m",
},
+ {
+ "mix color-16 (foreground) with rgb (background)",
+ []TextStyle{FgRed, New().SetBg(rgbYellow)},
+ TextStyle{
+ fg: &Color{basic: &fgRed},
+ bg: &rgbYellow,
+ Style: color.NewRGBStyle(
+ fgRed.RGB(),
+ rgbYellowLib,
+ ).SetOpts(color.Opts{}),
+ },
+ "\x1b[38;2;197;30;20;48;2;255;255;0mfoo\x1b[0m",
+ },
}
for _, s := range scenarios {
diff --git a/pkg/gui/sub_commits_panel.go b/pkg/gui/sub_commits_panel.go
index 97f57d158..c3b83d122 100644
--- a/pkg/gui/sub_commits_panel.go
+++ b/pkg/gui/sub_commits_panel.go
@@ -3,7 +3,9 @@ package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/gui/controllers"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
)
// list panel functions
@@ -24,7 +26,7 @@ func (gui *Gui) subCommitsRenderToMain() error {
if commit == nil {
task = NewRenderStringTask("No commits")
} else {
- cmdObj := gui.Git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
+ cmdObj := gui.git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
task = NewRunPtyTask(cmdObj.GetCmd())
}
@@ -43,19 +45,19 @@ func (gui *Gui) handleCheckoutSubCommit() error {
return nil
}
- err := gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.LcCheckoutCommit,
- Prompt: gui.Tr.SureCheckoutThisCommit,
+ err := gui.c.Ask(popup.AskOpts{
+ Title: gui.c.Tr.LcCheckoutCommit,
+ Prompt: gui.c.Tr.SureCheckoutThisCommit,
HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.CheckoutCommit)
- return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{})
+ gui.c.LogAction(gui.c.Tr.Actions.CheckoutCommit)
+ return gui.refHelper.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
},
})
if err != nil {
return err
}
- gui.State.Panels.SubCommits.SelectedLineIdx = 0
+ gui.State.Contexts.SubCommits.GetPanelState().SetSelectedLineIdx(0)
return nil
}
@@ -63,7 +65,7 @@ func (gui *Gui) handleCheckoutSubCommit() error {
func (gui *Gui) handleCreateSubCommitResetMenu() error {
commit := gui.getSelectedSubCommit()
- return gui.createResetMenu(commit.Sha)
+ return gui.refHelper.CreateGitResetMenu(commit.Sha)
}
func (gui *Gui) handleViewSubCommitFiles() error {
@@ -72,12 +74,17 @@ func (gui *Gui) handleViewSubCommitFiles() error {
return nil
}
- return gui.switchToCommitFilesContext(commit.Sha, false, gui.State.Contexts.SubCommits, "branches")
+ return gui.SwitchToCommitFilesContext(controllers.SwitchToCommitFilesContextOpts{
+ RefName: commit.Sha,
+ CanRebase: false,
+ Context: gui.State.Contexts.SubCommits,
+ WindowName: "branches",
+ })
}
func (gui *Gui) switchToSubCommitsContext(refName string) error {
// need to populate my sub commits
- commits, err := gui.Git.Loaders.Commits.GetCommits(
+ commits, err := gui.git.Loaders.Commits.GetCommits(
loaders.GetCommitsOptions{
Limit: gui.State.Panels.Commits.LimitCommits,
FilterPath: gui.State.Modes.Filtering.GetPath(),
@@ -91,10 +98,10 @@ func (gui *Gui) switchToSubCommitsContext(refName string) error {
gui.State.SubCommits = commits
gui.State.Panels.SubCommits.refName = refName
- gui.State.Panels.SubCommits.SelectedLineIdx = 0
+ gui.State.Contexts.SubCommits.GetPanelState().SetSelectedLineIdx(0)
gui.State.Contexts.SubCommits.SetParentContext(gui.currentSideListContext())
- return gui.pushContext(gui.State.Contexts.SubCommits)
+ return gui.c.PushContext(gui.State.Contexts.SubCommits)
}
func (gui *Gui) handleSwitchToSubCommits() error {
diff --git a/pkg/gui/submodules_panel.go b/pkg/gui/submodules_panel.go
index e63634bc6..7c29c7840 100644
--- a/pkg/gui/submodules_panel.go
+++ b/pkg/gui/submodules_panel.go
@@ -30,11 +30,11 @@ func (gui *Gui) submodulesRenderToMain() error {
style.FgCyan.Sprint(submodule.Url),
)
- file := gui.fileForSubmodule(submodule)
+ file := gui.workingTreeHelper.FileForSubmodule(submodule)
if file == nil {
task = NewRenderStringTask(prefix)
} else {
- cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, gui.IgnoreWhitespaceInDiffView)
+ cmdObj := gui.git.WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, gui.IgnoreWhitespaceInDiffView)
task = NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix)
}
}
@@ -48,7 +48,7 @@ func (gui *Gui) submodulesRenderToMain() error {
}
func (gui *Gui) refreshStateSubmoduleConfigs() error {
- configs, err := gui.Git.Submodule.GetConfigs()
+ configs, err := gui.git.Submodule.GetConfigs()
if err != nil {
return err
}
diff --git a/pkg/gui/find_suggestions.go b/pkg/gui/suggestions_helper.go
similarity index 61%
rename from pkg/gui/find_suggestions.go
rename to pkg/gui/suggestions_helper.go
index 75215d673..8a871cf20 100644
--- a/pkg/gui/find_suggestions.go
+++ b/pkg/gui/suggestions_helper.go
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
+ "github.com/jesseduffield/lazygit/pkg/gui/controllers"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -21,9 +22,30 @@ import (
// finding suggestions in this file, so that it's easy to see if a function already
// exists for fetching a particular model.
-func (gui *Gui) getRemoteNames() []string {
- result := make([]string, len(gui.State.Remotes))
- for i, remote := range gui.State.Remotes {
+type SuggestionsHelper struct {
+ c *controllers.ControllerCommon
+
+ State *GuiRepoState
+ refreshSuggestionsFn func()
+}
+
+var _ controllers.ISuggestionsHelper = &SuggestionsHelper{}
+
+func NewSuggestionsHelper(
+ c *controllers.ControllerCommon,
+ state *GuiRepoState,
+ refreshSuggestionsFn func(),
+) *SuggestionsHelper {
+ return &SuggestionsHelper{
+ c: c,
+ State: state,
+ refreshSuggestionsFn: refreshSuggestionsFn,
+ }
+}
+
+func (self *SuggestionsHelper) getRemoteNames() []string {
+ result := make([]string, len(self.State.Remotes))
+ for i, remote := range self.State.Remotes {
result[i] = remote.Name
}
return result
@@ -40,22 +62,22 @@ func matchesToSuggestions(matches []string) []*types.Suggestion {
return suggestions
}
-func (gui *Gui) getRemoteSuggestionsFunc() func(string) []*types.Suggestion {
- remoteNames := gui.getRemoteNames()
+func (self *SuggestionsHelper) GetRemoteSuggestionsFunc() func(string) []*types.Suggestion {
+ remoteNames := self.getRemoteNames()
return fuzzySearchFunc(remoteNames)
}
-func (gui *Gui) getBranchNames() []string {
- result := make([]string, len(gui.State.Branches))
- for i, branch := range gui.State.Branches {
+func (self *SuggestionsHelper) getBranchNames() []string {
+ result := make([]string, len(self.State.Branches))
+ for i, branch := range self.State.Branches {
result[i] = branch.Name
}
return result
}
-func (gui *Gui) getBranchNameSuggestionsFunc() func(string) []*types.Suggestion {
- branchNames := gui.getBranchNames()
+func (self *SuggestionsHelper) GetBranchNameSuggestionsFunc() func(string) []*types.Suggestion {
+ branchNames := self.getBranchNames()
return func(input string) []*types.Suggestion {
var matchingBranchNames []string
@@ -78,13 +100,13 @@ func (gui *Gui) getBranchNameSuggestionsFunc() func(string) []*types.Suggestion
}
// here we asynchronously fetch the latest set of paths in the repo and store in
-// gui.State.FilesTrie. On the main thread we'll be doing a fuzzy search via
-// gui.State.FilesTrie. So if we've looked for a file previously, we'll start with
+// self.State.FilesTrie. On the main thread we'll be doing a fuzzy search via
+// self.State.FilesTrie. So if we've looked for a file previously, we'll start with
// the old trie and eventually it'll be swapped out for the new one.
// Notably, unlike other suggestion functions we're not showing all the options
// if nothing has been typed because there'll be too much to display efficiently
-func (gui *Gui) getFilePathSuggestionsFunc() func(string) []*types.Suggestion {
- _ = gui.PopupHandler.WithWaitingStatus(gui.Tr.LcLoadingFileSuggestions, func() error {
+func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*types.Suggestion {
+ _ = self.c.WithWaitingStatus(self.c.Tr.LcLoadingFileSuggestions, func() error {
trie := patricia.NewTrie()
// load every non-gitignored file in the repo
ignore, err := gitignore.FromGit()
@@ -101,22 +123,16 @@ func (gui *Gui) getFilePathSuggestionsFunc() func(string) []*types.Suggestion {
return nil
})
// cache the trie for future use
- gui.State.FilesTrie = trie
+ self.State.FilesTrie = trie
- // refresh the selections view
- gui.suggestionsAsyncHandler.Do(func() func() {
- // assuming here that the confirmation view is what we're typing into.
- // This assumption may prove false over time
- suggestions := gui.findSuggestions(gui.Views.Confirmation.TextArea.GetContent())
- return func() { gui.setSuggestions(suggestions) }
- })
+ self.refreshSuggestionsFn()
return err
})
return func(input string) []*types.Suggestion {
matchingNames := []string{}
- _ = gui.State.FilesTrie.VisitFuzzy(patricia.Prefix(input), true, func(prefix patricia.Prefix, item patricia.Item, skipped int) error {
+ _ = self.State.FilesTrie.VisitFuzzy(patricia.Prefix(input), true, func(prefix patricia.Prefix, item patricia.Item, skipped int) error {
matchingNames = append(matchingNames, item.(string))
return nil
})
@@ -136,9 +152,9 @@ func (gui *Gui) getFilePathSuggestionsFunc() func(string) []*types.Suggestion {
}
}
-func (gui *Gui) getRemoteBranchNames(separator string) []string {
+func (self *SuggestionsHelper) getRemoteBranchNames(separator string) []string {
result := []string{}
- for _, remote := range gui.State.Remotes {
+ for _, remote := range self.State.Remotes {
for _, branch := range remote.Branches {
result = append(result, fmt.Sprintf("%s%s%s", remote.Name, separator, branch.Name))
}
@@ -146,22 +162,22 @@ func (gui *Gui) getRemoteBranchNames(separator string) []string {
return result
}
-func (gui *Gui) getRemoteBranchesSuggestionsFunc(separator string) func(string) []*types.Suggestion {
- return fuzzySearchFunc(gui.getRemoteBranchNames(separator))
+func (self *SuggestionsHelper) GetRemoteBranchesSuggestionsFunc(separator string) func(string) []*types.Suggestion {
+ return fuzzySearchFunc(self.getRemoteBranchNames(separator))
}
-func (gui *Gui) getTagNames() []string {
- result := make([]string, len(gui.State.Tags))
- for i, tag := range gui.State.Tags {
+func (self *SuggestionsHelper) getTagNames() []string {
+ result := make([]string, len(self.State.Tags))
+ for i, tag := range self.State.Tags {
result[i] = tag.Name
}
return result
}
-func (gui *Gui) getRefsSuggestionsFunc() func(string) []*types.Suggestion {
- remoteBranchNames := gui.getRemoteBranchNames("/")
- localBranchNames := gui.getBranchNames()
- tagNames := gui.getTagNames()
+func (self *SuggestionsHelper) GetRefsSuggestionsFunc() func(string) []*types.Suggestion {
+ remoteBranchNames := self.getRemoteBranchNames("/")
+ localBranchNames := self.getBranchNames()
+ tagNames := self.getTagNames()
additionalRefNames := []string{"HEAD", "FETCH_HEAD", "MERGE_HEAD", "ORIG_HEAD"}
refNames := append(append(append(remoteBranchNames, localBranchNames...), tagNames...), additionalRefNames...)
@@ -169,9 +185,9 @@ func (gui *Gui) getRefsSuggestionsFunc() func(string) []*types.Suggestion {
return fuzzySearchFunc(refNames)
}
-func (gui *Gui) getCustomCommandsHistorySuggestionsFunc() func(string) []*types.Suggestion {
+func (self *SuggestionsHelper) GetCustomCommandsHistorySuggestionsFunc() func(string) []*types.Suggestion {
// reversing so that we display the latest command first
- history := utils.Reverse(gui.Config.GetAppState().CustomCommandsHistory)
+ history := utils.Reverse(self.c.GetAppState().CustomCommandsHistory)
return fuzzySearchFunc(history)
}
diff --git a/pkg/gui/tags_panel.go b/pkg/gui/tags_panel.go
index 9f516a006..9e00dd122 100644
--- a/pkg/gui/tags_panel.go
+++ b/pkg/gui/tags_panel.go
@@ -2,36 +2,28 @@ package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
- "github.com/jesseduffield/lazygit/pkg/gui/popup"
- "github.com/jesseduffield/lazygit/pkg/gui/types"
- "github.com/jesseduffield/lazygit/pkg/utils"
)
-func (gui *Gui) getSelectedTag() *models.Tag {
- selectedLine := gui.State.Panels.Tags.SelectedLineIdx
- if selectedLine == -1 || len(gui.State.Tags) == 0 {
+func (self *Gui) getSelectedTag() *models.Tag {
+ selectedLine := self.State.Panels.Tags.SelectedLineIdx
+ if selectedLine == -1 || len(self.State.Tags) == 0 {
return nil
}
- return gui.State.Tags[selectedLine]
+ return self.State.Tags[selectedLine]
}
-func (gui *Gui) handleCreateTag() error {
- // leaving commit SHA blank so that we're just creating the tag for the current commit
- return gui.createTagMenu("")
-}
-
-func (gui *Gui) tagsRenderToMain() error {
+func (self *Gui) tagsRenderToMain() error {
var task updateTask
- tag := gui.getSelectedTag()
+ tag := self.getSelectedTag()
if tag == nil {
task = NewRenderStringTask("No tags")
} else {
- cmdObj := gui.Git.Branch.GetGraphCmdObj(tag.Name)
+ cmdObj := self.git.Branch.GetGraphCmdObj(tag.Name)
task = NewRunCommandTask(cmdObj.GetCmd())
}
- return gui.refreshMainViews(refreshMainOpts{
+ return self.refreshMainViews(refreshMainOpts{
main: &viewUpdateOpts{
title: "Tag",
task: task,
@@ -40,85 +32,13 @@ func (gui *Gui) tagsRenderToMain() error {
}
// this is a controller: it can't access tags directly. Or can it? It should be able to get but not set. But that's exactly what I'm doing here, setting it. but through a mutator which encapsulates the event.
-func (gui *Gui) refreshTags() error {
- tags, err := gui.Git.Loaders.Tags.GetTags()
+func (self *Gui) refreshTags() error {
+ tags, err := self.git.Loaders.Tags.GetTags()
if err != nil {
- return gui.PopupHandler.Error(err)
+ return self.c.Error(err)
}
- gui.State.Tags = tags
+ self.State.Tags = tags
- return gui.postRefreshUpdate(gui.State.Contexts.Tags)
-}
-
-func (gui *Gui) withSelectedTag(f func(tag *models.Tag) error) func() error {
- return func() error {
- tag := gui.getSelectedTag()
- if tag == nil {
- return nil
- }
-
- return f(tag)
- }
-}
-
-// tag-specific handlers
-
-func (gui *Gui) handleCheckoutTag(tag *models.Tag) error {
- gui.logAction(gui.Tr.Actions.CheckoutTag)
- if err := gui.handleCheckoutRef(tag.Name, handleCheckoutRefOptions{}); err != nil {
- return err
- }
- return gui.pushContext(gui.State.Contexts.Branches)
-}
-
-func (gui *Gui) handleDeleteTag(tag *models.Tag) error {
- prompt := utils.ResolvePlaceholderString(
- gui.Tr.DeleteTagPrompt,
- map[string]string{
- "tagName": tag.Name,
- },
- )
-
- return gui.PopupHandler.Ask(popup.AskOpts{
- Title: gui.Tr.DeleteTagTitle,
- Prompt: prompt,
- HandleConfirm: func() error {
- gui.logAction(gui.Tr.Actions.DeleteTag)
- if err := gui.Git.Tag.Delete(tag.Name); err != nil {
- return gui.PopupHandler.Error(err)
- }
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
- },
- })
-}
-
-func (gui *Gui) handlePushTag(tag *models.Tag) error {
- title := utils.ResolvePlaceholderString(
- gui.Tr.PushTagTitle,
- map[string]string{
- "tagName": tag.Name,
- },
- )
-
- return gui.PopupHandler.Prompt(popup.PromptOpts{
- Title: title,
- InitialContent: "origin",
- FindSuggestionsFunc: gui.getRemoteSuggestionsFunc(),
- HandleConfirm: func(response string) error {
- return gui.PopupHandler.WithWaitingStatus(gui.Tr.PushingTagStatus, func() error {
- gui.logAction(gui.Tr.Actions.PushTag)
- err := gui.Git.Tag.Push(response, tag.Name)
- if err != nil {
- _ = gui.PopupHandler.Error(err)
- }
-
- return nil
- })
- },
- })
-}
-
-func (gui *Gui) handleCreateResetToTagMenu(tag *models.Tag) error {
- return gui.createResetMenu(tag.Name)
+ return self.postRefreshUpdate(self.State.Contexts.Tags)
}
diff --git a/pkg/gui/tasks_adapter.go b/pkg/gui/tasks_adapter.go
index 4c268c14d..d619c58ba 100644
--- a/pkg/gui/tasks_adapter.go
+++ b/pkg/gui/tasks_adapter.go
@@ -11,7 +11,7 @@ import (
func (gui *Gui) newCmdTask(view *gocui.View, cmd *exec.Cmd, prefix string) error {
cmdStr := strings.Join(cmd.Args, " ")
- gui.Log.WithField(
+ gui.c.Log.WithField(
"command",
cmdStr,
).Debug("RunCommand")
@@ -24,19 +24,19 @@ func (gui *Gui) newCmdTask(view *gocui.View, cmd *exec.Cmd, prefix string) error
start := func() (*exec.Cmd, io.Reader) {
r, err := cmd.StdoutPipe()
if err != nil {
- gui.Log.Warn(err)
+ gui.c.Log.Warn(err)
}
cmd.Stderr = cmd.Stdout
if err := cmd.Start(); err != nil {
- gui.Log.Warn(err)
+ gui.c.Log.Warn(err)
}
return cmd, r
}
if err := manager.NewTask(manager.NewCmdTask(start, prefix, height+oy+10, nil), cmdStr); err != nil {
- gui.Log.Warn(err)
+ gui.c.Log.Warn(err)
}
return nil
diff --git a/pkg/gui/types/common_commands.go b/pkg/gui/types/common_commands.go
new file mode 100644
index 000000000..74bfd603b
--- /dev/null
+++ b/pkg/gui/types/common_commands.go
@@ -0,0 +1,7 @@
+package types
+
+type CheckoutRefOptions struct {
+ WaitingStatus string
+ EnvVars []string
+ OnRefNotFound func(ref string) error
+}
diff --git a/pkg/gui/types/context.go b/pkg/gui/types/context.go
new file mode 100644
index 000000000..833d1cba6
--- /dev/null
+++ b/pkg/gui/types/context.go
@@ -0,0 +1,87 @@
+package types
+
+import "github.com/jesseduffield/lazygit/pkg/config"
+
+type ContextKind int
+
+const (
+ SIDE_CONTEXT ContextKind = iota
+ MAIN_CONTEXT
+ TEMPORARY_POPUP
+ PERSISTENT_POPUP
+ EXTRAS_CONTEXT
+)
+
+type Context interface {
+ HandleFocus(opts ...OnFocusOpts) error
+ HandleFocusLost() error
+ HandleRender() error
+ HandleRenderToMain() error
+ GetKind() ContextKind
+ GetViewName() string
+ GetWindowName() string
+ SetWindowName(string)
+ GetKey() ContextKey
+ SetParentContext(Context)
+
+ // we return a bool here to tell us whether or not the returned value just wraps a nil
+ GetParentContext() (Context, bool)
+ GetOptionsMap() map[string]string
+}
+
+type OnFocusOpts struct {
+ ClickedViewName string
+ ClickedViewLineIdx int
+}
+
+type ContextKey string
+
+type HasKeybindings interface {
+ Keybindings(
+ getKey func(key string) interface{},
+ config config.KeybindingConfig,
+ guards KeybindingGuards,
+ ) []*Binding
+}
+
+type IController interface {
+ HasKeybindings
+ Context() Context
+}
+
+type IListContext interface {
+ HasKeybindings
+ GetSelectedItem() (ListItem, bool)
+ GetSelectedItemId() string
+
+ HandlePrevLine() error
+ HandleNextLine() error
+ HandleScrollLeft() error
+ HandleScrollRight() error
+ HandleNextPage() error
+ HandleGotoTop() error
+ HandleGotoBottom() error
+ HandlePrevPage() error
+ HandleClick(onClick func() error) error
+
+ OnSearchSelect(selectedLineIdx int) error
+ FocusLine()
+ HandleRenderToMain() error
+
+ GetPanelState() IListPanelState
+
+ Context
+}
+
+type IListPanelState interface {
+ SetSelectedLineIdx(int)
+ GetSelectedLineIdx() int
+}
+
+type ListItem interface {
+ // ID is a SHA when the item is a commit, a filename when the item is a file, 'stash@{4}' when it's a stash entry, 'my_branch' when it's a branch
+ ID() string
+
+ // Description is something we would show in a message e.g. '123as14: push blah' for a commit
+ Description() string
+}
diff --git a/pkg/gui/types/keybindings.go b/pkg/gui/types/keybindings.go
index abe3f84d0..7d1befc1b 100644
--- a/pkg/gui/types/keybindings.go
+++ b/pkg/gui/types/keybindings.go
@@ -16,3 +16,12 @@ type Binding struct {
Tag string // e.g. 'navigation'. Used for grouping things in the cheatsheet
OpensMenu bool
}
+
+// A guard is a decorator which checks something before executing a handler
+// and potentially early-exits if some precondition hasn't been met.
+type Guard func(func() error) func() error
+
+type KeybindingGuards struct {
+ OutsideFilterMode Guard
+ NoPopupPanel Guard
+}
diff --git a/pkg/gui/types/refresh.go b/pkg/gui/types/refresh.go
index d0cbe02ba..3a7e6db17 100644
--- a/pkg/gui/types/refresh.go
+++ b/pkg/gui/types/refresh.go
@@ -5,6 +5,7 @@ type RefreshableView int
const (
COMMITS RefreshableView = iota
+ REBASE_COMMITS
BRANCHES
FILES
STASH
diff --git a/pkg/gui/updates.go b/pkg/gui/updates.go
index 5eb08dae3..f3dc14b1d 100644
--- a/pkg/gui/updates.go
+++ b/pkg/gui/updates.go
@@ -8,7 +8,7 @@ import (
)
func (gui *Gui) showUpdatePrompt(newVersion string) error {
- return gui.PopupHandler.Ask(popup.AskOpts{
+ return gui.c.Ask(popup.AskOpts{
Title: "New version available!",
Prompt: fmt.Sprintf("Download version %s? (enter/esc)", newVersion),
HandleConfirm: func() error {
@@ -20,10 +20,10 @@ func (gui *Gui) showUpdatePrompt(newVersion string) error {
func (gui *Gui) onUserUpdateCheckFinish(newVersion string, err error) error {
if err != nil {
- return gui.PopupHandler.Error(err)
+ return gui.c.Error(err)
}
if newVersion == "" {
- return gui.PopupHandler.ErrorMsg("New version not found")
+ return gui.c.ErrorMsg("New version not found")
}
return gui.showUpdatePrompt(newVersion)
}
@@ -31,13 +31,13 @@ func (gui *Gui) onUserUpdateCheckFinish(newVersion string, err error) error {
func (gui *Gui) onBackgroundUpdateCheckFinish(newVersion string, err error) error {
if err != nil {
// ignoring the error for now so that I'm not annoying users
- gui.Log.Error(err.Error())
+ gui.c.Log.Error(err.Error())
return nil
}
if newVersion == "" {
return nil
}
- if gui.UserConfig.Update.Method == "background" {
+ if gui.c.UserConfig.Update.Method == "background" {
gui.startUpdating(newVersion)
return nil
}
@@ -56,7 +56,7 @@ func (gui *Gui) onUpdateFinish(statusId int, err error) error {
gui.OnUIThread(func() error {
_ = gui.renderString(gui.Views.AppStatus, "")
if err != nil {
- return gui.PopupHandler.ErrorMsg("Update failed: " + err.Error())
+ return gui.c.ErrorMsg("Update failed: " + err.Error())
}
return nil
})
@@ -65,7 +65,7 @@ func (gui *Gui) onUpdateFinish(statusId int, err error) error {
}
func (gui *Gui) createUpdateQuitConfirmation() error {
- return gui.PopupHandler.Ask(popup.AskOpts{
+ return gui.c.Ask(popup.AskOpts{
Title: "Currently Updating",
Prompt: "An update is in progress. Are you sure you want to quit?",
HandleConfirm: func() error {
diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go
index 089cee454..29dd70048 100644
--- a/pkg/gui/view_helpers.go
+++ b/pkg/gui/view_helpers.go
@@ -59,14 +59,14 @@ func arrToMap(arr []types.RefreshableView) map[types.RefreshableView]bool {
return output
}
-func (gui *Gui) refreshSidePanels(options types.RefreshOptions) error {
+func (gui *Gui) Refresh(options types.RefreshOptions) error {
if options.Scope == nil {
- gui.Log.Infof(
+ gui.c.Log.Infof(
"refreshing all scopes in %s mode",
getModeName(options.Mode),
)
} else {
- gui.Log.Infof(
+ gui.c.Log.Infof(
"refreshing the following scopes in %s mode: %s",
getModeName(options.Mode),
strings.Join(getScopeNames(options.Scope), ","),
@@ -78,69 +78,55 @@ func (gui *Gui) refreshSidePanels(options types.RefreshOptions) error {
f := func() {
var scopeMap map[types.RefreshableView]bool
if len(options.Scope) == 0 {
- scopeMap = arrToMap([]types.RefreshableView{types.COMMITS, types.BRANCHES, types.FILES, types.STASH, types.REFLOG, types.TAGS, types.REMOTES, types.STATUS, types.BISECT_INFO})
+ scopeMap = arrToMap([]types.RefreshableView{
+ types.COMMITS,
+ types.BRANCHES,
+ types.FILES,
+ types.STASH,
+ types.REFLOG,
+ types.TAGS,
+ types.REMOTES,
+ types.STATUS,
+ types.BISECT_INFO,
+ })
} else {
scopeMap = arrToMap(options.Scope)
}
- if scopeMap[types.COMMITS] || scopeMap[types.BRANCHES] || scopeMap[types.REFLOG] || scopeMap[types.BISECT_INFO] {
+ refresh := func(f func()) {
wg.Add(1)
func() {
if options.Mode == types.ASYNC {
- go utils.Safe(func() { gui.refreshCommits() })
+ go utils.Safe(f)
} else {
- gui.refreshCommits()
+ f()
}
wg.Done()
}()
}
+ if scopeMap[types.COMMITS] || scopeMap[types.BRANCHES] || scopeMap[types.REFLOG] || scopeMap[types.BISECT_INFO] {
+ refresh(gui.refreshCommits)
+ } else if scopeMap[types.REBASE_COMMITS] {
+ // the above block handles rebase commits so we only need to call this one
+ // if we've asked specifically for rebase commits and not those other things
+ refresh(func() { _ = gui.refreshRebaseCommits() })
+ }
+
if scopeMap[types.FILES] || scopeMap[types.SUBMODULES] {
- wg.Add(1)
- func() {
- if options.Mode == types.ASYNC {
- go utils.Safe(func() { _ = gui.refreshFilesAndSubmodules() })
- } else {
- _ = gui.refreshFilesAndSubmodules()
- }
- wg.Done()
- }()
+ refresh(func() { _ = gui.refreshFilesAndSubmodules() })
}
if scopeMap[types.STASH] {
- wg.Add(1)
- func() {
- if options.Mode == types.ASYNC {
- go utils.Safe(func() { _ = gui.refreshStashEntries() })
- } else {
- _ = gui.refreshStashEntries()
- }
- wg.Done()
- }()
+ refresh(func() { _ = gui.refreshStashEntries() })
}
if scopeMap[types.TAGS] {
- wg.Add(1)
- func() {
- if options.Mode == types.ASYNC {
- go utils.Safe(func() { _ = gui.refreshTags() })
- } else {
- _ = gui.refreshTags()
- }
- wg.Done()
- }()
+ refresh(func() { _ = gui.refreshTags() })
}
if scopeMap[types.REMOTES] {
- wg.Add(1)
- func() {
- if options.Mode == types.ASYNC {
- go utils.Safe(func() { _ = gui.refreshRemotes() })
- } else {
- _ = gui.refreshRemotes()
- }
- wg.Done()
- }()
+ refresh(func() { _ = gui.refreshRemotes() })
}
wg.Wait()
@@ -234,7 +220,7 @@ func (gui *Gui) resizePopupPanel(v *gocui.View, content string) error {
return err
}
-func (gui *Gui) changeSelectedLine(panelState IListPanelState, total int, change int) {
+func (gui *Gui) changeSelectedLine(panelState types.IListPanelState, total int, change int) {
// TODO: find out why we're doing this
line := panelState.GetSelectedLineIdx()
@@ -253,7 +239,7 @@ func (gui *Gui) changeSelectedLine(panelState IListPanelState, total int, change
panelState.SetSelectedLineIdx(newLine)
}
-func (gui *Gui) refreshSelectedLine(panelState IListPanelState, total int) {
+func (gui *Gui) refreshSelectedLine(panelState types.IListPanelState, total int) {
line := panelState.GetSelectedLineIdx()
if line == -1 && total > 0 {
@@ -274,16 +260,16 @@ func (gui *Gui) renderDisplayStringsAtPos(v *gocui.View, y int, displayStrings [
}
func (gui *Gui) globalOptionsMap() map[string]string {
- keybindingConfig := gui.UserConfig.Keybinding
+ keybindingConfig := gui.c.UserConfig.Keybinding
return map[string]string{
- fmt.Sprintf("%s/%s", gui.getKeyDisplay(keybindingConfig.Universal.ScrollUpMain), gui.getKeyDisplay(keybindingConfig.Universal.ScrollDownMain)): gui.Tr.LcScroll,
- fmt.Sprintf("%s %s %s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevBlock), gui.getKeyDisplay(keybindingConfig.Universal.NextBlock), gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.Tr.LcNavigate,
- gui.getKeyDisplay(keybindingConfig.Universal.Return): gui.Tr.LcCancel,
- gui.getKeyDisplay(keybindingConfig.Universal.Quit): gui.Tr.LcQuit,
- gui.getKeyDisplay(keybindingConfig.Universal.OptionMenu): gui.Tr.LcMenu,
- fmt.Sprintf("%s-%s", gui.getKeyDisplay(keybindingConfig.Universal.JumpToBlock[0]), gui.getKeyDisplay(keybindingConfig.Universal.JumpToBlock[len(keybindingConfig.Universal.JumpToBlock)-1])): gui.Tr.LcJump,
- fmt.Sprintf("%s/%s", gui.getKeyDisplay(keybindingConfig.Universal.ScrollLeft), gui.getKeyDisplay(keybindingConfig.Universal.ScrollRight)): gui.Tr.LcScrollLeftRight,
+ fmt.Sprintf("%s/%s", gui.getKeyDisplay(keybindingConfig.Universal.ScrollUpMain), gui.getKeyDisplay(keybindingConfig.Universal.ScrollDownMain)): gui.c.Tr.LcScroll,
+ fmt.Sprintf("%s %s %s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevBlock), gui.getKeyDisplay(keybindingConfig.Universal.NextBlock), gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
+ gui.getKeyDisplay(keybindingConfig.Universal.Return): gui.c.Tr.LcCancel,
+ gui.getKeyDisplay(keybindingConfig.Universal.Quit): gui.c.Tr.LcQuit,
+ gui.getKeyDisplay(keybindingConfig.Universal.OptionMenu): gui.c.Tr.LcMenu,
+ fmt.Sprintf("%s-%s", gui.getKeyDisplay(keybindingConfig.Universal.JumpToBlock[0]), gui.getKeyDisplay(keybindingConfig.Universal.JumpToBlock[len(keybindingConfig.Universal.JumpToBlock)-1])): gui.c.Tr.LcJump,
+ fmt.Sprintf("%s/%s", gui.getKeyDisplay(keybindingConfig.Universal.ScrollLeft), gui.getKeyDisplay(keybindingConfig.Universal.ScrollRight)): gui.c.Tr.LcScrollLeftRight,
}
}
@@ -302,9 +288,9 @@ func (gui *Gui) secondaryViewFocused() bool {
}
func (gui *Gui) onViewTabClick(viewName string, tabIndex int) error {
- context := gui.State.ViewTabContextMap[viewName][tabIndex].contexts[0]
+ context := gui.State.ViewTabContextMap[viewName][tabIndex].Contexts[0]
- return gui.pushContext(context)
+ return gui.c.PushContext(context)
}
func (gui *Gui) handleNextTab() error {
diff --git a/pkg/gui/whitespace-toggle.go b/pkg/gui/whitespace-toggle.go
index 7ded50c18..ad82bc036 100644
--- a/pkg/gui/whitespace-toggle.go
+++ b/pkg/gui/whitespace-toggle.go
@@ -3,11 +3,11 @@ package gui
func (gui *Gui) toggleWhitespaceInDiffView() error {
gui.IgnoreWhitespaceInDiffView = !gui.IgnoreWhitespaceInDiffView
- toastMessage := gui.Tr.ShowingWhitespaceInDiffView
+ toastMessage := gui.c.Tr.ShowingWhitespaceInDiffView
if gui.IgnoreWhitespaceInDiffView {
- toastMessage = gui.Tr.IgnoringWhitespaceInDiffView
+ toastMessage = gui.c.Tr.IgnoringWhitespaceInDiffView
}
- gui.raiseToast(toastMessage)
+ gui.c.Toast(toastMessage)
return gui.refreshFilesAndSubmodules()
}
diff --git a/pkg/gui/working_tree_helper.go b/pkg/gui/working_tree_helper.go
new file mode 100644
index 000000000..964a4bc5a
--- /dev/null
+++ b/pkg/gui/working_tree_helper.go
@@ -0,0 +1,50 @@
+package gui
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/gui/filetree"
+)
+
+type WorkingTreeHelper struct {
+ fileTreeViewModel *filetree.FileTreeViewModel
+}
+
+func NewWorkingTreeHelper(fileTreeViewModel *filetree.FileTreeViewModel) *WorkingTreeHelper {
+ return &WorkingTreeHelper{
+ fileTreeViewModel: fileTreeViewModel,
+ }
+}
+
+func (self *WorkingTreeHelper) AnyStagedFiles() bool {
+ files := self.fileTreeViewModel.GetAllFiles()
+ for _, file := range files {
+ if file.HasStagedChanges {
+ return true
+ }
+ }
+ return false
+}
+
+func (self *WorkingTreeHelper) AnyTrackedFiles() bool {
+ files := self.fileTreeViewModel.GetAllFiles()
+ for _, file := range files {
+ if file.Tracked {
+ return true
+ }
+ }
+ return false
+}
+
+func (self *WorkingTreeHelper) IsWorkingTreeDirty() bool {
+ return self.AnyStagedFiles() || self.AnyTrackedFiles()
+}
+
+func (self *WorkingTreeHelper) FileForSubmodule(submodule *models.SubmoduleConfig) *models.File {
+ for _, file := range self.fileTreeViewModel.GetAllFiles() {
+ if file.IsSubmodule([]*models.SubmoduleConfig{submodule}) {
+ return file
+ }
+ }
+
+ return nil
+}
diff --git a/pkg/gui/workspace_reset_options_panel.go b/pkg/gui/workspace_reset_options_panel.go
index 6230e0966..2b9fd97d1 100644
--- a/pkg/gui/workspace_reset_options_panel.go
+++ b/pkg/gui/workspace_reset_options_panel.go
@@ -13,64 +13,64 @@ func (gui *Gui) handleCreateResetMenu() error {
nukeStr := "reset --hard HEAD && git clean -fd"
if len(gui.State.Submodules) > 0 {
- nukeStr = fmt.Sprintf("%s (%s)", nukeStr, gui.Tr.LcAndResetSubmodules)
+ nukeStr = fmt.Sprintf("%s (%s)", nukeStr, gui.c.Tr.LcAndResetSubmodules)
}
menuItems := []*popup.MenuItem{
{
DisplayStrings: []string{
- gui.Tr.LcDiscardAllChangesToAllFiles,
+ gui.c.Tr.LcDiscardAllChangesToAllFiles,
red.Sprint(nukeStr),
},
OnPress: func() error {
- gui.logAction(gui.Tr.Actions.NukeWorkingTree)
- if err := gui.Git.WorkingTree.ResetAndClean(); err != nil {
- return gui.PopupHandler.Error(err)
+ gui.c.LogAction(gui.c.Tr.Actions.NukeWorkingTree)
+ if err := gui.git.WorkingTree.ResetAndClean(); err != nil {
+ return gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
{
DisplayStrings: []string{
- gui.Tr.LcDiscardAnyUnstagedChanges,
+ gui.c.Tr.LcDiscardAnyUnstagedChanges,
red.Sprint("git checkout -- ."),
},
OnPress: func() error {
- gui.logAction(gui.Tr.Actions.DiscardUnstagedFileChanges)
- if err := gui.Git.WorkingTree.DiscardAnyUnstagedFileChanges(); err != nil {
- return gui.PopupHandler.Error(err)
+ gui.c.LogAction(gui.c.Tr.Actions.DiscardUnstagedFileChanges)
+ if err := gui.git.WorkingTree.DiscardAnyUnstagedFileChanges(); err != nil {
+ return gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
{
DisplayStrings: []string{
- gui.Tr.LcDiscardUntrackedFiles,
+ gui.c.Tr.LcDiscardUntrackedFiles,
red.Sprint("git clean -fd"),
},
OnPress: func() error {
- gui.logAction(gui.Tr.Actions.RemoveUntrackedFiles)
- if err := gui.Git.WorkingTree.RemoveUntrackedFiles(); err != nil {
- return gui.PopupHandler.Error(err)
+ gui.c.LogAction(gui.c.Tr.Actions.RemoveUntrackedFiles)
+ if err := gui.git.WorkingTree.RemoveUntrackedFiles(); err != nil {
+ return gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
{
DisplayStrings: []string{
- gui.Tr.LcSoftReset,
+ gui.c.Tr.LcSoftReset,
red.Sprint("git reset --soft HEAD"),
},
OnPress: func() error {
- gui.logAction(gui.Tr.Actions.SoftReset)
- if err := gui.Git.WorkingTree.ResetSoft("HEAD"); err != nil {
- return gui.PopupHandler.Error(err)
+ gui.c.LogAction(gui.c.Tr.Actions.SoftReset)
+ if err := gui.git.WorkingTree.ResetSoft("HEAD"); err != nil {
+ return gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
{
@@ -79,29 +79,29 @@ func (gui *Gui) handleCreateResetMenu() error {
red.Sprint("git reset --mixed HEAD"),
},
OnPress: func() error {
- gui.logAction(gui.Tr.Actions.MixedReset)
- if err := gui.Git.WorkingTree.ResetMixed("HEAD"); err != nil {
- return gui.PopupHandler.Error(err)
+ gui.c.LogAction(gui.c.Tr.Actions.MixedReset)
+ if err := gui.git.WorkingTree.ResetMixed("HEAD"); err != nil {
+ return gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
{
DisplayStrings: []string{
- gui.Tr.LcHardReset,
+ gui.c.Tr.LcHardReset,
red.Sprint("git reset --hard HEAD"),
},
OnPress: func() error {
- gui.logAction(gui.Tr.Actions.HardReset)
- if err := gui.Git.WorkingTree.ResetHard("HEAD"); err != nil {
- return gui.PopupHandler.Error(err)
+ gui.c.LogAction(gui.c.Tr.Actions.HardReset)
+ if err := gui.git.WorkingTree.ResetHard("HEAD"); err != nil {
+ return gui.c.Error(err)
}
- return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
+ return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
},
},
}
- return gui.PopupHandler.Menu(popup.CreateMenuOptions{Title: "", Items: menuItems})
+ return gui.c.Menu(popup.CreateMenuOptions{Title: "", Items: menuItems})
}
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index f3b90a194..5db081625 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -46,6 +46,7 @@ type TranslationSet struct {
LcPush string
LcPull string
LcScroll string
+ LcFileFilter string
LcCommitFileFilter string
FilterStagedFiles string
FilterUnstagedFiles string
@@ -620,6 +621,7 @@ func EnglishTranslationSet() TranslationSet {
LcScroll: "scroll",
MergeConflictsTitle: "Merge Conflicts",
LcCheckout: "checkout",
+ LcFileFilter: "Filter files (staged/unstaged)",
LcCommitFileFilter: "Filter commit files",
FilterStagedFiles: "Show only staged files",
FilterUnstagedFiles: "Show only unstaged files",
diff --git a/test/integration/commit/expected/.git_keep/index b/test/integration/commit/expected/.git_keep/index
index 6bda11a96..e0cf62690 100644
Binary files a/test/integration/commit/expected/.git_keep/index and b/test/integration/commit/expected/.git_keep/index differ