mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-02-01 13:17:53 +02:00
start moving commit panel handlers into controller
more and more move rebase commit refreshing into existing abstraction and more and more WIP and more handling clicks properly fix merge conflicts update cheatsheet lots more preparation to start moving things into controllers WIP better typing expand on remotes controller moving more code into controllers
This commit is contained in:
parent
a90b6efded
commit
1dd7307fde
@ -123,30 +123,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Commits Panel (Commits)
|
||||
|
||||
<pre>
|
||||
<kbd>ctrl+l</kbd>: open log menu
|
||||
<kbd>s</kbd>: squash down
|
||||
<kbd>r</kbd>: reword commit
|
||||
<kbd>R</kbd>: reword commit with editor
|
||||
<kbd>g</kbd>: reset to this commit
|
||||
<kbd>f</kbd>: fixup commit
|
||||
<kbd>F</kbd>: create fixup commit for this commit
|
||||
<kbd>S</kbd>: squash all 'fixup!' commits above selected commit (autosquash)
|
||||
<kbd>d</kbd>: delete commit
|
||||
<kbd>ctrl+j</kbd>: move commit down one
|
||||
<kbd>ctrl+k</kbd>: move commit up one
|
||||
<kbd>e</kbd>: edit commit
|
||||
<kbd>A</kbd>: amend commit with staged changes
|
||||
<kbd>p</kbd>: pick commit (when mid-rebase)
|
||||
<kbd>t</kbd>: revert commit
|
||||
<kbd>c</kbd>: copy commit (cherry-pick)
|
||||
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
|
||||
<kbd>C</kbd>: copy commit range (cherry-pick)
|
||||
<kbd>v</kbd>: paste commits (cherry-pick)
|
||||
<kbd>n</kbd>: create new branch off of commit
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>s</kbd>: squash down
|
||||
<kbd>f</kbd>: fixup commit
|
||||
<kbd>r</kbd>: reword commit
|
||||
<kbd>R</kbd>: reword commit with editor
|
||||
<kbd>d</kbd>: delete commit
|
||||
<kbd>e</kbd>: edit commit
|
||||
<kbd>p</kbd>: pick commit (when mid-rebase)
|
||||
<kbd>F</kbd>: create fixup commit for this commit
|
||||
<kbd>S</kbd>: squash all 'fixup!' commits above selected commit (autosquash)
|
||||
<kbd>ctrl+j</kbd>: move commit down one
|
||||
<kbd>ctrl+k</kbd>: move commit up one
|
||||
<kbd>A</kbd>: amend commit with staged changes
|
||||
<kbd>t</kbd>: revert commit
|
||||
<kbd>ctrl+l</kbd>: open log menu
|
||||
<kbd>g</kbd>: reset to this commit
|
||||
<kbd>enter</kbd>: view commit's files
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>n</kbd>: create new branch off of commit
|
||||
<kbd>T</kbd>: tag commit
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>ctrl+y</kbd>: copy commit message to clipboard
|
||||
<kbd>o</kbd>: open commit in browser
|
||||
<kbd>b</kbd>: view bisect options
|
||||
@ -183,7 +183,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: amend last commit
|
||||
<kbd>C</kbd>: commit changes using git editor
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>o</kbd>: open file
|
||||
@ -200,6 +199,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>`</kbd>: toggle file tree view
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||
<kbd>space</kbd>: toggle staged
|
||||
</pre>
|
||||
|
||||
## Files Panel (Submodules)
|
||||
|
@ -123,30 +123,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Commits Paneel (Commits)
|
||||
|
||||
<pre>
|
||||
<kbd>ctrl+l</kbd>: open log menu
|
||||
<kbd>s</kbd>: squash beneden
|
||||
<kbd>r</kbd>: hernoem commit
|
||||
<kbd>R</kbd>: hernoem commit met editor
|
||||
<kbd>g</kbd>: reset naar deze commit
|
||||
<kbd>f</kbd>: Fixup commit
|
||||
<kbd>F</kbd>: creëer fixup commit voor deze commit
|
||||
<kbd>S</kbd>: squash bovenstaande commits
|
||||
<kbd>d</kbd>: verwijder commit
|
||||
<kbd>ctrl+j</kbd>: verplaats commit 1 naar beneden
|
||||
<kbd>ctrl+k</kbd>: verplaats commit 1 naar boven
|
||||
<kbd>e</kbd>: wijzig commit
|
||||
<kbd>A</kbd>: wijzig commit met staged veranderingen
|
||||
<kbd>p</kbd>: kies commit (wanneer midden in rebase)
|
||||
<kbd>t</kbd>: commit ongedaan maken
|
||||
<kbd>c</kbd>: kopieer commit (cherry-pick)
|
||||
<kbd>ctrl+o</kbd>: kopieer commit SHA naar klembord
|
||||
<kbd>C</kbd>: kopieer commit reeks (cherry-pick)
|
||||
<kbd>v</kbd>: plak commits (cherry-pick)
|
||||
<kbd>n</kbd>: creëer nieuwe branch van commit
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (gekopieerde) commits selectie
|
||||
<kbd>s</kbd>: squash beneden
|
||||
<kbd>f</kbd>: Fixup commit
|
||||
<kbd>r</kbd>: hernoem commit
|
||||
<kbd>R</kbd>: hernoem commit met editor
|
||||
<kbd>d</kbd>: verwijder commit
|
||||
<kbd>e</kbd>: wijzig commit
|
||||
<kbd>p</kbd>: kies commit (wanneer midden in rebase)
|
||||
<kbd>F</kbd>: creëer fixup commit voor deze commit
|
||||
<kbd>S</kbd>: squash bovenstaande commits
|
||||
<kbd>ctrl+j</kbd>: verplaats commit 1 naar beneden
|
||||
<kbd>ctrl+k</kbd>: verplaats commit 1 naar boven
|
||||
<kbd>A</kbd>: wijzig commit met staged veranderingen
|
||||
<kbd>t</kbd>: commit ongedaan maken
|
||||
<kbd>ctrl+l</kbd>: open log menu
|
||||
<kbd>g</kbd>: reset naar deze commit
|
||||
<kbd>enter</kbd>: bekijk gecommite bestanden
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>n</kbd>: creëer nieuwe branch van commit
|
||||
<kbd>T</kbd>: tag commit
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (gekopieerde) commits selectie
|
||||
<kbd>ctrl+y</kbd>: kopieer commit bericht naar klembord
|
||||
<kbd>o</kbd>: open commit in browser
|
||||
<kbd>b</kbd>: view bisect options
|
||||
@ -183,7 +183,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>w</kbd>: commit veranderingen zonder pre-commit hook
|
||||
<kbd>A</kbd>: wijzig laatste commit
|
||||
<kbd>C</kbd>: commit veranderingen met de git editor
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>d</kbd>: bekijk 'veranderingen ongedaan maken' opties
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
@ -200,6 +199,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>`</kbd>: toggle bestandsboom weergave
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||
<kbd>space</kbd>: toggle staged
|
||||
</pre>
|
||||
|
||||
## Bestanden Paneel (Submodules)
|
||||
|
@ -123,30 +123,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Commity Panel (Commity)
|
||||
|
||||
<pre>
|
||||
<kbd>ctrl+l</kbd>: open log menu
|
||||
<kbd>s</kbd>: ściśnij
|
||||
<kbd>r</kbd>: zmień nazwę commita
|
||||
<kbd>R</kbd>: zmień nazwę commita w edytorze
|
||||
<kbd>g</kbd>: zresetuj do tego commita
|
||||
<kbd>f</kbd>: napraw commit
|
||||
<kbd>F</kbd>: utwórz commit naprawczy dla tego commita
|
||||
<kbd>S</kbd>: spłaszcz wszystkie commity naprawcze powyżej zaznaczonych commitów (autosquash)
|
||||
<kbd>d</kbd>: usuń commit
|
||||
<kbd>ctrl+j</kbd>: przenieś commit 1 w dół
|
||||
<kbd>ctrl+k</kbd>: przenieś commit 1 w górę
|
||||
<kbd>e</kbd>: edytuj commit
|
||||
<kbd>A</kbd>: popraw commit zmianami z poczekalni
|
||||
<kbd>p</kbd>: wybierz commit (podczas zmiany bazy)
|
||||
<kbd>t</kbd>: odwróć commit
|
||||
<kbd>c</kbd>: kopiuj commit (przebieranie)
|
||||
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
|
||||
<kbd>C</kbd>: kopiuj zakres commitów (przebieranie)
|
||||
<kbd>v</kbd>: wklej commity (przebieranie)
|
||||
<kbd>n</kbd>: create new branch off of commit
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>s</kbd>: ściśnij
|
||||
<kbd>f</kbd>: napraw commit
|
||||
<kbd>r</kbd>: zmień nazwę commita
|
||||
<kbd>R</kbd>: zmień nazwę commita w edytorze
|
||||
<kbd>d</kbd>: usuń commit
|
||||
<kbd>e</kbd>: edytuj commit
|
||||
<kbd>p</kbd>: wybierz commit (podczas zmiany bazy)
|
||||
<kbd>F</kbd>: utwórz commit naprawczy dla tego commita
|
||||
<kbd>S</kbd>: spłaszcz wszystkie commity naprawcze powyżej zaznaczonych commitów (autosquash)
|
||||
<kbd>ctrl+j</kbd>: przenieś commit 1 w dół
|
||||
<kbd>ctrl+k</kbd>: przenieś commit 1 w górę
|
||||
<kbd>A</kbd>: popraw commit zmianami z poczekalni
|
||||
<kbd>t</kbd>: odwróć commit
|
||||
<kbd>ctrl+l</kbd>: open log menu
|
||||
<kbd>g</kbd>: zresetuj do tego commita
|
||||
<kbd>enter</kbd>: przeglądaj pliki commita
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>n</kbd>: create new branch off of commit
|
||||
<kbd>T</kbd>: tag commit
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>ctrl+y</kbd>: copy commit message to clipboard
|
||||
<kbd>o</kbd>: open commit in browser
|
||||
<kbd>b</kbd>: view bisect options
|
||||
@ -183,7 +183,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>w</kbd>: zatwierdź zmiany bez skryptu pre-commit
|
||||
<kbd>A</kbd>: Zmień ostatni commit
|
||||
<kbd>C</kbd>: Zatwierdź zmiany używając edytora
|
||||
<kbd>space</kbd>: przełącz stan poczekalni
|
||||
<kbd>d</kbd>: pokaż opcje porzucania zmian
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
@ -200,6 +199,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>`</kbd>: toggle file tree view
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||
<kbd>space</kbd>: przełącz stan poczekalni
|
||||
</pre>
|
||||
|
||||
## Pliki Panel (Submodules)
|
||||
|
@ -123,30 +123,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## 提交 面板 (提交)
|
||||
|
||||
<pre>
|
||||
<kbd>ctrl+l</kbd>: open log menu
|
||||
<kbd>s</kbd>: 向下压缩
|
||||
<kbd>r</kbd>: 改写提交
|
||||
<kbd>R</kbd>: 使用编辑器重命名提交
|
||||
<kbd>g</kbd>: 重置为此提交
|
||||
<kbd>f</kbd>: 修正提交(fixup)
|
||||
<kbd>F</kbd>: 为此提交创建修正
|
||||
<kbd>S</kbd>: 压缩在所选提交之上的所有“fixup!”提交(自动压缩)
|
||||
<kbd>d</kbd>: 删除提交
|
||||
<kbd>ctrl+j</kbd>: 下移提交
|
||||
<kbd>ctrl+k</kbd>: 上移提交
|
||||
<kbd>e</kbd>: 编辑提交
|
||||
<kbd>A</kbd>: 用已暂存的更改来修补提交
|
||||
<kbd>p</kbd>: 选择提交(变基过程中)
|
||||
<kbd>t</kbd>: 还原提交
|
||||
<kbd>c</kbd>: 复制提交(拣选)
|
||||
<kbd>ctrl+o</kbd>: 将提交的 SHA 复制到剪贴板
|
||||
<kbd>C</kbd>: 复制提交范围(拣选)
|
||||
<kbd>v</kbd>: 粘贴提交(拣选)
|
||||
<kbd>n</kbd>: 从提交创建新分支
|
||||
<kbd>ctrl+r</kbd>: 重置已拣选(复制)的提交
|
||||
<kbd>s</kbd>: 向下压缩
|
||||
<kbd>f</kbd>: 修正提交(fixup)
|
||||
<kbd>r</kbd>: 改写提交
|
||||
<kbd>R</kbd>: 使用编辑器重命名提交
|
||||
<kbd>d</kbd>: 删除提交
|
||||
<kbd>e</kbd>: 编辑提交
|
||||
<kbd>p</kbd>: 选择提交(变基过程中)
|
||||
<kbd>F</kbd>: 为此提交创建修正
|
||||
<kbd>S</kbd>: 压缩在所选提交之上的所有“fixup!”提交(自动压缩)
|
||||
<kbd>ctrl+j</kbd>: 下移提交
|
||||
<kbd>ctrl+k</kbd>: 上移提交
|
||||
<kbd>A</kbd>: 用已暂存的更改来修补提交
|
||||
<kbd>t</kbd>: 还原提交
|
||||
<kbd>ctrl+l</kbd>: open log menu
|
||||
<kbd>g</kbd>: 重置为此提交
|
||||
<kbd>enter</kbd>: 查看提交的文件
|
||||
<kbd>space</kbd>: 检出提交
|
||||
<kbd>n</kbd>: 从提交创建新分支
|
||||
<kbd>T</kbd>: 标签提交
|
||||
<kbd>ctrl+r</kbd>: 重置已拣选(复制)的提交
|
||||
<kbd>ctrl+y</kbd>: 将提交消息复制到剪贴板
|
||||
<kbd>o</kbd>: open commit in browser
|
||||
<kbd>b</kbd>: view bisect options
|
||||
@ -183,7 +183,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>w</kbd>: 提交更改而无需预先提交钩子
|
||||
<kbd>A</kbd>: 修补最后一次提交
|
||||
<kbd>C</kbd>: 提交更改(使用编辑器编辑提交信息)
|
||||
<kbd>space</kbd>: 切换暂存状态
|
||||
<kbd>d</kbd>: 查看'放弃更改‘选项
|
||||
<kbd>e</kbd>: 编辑文件
|
||||
<kbd>o</kbd>: 打开文件
|
||||
@ -200,6 +199,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>`</kbd>: 切换文件树视图
|
||||
<kbd>M</kbd>: 打开合并工具
|
||||
<kbd>ctrl+w</kbd>: 切换是否在差异视图中显示空白更改
|
||||
<kbd>space</kbd>: 切换暂存状态
|
||||
</pre>
|
||||
|
||||
## 文件 面板 (子模块)
|
||||
|
@ -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(" <kbd>%s</kbd>: %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)
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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{},
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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) }
|
||||
})
|
||||
}
|
||||
|
@ -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()
|
||||
|
98
pkg/gui/context/context.go
Normal file
98
pkg/gui/context/context.go
Normal file
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
273
pkg/gui/controllers/bisect_controller.go
Normal file
273
pkg/gui/controllers/bisect_controller.go
Normal file
@ -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
|
||||
}
|
10
pkg/gui/controllers/controller_common.go
Normal file
10
pkg/gui/controllers/controller_common.go
Normal file
@ -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
|
||||
}
|
737
pkg/gui/controllers/files_controller.go
Normal file
737
pkg/gui/controllers/files_controller.go
Normal file
@ -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("<c-b>"), // 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}})
|
||||
},
|
||||
})
|
||||
}
|
783
pkg/gui/controllers/local_commits_controller.go
Normal file
783
pkg/gui/controllers/local_commits_controller.go
Normal file
@ -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
|
||||
}
|
70
pkg/gui/controllers/menu_controller.go
Normal file
70
pkg/gui/controllers/menu_controller.go
Normal file
@ -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
|
||||
}
|
204
pkg/gui/controllers/remotes_controller.go
Normal file
204
pkg/gui/controllers/remotes_controller.go
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
253
pkg/gui/controllers/sync_controller.go
Normal file
253
pkg/gui/controllers/sync_controller.go
Normal file
@ -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)
|
||||
},
|
||||
})
|
||||
}
|
229
pkg/gui/controllers/tags_controller.go
Normal file
229
pkg/gui/controllers/tags_controller.go
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
})
|
||||
}
|
@ -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),
|
||||
|
@ -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, ", "))
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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})
|
||||
}
|
||||
|
@ -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})
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
|
51
pkg/gui/file_helper.go
Normal file
51
pkg/gui/file_helper.go
Normal file
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}})
|
||||
}
|
||||
|
@ -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})
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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})
|
||||
})
|
||||
}
|
||||
|
243
pkg/gui/gui.go
243
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 {
|
||||
|
53
pkg/gui/gui_common.go
Normal file
53
pkg/gui/gui_common.go
Normal file
@ -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()
|
||||
}
|
@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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]
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
19
pkg/gui/misc.go
Normal file
19
pkg/gui/misc.go
Normal file
@ -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
|
||||
}
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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 ""
|
||||
|
@ -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
|
||||
|
137
pkg/gui/ref_helper.go
Normal file
137
pkg/gui/ref_helper.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
@ -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",
|
||||
})
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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)))
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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",
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
7
pkg/gui/types/common_commands.go
Normal file
7
pkg/gui/types/common_commands.go
Normal file
@ -0,0 +1,7 @@
|
||||
package types
|
||||
|
||||
type CheckoutRefOptions struct {
|
||||
WaitingStatus string
|
||||
EnvVars []string
|
||||
OnRefNotFound func(ref string) error
|
||||
}
|
87
pkg/gui/types/context.go
Normal file
87
pkg/gui/types/context.go
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ type RefreshableView int
|
||||
|
||||
const (
|
||||
COMMITS RefreshableView = iota
|
||||
REBASE_COMMITS
|
||||
BRANCHES
|
||||
FILES
|
||||
STASH
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user