mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-27 23:08:02 +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)
|
## Commits Panel (Commits)
|
||||||
|
|
||||||
<pre>
|
<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>c</kbd>: copy commit (cherry-pick)
|
||||||
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
|
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
|
||||||
<kbd>C</kbd>: copy commit range (cherry-pick)
|
<kbd>C</kbd>: copy commit range (cherry-pick)
|
||||||
<kbd>v</kbd>: paste commits (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>enter</kbd>: view commit's files
|
||||||
<kbd>space</kbd>: checkout commit
|
<kbd>space</kbd>: checkout commit
|
||||||
<kbd>n</kbd>: create new branch off of commit
|
|
||||||
<kbd>T</kbd>: tag 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>ctrl+y</kbd>: copy commit message to clipboard
|
||||||
<kbd>o</kbd>: open commit in browser
|
<kbd>o</kbd>: open commit in browser
|
||||||
<kbd>b</kbd>: view bisect options
|
<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>w</kbd>: commit changes without pre-commit hook
|
||||||
<kbd>A</kbd>: amend last commit
|
<kbd>A</kbd>: amend last commit
|
||||||
<kbd>C</kbd>: commit changes using git editor
|
<kbd>C</kbd>: commit changes using git editor
|
||||||
<kbd>space</kbd>: toggle staged
|
|
||||||
<kbd>d</kbd>: view 'discard changes' options
|
<kbd>d</kbd>: view 'discard changes' options
|
||||||
<kbd>e</kbd>: edit file
|
<kbd>e</kbd>: edit file
|
||||||
<kbd>o</kbd>: open 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>`</kbd>: toggle file tree view
|
||||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
<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>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||||
|
<kbd>space</kbd>: toggle staged
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Files Panel (Submodules)
|
## 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)
|
## Commits Paneel (Commits)
|
||||||
|
|
||||||
<pre>
|
<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>c</kbd>: kopieer commit (cherry-pick)
|
||||||
<kbd>ctrl+o</kbd>: kopieer commit SHA naar klembord
|
<kbd>ctrl+o</kbd>: kopieer commit SHA naar klembord
|
||||||
<kbd>C</kbd>: kopieer commit reeks (cherry-pick)
|
<kbd>C</kbd>: kopieer commit reeks (cherry-pick)
|
||||||
<kbd>v</kbd>: plak commits (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>enter</kbd>: bekijk gecommite bestanden
|
||||||
<kbd>space</kbd>: checkout commit
|
<kbd>space</kbd>: checkout commit
|
||||||
<kbd>n</kbd>: creëer nieuwe branch van commit
|
|
||||||
<kbd>T</kbd>: tag 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>ctrl+y</kbd>: kopieer commit bericht naar klembord
|
||||||
<kbd>o</kbd>: open commit in browser
|
<kbd>o</kbd>: open commit in browser
|
||||||
<kbd>b</kbd>: view bisect options
|
<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>w</kbd>: commit veranderingen zonder pre-commit hook
|
||||||
<kbd>A</kbd>: wijzig laatste commit
|
<kbd>A</kbd>: wijzig laatste commit
|
||||||
<kbd>C</kbd>: commit veranderingen met de git editor
|
<kbd>C</kbd>: commit veranderingen met de git editor
|
||||||
<kbd>space</kbd>: toggle staged
|
|
||||||
<kbd>d</kbd>: bekijk 'veranderingen ongedaan maken' opties
|
<kbd>d</kbd>: bekijk 'veranderingen ongedaan maken' opties
|
||||||
<kbd>e</kbd>: verander bestand
|
<kbd>e</kbd>: verander bestand
|
||||||
<kbd>o</kbd>: open 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>`</kbd>: toggle bestandsboom weergave
|
||||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
<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>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||||
|
<kbd>space</kbd>: toggle staged
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Bestanden Paneel (Submodules)
|
## 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)
|
## Commity Panel (Commity)
|
||||||
|
|
||||||
<pre>
|
<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>c</kbd>: kopiuj commit (przebieranie)
|
||||||
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
|
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
|
||||||
<kbd>C</kbd>: kopiuj zakres commitów (przebieranie)
|
<kbd>C</kbd>: kopiuj zakres commitów (przebieranie)
|
||||||
<kbd>v</kbd>: wklej commity (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>enter</kbd>: przeglądaj pliki commita
|
||||||
<kbd>space</kbd>: checkout commit
|
<kbd>space</kbd>: checkout commit
|
||||||
<kbd>n</kbd>: create new branch off of commit
|
|
||||||
<kbd>T</kbd>: tag 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>ctrl+y</kbd>: copy commit message to clipboard
|
||||||
<kbd>o</kbd>: open commit in browser
|
<kbd>o</kbd>: open commit in browser
|
||||||
<kbd>b</kbd>: view bisect options
|
<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>w</kbd>: zatwierdź zmiany bez skryptu pre-commit
|
||||||
<kbd>A</kbd>: Zmień ostatni commit
|
<kbd>A</kbd>: Zmień ostatni commit
|
||||||
<kbd>C</kbd>: Zatwierdź zmiany używając edytora
|
<kbd>C</kbd>: Zatwierdź zmiany używając edytora
|
||||||
<kbd>space</kbd>: przełącz stan poczekalni
|
|
||||||
<kbd>d</kbd>: pokaż opcje porzucania zmian
|
<kbd>d</kbd>: pokaż opcje porzucania zmian
|
||||||
<kbd>e</kbd>: edytuj plik
|
<kbd>e</kbd>: edytuj plik
|
||||||
<kbd>o</kbd>: otwórz 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>`</kbd>: toggle file tree view
|
||||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
<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>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||||
|
<kbd>space</kbd>: przełącz stan poczekalni
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Pliki Panel (Submodules)
|
## Pliki Panel (Submodules)
|
||||||
|
@ -123,30 +123,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||||||
## 提交 面板 (提交)
|
## 提交 面板 (提交)
|
||||||
|
|
||||||
<pre>
|
<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>c</kbd>: 复制提交(拣选)
|
||||||
<kbd>ctrl+o</kbd>: 将提交的 SHA 复制到剪贴板
|
<kbd>ctrl+o</kbd>: 将提交的 SHA 复制到剪贴板
|
||||||
<kbd>C</kbd>: 复制提交范围(拣选)
|
<kbd>C</kbd>: 复制提交范围(拣选)
|
||||||
<kbd>v</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>enter</kbd>: 查看提交的文件
|
||||||
<kbd>space</kbd>: 检出提交
|
<kbd>space</kbd>: 检出提交
|
||||||
<kbd>n</kbd>: 从提交创建新分支
|
|
||||||
<kbd>T</kbd>: 标签提交
|
<kbd>T</kbd>: 标签提交
|
||||||
<kbd>ctrl+r</kbd>: 重置已拣选(复制)的提交
|
|
||||||
<kbd>ctrl+y</kbd>: 将提交消息复制到剪贴板
|
<kbd>ctrl+y</kbd>: 将提交消息复制到剪贴板
|
||||||
<kbd>o</kbd>: open commit in browser
|
<kbd>o</kbd>: open commit in browser
|
||||||
<kbd>b</kbd>: view bisect options
|
<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>w</kbd>: 提交更改而无需预先提交钩子
|
||||||
<kbd>A</kbd>: 修补最后一次提交
|
<kbd>A</kbd>: 修补最后一次提交
|
||||||
<kbd>C</kbd>: 提交更改(使用编辑器编辑提交信息)
|
<kbd>C</kbd>: 提交更改(使用编辑器编辑提交信息)
|
||||||
<kbd>space</kbd>: 切换暂存状态
|
|
||||||
<kbd>d</kbd>: 查看'放弃更改‘选项
|
<kbd>d</kbd>: 查看'放弃更改‘选项
|
||||||
<kbd>e</kbd>: 编辑文件
|
<kbd>e</kbd>: 编辑文件
|
||||||
<kbd>o</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>`</kbd>: 切换文件树视图
|
||||||
<kbd>M</kbd>: 打开合并工具
|
<kbd>M</kbd>: 打开合并工具
|
||||||
<kbd>ctrl+w</kbd>: 切换是否在差异视图中显示空白更改
|
<kbd>ctrl+w</kbd>: 切换是否在差异视图中显示空白更改
|
||||||
|
<kbd>space</kbd>: 切换暂存状态
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## 文件 面板 (子模块)
|
## 文件 面板 (子模块)
|
||||||
|
@ -17,13 +17,14 @@ import (
|
|||||||
"github.com/jesseduffield/lazygit/pkg/app"
|
"github.com/jesseduffield/lazygit/pkg/app"
|
||||||
"github.com/jesseduffield/lazygit/pkg/config"
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui"
|
"github.com/jesseduffield/lazygit/pkg/gui"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||||
"github.com/jesseduffield/lazygit/pkg/integration"
|
"github.com/jesseduffield/lazygit/pkg/integration"
|
||||||
)
|
)
|
||||||
|
|
||||||
type bindingSection struct {
|
type bindingSection struct {
|
||||||
title string
|
title string
|
||||||
bindings []*gui.Binding
|
bindings []*types.Binding
|
||||||
}
|
}
|
||||||
|
|
||||||
func CommandToRun() string {
|
func CommandToRun() string {
|
||||||
@ -113,7 +114,7 @@ func formatTitle(title string) string {
|
|||||||
return fmt.Sprintf("\n## %s\n\n", title)
|
return fmt.Sprintf("\n## %s\n\n", title)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatBinding(binding *gui.Binding) string {
|
func formatBinding(binding *types.Binding) string {
|
||||||
if binding.Alternative != "" {
|
if binding.Alternative != "" {
|
||||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s (%s)\n", gui.GetKeyDisplay(binding.Key), binding.Description, 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
|
title string
|
||||||
}
|
}
|
||||||
|
|
||||||
contextAndViewBindingMap := map[contextAndViewType][]*gui.Binding{}
|
contextAndViewBindingMap := map[contextAndViewType][]*types.Binding{}
|
||||||
|
|
||||||
outer:
|
outer:
|
||||||
for _, binding := range bindings {
|
for _, binding := range bindings {
|
||||||
@ -138,7 +139,7 @@ outer:
|
|||||||
key := contextAndViewType{subtitle: "", title: "navigation"}
|
key := contextAndViewType{subtitle: "", title: "navigation"}
|
||||||
existing := contextAndViewBindingMap[key]
|
existing := contextAndViewBindingMap[key]
|
||||||
if existing == nil {
|
if existing == nil {
|
||||||
contextAndViewBindingMap[key] = []*gui.Binding{binding}
|
contextAndViewBindingMap[key] = []*types.Binding{binding}
|
||||||
} else {
|
} else {
|
||||||
for _, navBinding := range contextAndViewBindingMap[key] {
|
for _, navBinding := range contextAndViewBindingMap[key] {
|
||||||
if navBinding.Description == binding.Description {
|
if navBinding.Description == binding.Description {
|
||||||
@ -162,7 +163,7 @@ outer:
|
|||||||
key := contextAndViewType{subtitle: context, title: binding.ViewName}
|
key := contextAndViewType{subtitle: context, title: binding.ViewName}
|
||||||
existing := contextAndViewBindingMap[key]
|
existing := contextAndViewBindingMap[key]
|
||||||
if existing == nil {
|
if existing == nil {
|
||||||
contextAndViewBindingMap[key] = []*gui.Binding{binding}
|
contextAndViewBindingMap[key] = []*types.Binding{binding}
|
||||||
} else {
|
} else {
|
||||||
contextAndViewBindingMap[key] = append(contextAndViewBindingMap[key], binding)
|
contextAndViewBindingMap[key] = append(contextAndViewBindingMap[key], binding)
|
||||||
}
|
}
|
||||||
@ -171,7 +172,7 @@ outer:
|
|||||||
|
|
||||||
type groupedBindingsType struct {
|
type groupedBindingsType struct {
|
||||||
contextAndView contextAndViewType
|
contextAndView contextAndViewType
|
||||||
bindings []*gui.Binding
|
bindings []*types.Binding
|
||||||
}
|
}
|
||||||
|
|
||||||
groupedBindings := make([]groupedBindingsType, len(contextAndViewBindingMap))
|
groupedBindings := make([]groupedBindingsType, len(contextAndViewBindingMap))
|
||||||
@ -227,7 +228,7 @@ outer:
|
|||||||
return bindingSections
|
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 == "" {
|
if binding.Description == "" && binding.Alternative == "" {
|
||||||
return bindingSections
|
return bindingSections
|
||||||
}
|
}
|
||||||
@ -241,7 +242,7 @@ func addBinding(title string, bindingSections []*bindingSection, binding *gui.Bi
|
|||||||
|
|
||||||
section := &bindingSection{
|
section := &bindingSection{
|
||||||
title: title,
|
title: title,
|
||||||
bindings: []*gui.Binding{binding},
|
bindings: []*types.Binding{binding},
|
||||||
}
|
}
|
||||||
|
|
||||||
return append(bindingSections, section)
|
return append(bindingSections, section)
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
|
|
||||||
@ -56,6 +57,7 @@ func NewGitCommand(
|
|||||||
cmn *common.Common,
|
cmn *common.Common,
|
||||||
osCommand *oscommands.OSCommand,
|
osCommand *oscommands.OSCommand,
|
||||||
gitConfig git_config.IGitConfig,
|
gitConfig git_config.IGitConfig,
|
||||||
|
syncMutex *sync.Mutex,
|
||||||
) (*GitCommand, error) {
|
) (*GitCommand, error) {
|
||||||
if err := navigateToRepoRootDirectory(os.Stat, os.Chdir); err != nil {
|
if err := navigateToRepoRootDirectory(os.Stat, os.Chdir); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -77,6 +79,7 @@ func NewGitCommand(
|
|||||||
gitConfig,
|
gitConfig,
|
||||||
dotGitDir,
|
dotGitDir,
|
||||||
repo,
|
repo,
|
||||||
|
syncMutex,
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +89,7 @@ func NewGitCommandAux(
|
|||||||
gitConfig git_config.IGitConfig,
|
gitConfig git_config.IGitConfig,
|
||||||
dotGitDir string,
|
dotGitDir string,
|
||||||
repo *gogit.Repository,
|
repo *gogit.Repository,
|
||||||
|
syncMutex *sync.Mutex,
|
||||||
) *GitCommand {
|
) *GitCommand {
|
||||||
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
|
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
|
||||||
|
|
||||||
@ -95,7 +99,7 @@ func NewGitCommandAux(
|
|||||||
// on the one struct.
|
// on the one struct.
|
||||||
// common ones are: cmn, osCommand, dotGitDir, configCommands
|
// common ones are: cmn, osCommand, dotGitDir, configCommands
|
||||||
configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo)
|
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)
|
statusCommands := git_commands.NewStatusCommands(gitCommon)
|
||||||
fileLoader := loaders.NewFileLoader(cmn, cmd, configCommands)
|
fileLoader := loaders.NewFileLoader(cmn, cmd, configCommands)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package git_commands
|
package git_commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
gogit "github.com/jesseduffield/go-git/v5"
|
gogit "github.com/jesseduffield/go-git/v5"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/common"
|
"github.com/jesseduffield/lazygit/pkg/common"
|
||||||
@ -13,6 +15,8 @@ type GitCommon struct {
|
|||||||
dotGitDir string
|
dotGitDir string
|
||||||
repo *gogit.Repository
|
repo *gogit.Repository
|
||||||
config *ConfigCommands
|
config *ConfigCommands
|
||||||
|
// mutex for doing things like push/pull/fetch
|
||||||
|
syncMutex *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGitCommon(
|
func NewGitCommon(
|
||||||
@ -22,6 +26,7 @@ func NewGitCommon(
|
|||||||
dotGitDir string,
|
dotGitDir string,
|
||||||
repo *gogit.Repository,
|
repo *gogit.Repository,
|
||||||
config *ConfigCommands,
|
config *ConfigCommands,
|
||||||
|
syncMutex *sync.Mutex,
|
||||||
) *GitCommon {
|
) *GitCommon {
|
||||||
return &GitCommon{
|
return &GitCommon{
|
||||||
Common: cmn,
|
Common: cmn,
|
||||||
@ -30,5 +35,6 @@ func NewGitCommon(
|
|||||||
dotGitDir: dotGitDir,
|
dotGitDir: dotGitDir,
|
||||||
repo: repo,
|
repo: repo,
|
||||||
config: config,
|
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 {
|
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))
|
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
|
// CheckRemoteBranchExists Returns remote branch
|
||||||
|
@ -47,7 +47,7 @@ func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error)
|
|||||||
cmdStr += " " + self.cmd.Quote(opts.UpstreamBranch)
|
cmdStr += " " + self.cmd.Quote(opts.UpstreamBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdObj := self.cmd.New(cmdStr).PromptOnCredentialRequest()
|
cmdObj := self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex)
|
||||||
return cmdObj, nil
|
return cmdObj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ func (self *SyncCommands) Fetch(opts FetchOptions) error {
|
|||||||
} else {
|
} else {
|
||||||
cmdObj.PromptOnCredentialRequest()
|
cmdObj.PromptOnCredentialRequest()
|
||||||
}
|
}
|
||||||
return cmdObj.Run()
|
return cmdObj.WithMutex(self.syncMutex).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
type PullOptions struct {
|
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
|
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
|
||||||
// has 'pull.rebase = interactive' configured.
|
// 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 {
|
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))
|
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 {
|
func (self *SyncCommands) FetchRemote(remoteName string) error {
|
||||||
cmdStr := fmt.Sprintf("git fetch %s", self.cmd.Quote(remoteName))
|
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 {
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -211,7 +212,12 @@ func TestNewGitCommand(t *testing.T) {
|
|||||||
s := s
|
s := s
|
||||||
t.Run(s.testName, func(t *testing.T) {
|
t.Run(s.testName, func(t *testing.T) {
|
||||||
s.setup()
|
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 (
|
import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A command object is a general way to represent a command to be run on the
|
// 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
|
PromptOnCredentialRequest() ICmdObj
|
||||||
FailOnCredentialRequest() ICmdObj
|
FailOnCredentialRequest() ICmdObj
|
||||||
|
|
||||||
|
WithMutex(mutex *sync.Mutex) ICmdObj
|
||||||
|
Mutex() *sync.Mutex
|
||||||
|
|
||||||
GetCredentialStrategy() CredentialStrategy
|
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.
|
// if set to true, it means we might be asked to enter a username/password by this command.
|
||||||
credentialStrategy CredentialStrategy
|
credentialStrategy CredentialStrategy
|
||||||
|
|
||||||
|
// can be set so that we don't run certain commands simultaneously
|
||||||
|
mutex *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type CredentialStrategy int
|
type CredentialStrategy int
|
||||||
@ -132,6 +139,16 @@ func (self *CmdObj) IgnoreEmptyError() ICmdObj {
|
|||||||
return self
|
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 {
|
func (self *CmdObj) ShouldIgnoreEmptyError() bool {
|
||||||
return self.ignoreEmptyError
|
return self.ignoreEmptyError
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,11 @@ type cmdObjRunner struct {
|
|||||||
var _ ICmdObjRunner = &cmdObjRunner{}
|
var _ ICmdObjRunner = &cmdObjRunner{}
|
||||||
|
|
||||||
func (self *cmdObjRunner) Run(cmdObj ICmdObj) error {
|
func (self *cmdObjRunner) Run(cmdObj ICmdObj) error {
|
||||||
|
if cmdObj.Mutex() != nil {
|
||||||
|
cmdObj.Mutex().Lock()
|
||||||
|
defer cmdObj.Mutex().Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
if cmdObj.GetCredentialStrategy() != NONE {
|
if cmdObj.GetCredentialStrategy() != NONE {
|
||||||
return self.runWithCredentialHandling(cmdObj)
|
return self.runWithCredentialHandling(cmdObj)
|
||||||
}
|
}
|
||||||
@ -42,17 +47,14 @@ func (self *cmdObjRunner) Run(cmdObj ICmdObj) error {
|
|||||||
return self.runAndStream(cmdObj)
|
return self.runAndStream(cmdObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := self.RunWithOutput(cmdObj)
|
_, err := self.RunWithOutputAux(cmdObj)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
|
func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
|
||||||
if cmdObj.ShouldStreamOutput() {
|
if cmdObj.Mutex() != nil {
|
||||||
err := self.runAndStream(cmdObj)
|
cmdObj.Mutex().Lock()
|
||||||
// for now we're not capturing output, just because it would take a little more
|
defer cmdObj.Mutex().Unlock()
|
||||||
// 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.GetCredentialStrategy() != NONE {
|
if cmdObj.GetCredentialStrategy() != NONE {
|
||||||
@ -63,6 +65,18 @@ func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
|
|||||||
return "", err
|
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")
|
self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand")
|
||||||
|
|
||||||
if cmdObj.ShouldLog() {
|
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 {
|
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 {
|
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")
|
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
|
return topStatus.message
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) raiseToast(message string) {
|
func (gui *Gui) toast(message string) {
|
||||||
gui.statusManager.addToastStatus(message)
|
gui.statusManager.addToastStatus(message)
|
||||||
|
|
||||||
gui.renderAppStatus()
|
gui.renderAppStatus()
|
||||||
@ -119,7 +119,7 @@ func (gui *Gui) withWaitingStatus(message string, f func() error) error {
|
|||||||
|
|
||||||
if err := f(); err != nil {
|
if err := f(); err != nil {
|
||||||
gui.OnUIThread(func() error {
|
gui.OnUIThread(func() error {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -2,6 +2,7 @@ package gui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/boxlayout"
|
"github.com/jesseduffield/lazygit/pkg/gui/boxlayout"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ func (gui *Gui) getMidSectionWeights() (int, int) {
|
|||||||
currentWindow := gui.currentWindow()
|
currentWindow := gui.currentWindow()
|
||||||
|
|
||||||
// we originally specified this as a ratio i.e. .20 would correspond to a weight of 1 against 4
|
// 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
|
// we could make this better by creating ratios like 2:3 rather than always 1:something
|
||||||
mainSectionWeight := int(1/sidePanelWidthRatio) - 1
|
mainSectionWeight := int(1/sidePanelWidthRatio) - 1
|
||||||
sideSectionWeight := 1
|
sideSectionWeight := 1
|
||||||
@ -115,7 +116,7 @@ func (gui *Gui) splitMainPanelSideBySide() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
mainPanelSplitMode := gui.UserConfig.Gui.MainPanelSplitMode
|
mainPanelSplitMode := gui.c.UserConfig.Gui.MainPanelSplitMode
|
||||||
width, height := gui.g.Size()
|
width, height := gui.g.Size()
|
||||||
|
|
||||||
switch mainPanelSplitMode {
|
switch mainPanelSplitMode {
|
||||||
@ -143,7 +144,7 @@ func (gui *Gui) getExtrasWindowSize(screenHeight int) int {
|
|||||||
} else if screenHeight < 40 {
|
} else if screenHeight < 40 {
|
||||||
baseSize = 1
|
baseSize = 1
|
||||||
} else {
|
} else {
|
||||||
baseSize = gui.UserConfig.Gui.CommandLogSize
|
baseSize = gui.c.UserConfig.Gui.CommandLogSize
|
||||||
}
|
}
|
||||||
|
|
||||||
frameSize := 2
|
frameSize := 2
|
||||||
@ -259,7 +260,7 @@ func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
|||||||
fullHeightBox("stash"),
|
fullHeightBox("stash"),
|
||||||
}
|
}
|
||||||
} else if height >= 28 {
|
} else if height >= 28 {
|
||||||
accordionMode := gui.UserConfig.Gui.ExpandFocusedSidePanel
|
accordionMode := gui.c.UserConfig.Gui.ExpandFocusedSidePanel
|
||||||
accordionBox := func(defaultBox *boxlayout.Box) *boxlayout.Box {
|
accordionBox := func(defaultBox *boxlayout.Box) *boxlayout.Box {
|
||||||
if accordionMode && defaultBox.Window == currentWindow {
|
if accordionMode && defaultBox.Window == currentWindow {
|
||||||
return &boxlayout.Box{
|
return &boxlayout.Box{
|
||||||
@ -320,7 +321,7 @@ func (gui *Gui) currentSideWindowName() string {
|
|||||||
reversedIdx := len(gui.State.ContextManager.ContextStack) - 1 - idx
|
reversedIdx := len(gui.State.ContextManager.ContextStack) - 1 - idx
|
||||||
context := gui.State.ContextManager.ContextStack[reversedIdx]
|
context := gui.State.ContextManager.ContextStack[reversedIdx]
|
||||||
|
|
||||||
if context.GetKind() == SIDE_CONTEXT {
|
if context.GetKind() == types.SIDE_CONTEXT {
|
||||||
return context.GetWindowName()
|
return context.GetWindowName()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,28 @@
|
|||||||
package gui
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
|
)
|
||||||
|
|
||||||
type BasicContext struct {
|
type BasicContext struct {
|
||||||
OnFocus func(opts ...OnFocusOpts) error
|
OnFocus func(opts ...types.OnFocusOpts) error
|
||||||
OnFocusLost func() error
|
OnFocusLost func() error
|
||||||
OnRender func() error
|
OnRender func() error
|
||||||
// this is for pushing some content to the main view
|
// this is for pushing some content to the main view
|
||||||
OnRenderToMain func(opts ...OnFocusOpts) error
|
OnRenderToMain func(opts ...types.OnFocusOpts) error
|
||||||
Kind ContextKind
|
Kind types.ContextKind
|
||||||
Key ContextKey
|
Key types.ContextKey
|
||||||
ViewName string
|
ViewName string
|
||||||
WindowName string
|
WindowName string
|
||||||
OnGetOptionsMap func() map[string]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
|
// 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
|
hasParent bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ types.Context = &BasicContext{}
|
||||||
|
|
||||||
func (self *BasicContext) GetOptionsMap() map[string]string {
|
func (self *BasicContext) GetOptionsMap() map[string]string {
|
||||||
if self.OnGetOptionsMap != nil {
|
if self.OnGetOptionsMap != nil {
|
||||||
return self.OnGetOptionsMap()
|
return self.OnGetOptionsMap()
|
||||||
@ -24,12 +30,12 @@ func (self *BasicContext) GetOptionsMap() map[string]string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BasicContext) SetParentContext(context Context) {
|
func (self *BasicContext) SetParentContext(context types.Context) {
|
||||||
self.ParentContext = context
|
self.ParentContext = context
|
||||||
self.hasParent = true
|
self.hasParent = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BasicContext) GetParentContext() (Context, bool) {
|
func (self *BasicContext) GetParentContext() (types.Context, bool) {
|
||||||
return self.ParentContext, self.hasParent
|
return self.ParentContext, self.hasParent
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +65,7 @@ func (self *BasicContext) GetViewName() string {
|
|||||||
return self.ViewName
|
return self.ViewName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BasicContext) HandleFocus(opts ...OnFocusOpts) error {
|
func (self *BasicContext) HandleFocus(opts ...types.OnFocusOpts) error {
|
||||||
if self.OnFocus != nil {
|
if self.OnFocus != nil {
|
||||||
if err := self.OnFocus(opts...); err != nil {
|
if err := self.OnFocus(opts...); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -90,10 +96,10 @@ func (self *BasicContext) HandleRenderToMain() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BasicContext) GetKind() ContextKind {
|
func (self *BasicContext) GetKind() types.ContextKind {
|
||||||
return self.Kind
|
return self.Kind
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BasicContext) GetKey() ContextKey {
|
func (self *BasicContext) GetKey() types.ContextKey {
|
||||||
return self.Key
|
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/git_commands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"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/popup"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
@ -31,9 +32,9 @@ func (gui *Gui) branchesRenderToMain() error {
|
|||||||
var task updateTask
|
var task updateTask
|
||||||
branch := gui.getSelectedBranch()
|
branch := gui.getSelectedBranch()
|
||||||
if branch == nil {
|
if branch == nil {
|
||||||
task = NewRenderStringTask(gui.Tr.NoBranchesThisRepo)
|
task = NewRenderStringTask(gui.c.Tr.NoBranchesThisRepo)
|
||||||
} else {
|
} else {
|
||||||
cmdObj := gui.Git.Branch.GetGraphCmdObj(branch.Name)
|
cmdObj := gui.git.Branch.GetGraphCmdObj(branch.Name)
|
||||||
|
|
||||||
task = NewRunPtyTask(cmdObj.GetCmd())
|
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
|
// which allows us to order them correctly. So if we're filtering we'll just
|
||||||
// manually load all the reflog commits here
|
// manually load all the reflog commits here
|
||||||
var err error
|
var err error
|
||||||
reflogCommits, _, err = gui.Git.Loaders.ReflogCommits.GetReflogCommits(nil, "")
|
reflogCommits, _, err = gui.git.Loaders.ReflogCommits.GetReflogCommits(nil, "")
|
||||||
if err != 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 {
|
if err != nil {
|
||||||
_ = gui.PopupHandler.Error(err)
|
_ = gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.State.Branches = branches
|
gui.State.Branches = branches
|
||||||
|
|
||||||
if err := gui.postRefreshUpdate(gui.State.Contexts.Branches); err != nil {
|
if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Branches); err != nil {
|
||||||
gui.Log.Error(err)
|
gui.c.Log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.refreshStatus()
|
gui.refreshStatus()
|
||||||
@ -83,11 +84,11 @@ func (gui *Gui) handleBranchPress() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if gui.State.Panels.Branches.SelectedLineIdx == 0 {
|
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()
|
branch := gui.getSelectedBranch()
|
||||||
gui.logAction(gui.Tr.Actions.CheckoutBranch)
|
gui.c.LogAction(gui.c.Tr.Actions.CheckoutBranch)
|
||||||
return gui.handleCheckoutRef(branch.Name, handleCheckoutRefOptions{})
|
return gui.refHelper.CheckoutRef(branch.Name, types.CheckoutRefOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCreatePullRequestPress() error {
|
func (gui *Gui) handleCreatePullRequestPress() error {
|
||||||
@ -110,129 +111,64 @@ func (gui *Gui) handleCopyPullRequestURLPress() error {
|
|||||||
|
|
||||||
branch := gui.getSelectedBranch()
|
branch := gui.getSelectedBranch()
|
||||||
|
|
||||||
branchExistsOnRemote := gui.Git.Remote.CheckRemoteBranchExists(branch.Name)
|
branchExistsOnRemote := gui.git.Remote.CheckRemoteBranchExists(branch.Name)
|
||||||
|
|
||||||
if !branchExistsOnRemote {
|
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, "")
|
url, err := hostingServiceMgr.GetPullRequestURL(branch.Name, "")
|
||||||
if err != nil {
|
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 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleGitFetch() error {
|
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 {
|
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 {
|
func (gui *Gui) handleForceCheckout() error {
|
||||||
branch := gui.getSelectedBranch()
|
branch := gui.getSelectedBranch()
|
||||||
message := gui.Tr.SureForceCheckout
|
message := gui.c.Tr.SureForceCheckout
|
||||||
title := gui.Tr.ForceCheckoutBranch
|
title := gui.c.Tr.ForceCheckoutBranch
|
||||||
|
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: title,
|
Title: title,
|
||||||
Prompt: message,
|
Prompt: message,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
gui.logAction(gui.Tr.Actions.ForceCheckoutBranch)
|
gui.c.LogAction(gui.c.Tr.Actions.ForceCheckoutBranch)
|
||||||
if err := gui.Git.Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
|
if err := gui.git.Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); 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})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
func (gui *Gui) handleCheckoutByName() error {
|
||||||
return gui.PopupHandler.Prompt(popup.PromptOpts{
|
return gui.c.Prompt(popup.PromptOpts{
|
||||||
Title: gui.Tr.BranchName + ":",
|
Title: gui.c.Tr.BranchName + ":",
|
||||||
FindSuggestionsFunc: gui.getRefsSuggestionsFunc(),
|
FindSuggestionsFunc: gui.suggestionsHelper.GetRefsSuggestionsFunc(),
|
||||||
HandleConfirm: func(response string) error {
|
HandleConfirm: func(response string) error {
|
||||||
gui.logAction("Checkout branch")
|
gui.c.LogAction("Checkout branch")
|
||||||
return gui.handleCheckoutRef(response, handleCheckoutRefOptions{
|
return gui.refHelper.CheckoutRef(response, types.CheckoutRefOptions{
|
||||||
onRefNotFound: func(ref string) error {
|
OnRefNotFound: func(ref string) error {
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.BranchNotFoundTitle,
|
Title: gui.c.Tr.BranchNotFoundTitle,
|
||||||
Prompt: fmt.Sprintf("%s %s%s", gui.Tr.BranchNotFoundPrompt, ref, "?"),
|
Prompt: fmt.Sprintf("%s %s%s", gui.c.Tr.BranchNotFoundPrompt, ref, "?"),
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
return gui.createNewBranchWithName(ref)
|
return gui.createNewBranchWithName(ref)
|
||||||
},
|
},
|
||||||
@ -257,12 +193,12 @@ func (gui *Gui) createNewBranchWithName(newBranchName string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gui.Git.Branch.New(newBranchName, branch.Name); err != nil {
|
if err := gui.git.Branch.New(newBranchName, branch.Name); err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.State.Panels.Branches.SelectedLineIdx = 0
|
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 {
|
func (gui *Gui) handleDeleteBranch() error {
|
||||||
@ -276,18 +212,18 @@ func (gui *Gui) deleteBranch(force bool) error {
|
|||||||
}
|
}
|
||||||
checkedOutBranch := gui.getCheckedOutBranch()
|
checkedOutBranch := gui.getCheckedOutBranch()
|
||||||
if checkedOutBranch.Name == selectedBranch.Name {
|
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)
|
return gui.deleteNamedBranch(selectedBranch, force)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) deleteNamedBranch(selectedBranch *models.Branch, force bool) error {
|
func (gui *Gui) deleteNamedBranch(selectedBranch *models.Branch, force bool) error {
|
||||||
title := gui.Tr.DeleteBranch
|
title := gui.c.Tr.DeleteBranch
|
||||||
var templateStr string
|
var templateStr string
|
||||||
if force {
|
if force {
|
||||||
templateStr = gui.Tr.ForceDeleteBranchMessage
|
templateStr = gui.c.Tr.ForceDeleteBranchMessage
|
||||||
} else {
|
} else {
|
||||||
templateStr = gui.Tr.DeleteBranchMessage
|
templateStr = gui.c.Tr.DeleteBranchMessage
|
||||||
}
|
}
|
||||||
message := utils.ResolvePlaceholderString(
|
message := utils.ResolvePlaceholderString(
|
||||||
templateStr,
|
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,
|
Title: title,
|
||||||
Prompt: message,
|
Prompt: message,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
gui.logAction(gui.Tr.Actions.DeleteBranch)
|
gui.c.LogAction(gui.c.Tr.Actions.DeleteBranch)
|
||||||
if err := gui.Git.Branch.Delete(selectedBranch.Name, force); err != nil {
|
if err := gui.git.Branch.Delete(selectedBranch.Name, force); err != nil {
|
||||||
errMessage := err.Error()
|
errMessage := err.Error()
|
||||||
if !force && strings.Contains(errMessage, "git branch -D ") {
|
if !force && strings.Contains(errMessage, "git branch -D ") {
|
||||||
return gui.deleteNamedBranch(selectedBranch, true)
|
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 {
|
func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error {
|
||||||
if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
|
if gui.git.Branch.IsHeadDetached() {
|
||||||
return err
|
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")
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
checkedOutBranchName := gui.getCheckedOutBranch().Name
|
checkedOutBranchName := gui.getCheckedOutBranch().Name
|
||||||
if checkedOutBranchName == branchName {
|
if checkedOutBranchName == branchName {
|
||||||
return gui.PopupHandler.ErrorMsg(gui.Tr.CantMergeBranchIntoItself)
|
return gui.c.ErrorMsg(gui.c.Tr.CantMergeBranchIntoItself)
|
||||||
}
|
}
|
||||||
prompt := utils.ResolvePlaceholderString(
|
prompt := utils.ResolvePlaceholderString(
|
||||||
gui.Tr.ConfirmMerge,
|
gui.c.Tr.ConfirmMerge,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"checkedOutBranch": checkedOutBranchName,
|
"checkedOutBranch": checkedOutBranchName,
|
||||||
"selectedBranch": branchName,
|
"selectedBranch": branchName,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.MergingTitle,
|
Title: gui.c.Tr.MergingTitle,
|
||||||
Prompt: prompt,
|
Prompt: prompt,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
gui.logAction(gui.Tr.Actions.Merge)
|
gui.c.LogAction(gui.c.Tr.Actions.Merge)
|
||||||
err := gui.Git.Branch.Merge(branchName, git_commands.MergeOpts{})
|
err := gui.git.Branch.Merge(branchName, git_commands.MergeOpts{})
|
||||||
return gui.handleGenericMergeCommandResult(err)
|
return gui.checkMergeOrRebase(err)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleMerge() error {
|
func (gui *Gui) handleMerge() error {
|
||||||
if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedBranchName := gui.getSelectedBranch().Name
|
selectedBranchName := gui.getSelectedBranch().Name
|
||||||
return gui.mergeBranchIntoCheckedOutBranch(selectedBranchName)
|
return gui.mergeBranchIntoCheckedOutBranch(selectedBranchName)
|
||||||
}
|
}
|
||||||
@ -359,29 +287,25 @@ func (gui *Gui) handleRebaseOntoLocalBranch() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleRebaseOntoBranch(selectedBranchName string) error {
|
func (gui *Gui) handleRebaseOntoBranch(selectedBranchName string) error {
|
||||||
if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
checkedOutBranch := gui.getCheckedOutBranch().Name
|
checkedOutBranch := gui.getCheckedOutBranch().Name
|
||||||
if selectedBranchName == checkedOutBranch {
|
if selectedBranchName == checkedOutBranch {
|
||||||
return gui.PopupHandler.ErrorMsg(gui.Tr.CantRebaseOntoSelf)
|
return gui.c.ErrorMsg(gui.c.Tr.CantRebaseOntoSelf)
|
||||||
}
|
}
|
||||||
prompt := utils.ResolvePlaceholderString(
|
prompt := utils.ResolvePlaceholderString(
|
||||||
gui.Tr.ConfirmRebase,
|
gui.c.Tr.ConfirmRebase,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"checkedOutBranch": checkedOutBranch,
|
"checkedOutBranch": checkedOutBranch,
|
||||||
"selectedBranch": selectedBranchName,
|
"selectedBranch": selectedBranchName,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.RebasingTitle,
|
Title: gui.c.Tr.RebasingTitle,
|
||||||
Prompt: prompt,
|
Prompt: prompt,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
gui.logAction(gui.Tr.Actions.RebaseBranch)
|
gui.c.LogAction(gui.c.Tr.Actions.RebaseBranch)
|
||||||
err := gui.Git.Rebase.RebaseBranch(selectedBranchName)
|
err := gui.git.Rebase.RebaseBranch(selectedBranchName)
|
||||||
return gui.handleGenericMergeCommandResult(err)
|
return gui.checkMergeOrRebase(err)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -393,35 +317,35 @@ func (gui *Gui) handleFastForward() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !branch.IsTrackingRemote() {
|
if !branch.IsTrackingRemote() {
|
||||||
return gui.PopupHandler.ErrorMsg(gui.Tr.FwdNoUpstream)
|
return gui.c.ErrorMsg(gui.c.Tr.FwdNoUpstream)
|
||||||
}
|
}
|
||||||
if !branch.RemoteBranchStoredLocally() {
|
if !branch.RemoteBranchStoredLocally() {
|
||||||
return gui.PopupHandler.ErrorMsg(gui.Tr.FwdNoLocalUpstream)
|
return gui.c.ErrorMsg(gui.c.Tr.FwdNoLocalUpstream)
|
||||||
}
|
}
|
||||||
if branch.HasCommitsToPush() {
|
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(
|
message := utils.ResolvePlaceholderString(
|
||||||
gui.Tr.Fetching,
|
gui.c.Tr.Fetching,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"from": fmt.Sprintf("%s/%s", branch.UpstreamRemote, branch.UpstreamBranch),
|
"from": fmt.Sprintf("%s/%s", branch.UpstreamRemote, branch.UpstreamBranch),
|
||||||
"to": branch.Name,
|
"to": branch.Name,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return gui.PopupHandler.WithLoaderPanel(message, func() error {
|
return gui.c.WithLoaderPanel(message, func() error {
|
||||||
if gui.State.Panels.Branches.SelectedLineIdx == 0 {
|
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 {
|
} else {
|
||||||
gui.logAction(action)
|
gui.c.LogAction(action)
|
||||||
err := gui.Git.Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
|
err := gui.git.Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
|
||||||
if err != nil {
|
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
|
return nil
|
||||||
@ -434,7 +358,7 @@ func (gui *Gui) handleCreateResetToBranchMenu() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.createResetMenu(branch.Name)
|
return gui.refHelper.CreateGitResetMenu(branch.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleRenameBranch() error {
|
func (gui *Gui) handleRenameBranch() error {
|
||||||
@ -444,13 +368,13 @@ func (gui *Gui) handleRenameBranch() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
promptForNewName := func() error {
|
promptForNewName := func() error {
|
||||||
return gui.PopupHandler.Prompt(popup.PromptOpts{
|
return gui.c.Prompt(popup.PromptOpts{
|
||||||
Title: gui.Tr.NewBranchNamePrompt + " " + branch.Name + ":",
|
Title: gui.c.Tr.NewBranchNamePrompt + " " + branch.Name + ":",
|
||||||
InitialContent: branch.Name,
|
InitialContent: branch.Name,
|
||||||
HandleConfirm: func(newBranchName string) error {
|
HandleConfirm: func(newBranchName string) error {
|
||||||
gui.logAction(gui.Tr.Actions.RenameBranch)
|
gui.c.LogAction(gui.c.Tr.Actions.RenameBranch)
|
||||||
if err := gui.Git.Branch.Rename(branch.Name, newBranchName); err != nil {
|
if err := gui.git.Branch.Rename(branch.Name, newBranchName); err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
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
|
// 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 promptForNewName()
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.LcRenameBranch,
|
Title: gui.c.Tr.LcRenameBranch,
|
||||||
Prompt: gui.Tr.RenameBranchWarning,
|
Prompt: gui.c.Tr.RenameBranchWarning,
|
||||||
HandleConfirm: promptForNewName,
|
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 {
|
func (gui *Gui) handleNewBranchOffCurrentItem() error {
|
||||||
context := gui.currentSideListContext()
|
context := gui.currentSideListContext()
|
||||||
|
|
||||||
@ -501,7 +418,7 @@ func (gui *Gui) handleNewBranchOffCurrentItem() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message := utils.ResolvePlaceholderString(
|
message := utils.ResolvePlaceholderString(
|
||||||
gui.Tr.NewBranchNameBranchOff,
|
gui.c.Tr.NewBranchNameBranchOff,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"branchName": item.Description(),
|
"branchName": item.Description(),
|
||||||
},
|
},
|
||||||
@ -513,12 +430,12 @@ func (gui *Gui) handleNewBranchOffCurrentItem() error {
|
|||||||
prefilledName = strings.SplitAfterN(item.ID(), "/", 2)[1]
|
prefilledName = strings.SplitAfterN(item.ID(), "/", 2)[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.PopupHandler.Prompt(popup.PromptOpts{
|
return gui.c.Prompt(popup.PromptOpts{
|
||||||
Title: message,
|
Title: message,
|
||||||
InitialContent: prefilledName,
|
InitialContent: prefilledName,
|
||||||
HandleConfirm: func(response string) error {
|
HandleConfirm: func(response string) error {
|
||||||
gui.logAction(gui.Tr.Actions.CreateBranch)
|
gui.c.LogAction(gui.c.Tr.Actions.CreateBranch)
|
||||||
if err := gui.Git.Branch.New(sanitizedBranchName(response), item.ID()); err != nil {
|
if err := gui.git.Branch.New(sanitizedBranchName(response), item.ID()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,14 +446,14 @@ func (gui *Gui) handleNewBranchOffCurrentItem() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if context.GetKey() != gui.State.Contexts.Branches.GetKey() {
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.State.Panels.Branches.SelectedLineIdx = 0
|
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 (
|
import (
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/popup"
|
"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
|
// 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 {
|
func (gui *Gui) resetCherryPickingIfNecessary(context types.Context) error {
|
||||||
oldContextKey := ContextKey(gui.State.Modes.CherryPicking.ContextKey)
|
oldContextKey := types.ContextKey(gui.State.Modes.CherryPicking.ContextKey)
|
||||||
|
|
||||||
if oldContextKey != context.GetKey() {
|
if oldContextKey != context.GetKey() {
|
||||||
// need to reset the cherry picking mode
|
// need to reset the cherry picking mode
|
||||||
@ -22,10 +23,6 @@ func (gui *Gui) resetCherryPickingIfNecessary(context Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCopyCommit() 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.
|
// get currently selected commit, add the sha to state.
|
||||||
context := gui.currentSideListContext()
|
context := gui.currentSideListContext()
|
||||||
if context == nil {
|
if context == nil {
|
||||||
@ -80,7 +77,7 @@ func (gui *Gui) commitsListForContext() []*models.Commit {
|
|||||||
case SUB_COMMITS_CONTEXT_KEY:
|
case SUB_COMMITS_CONTEXT_KEY:
|
||||||
return gui.State.SubCommits
|
return gui.State.SubCommits
|
||||||
default:
|
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
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,10 +99,6 @@ func (gui *Gui) addCommitToCherryPickedCommits(index int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCopyCommitRange() error {
|
func (gui *Gui) handleCopyCommitRange() error {
|
||||||
if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// get currently selected commit, add the sha to state.
|
// get currently selected commit, add the sha to state.
|
||||||
context := gui.currentSideListContext()
|
context := gui.currentSideListContext()
|
||||||
if context == nil {
|
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
|
// HandlePasteCommits begins a cherry-pick rebase with the commits the user has copied
|
||||||
func (gui *Gui) HandlePasteCommits() error {
|
func (gui *Gui) HandlePasteCommits() error {
|
||||||
if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
|
return gui.c.Ask(popup.AskOpts{
|
||||||
return err
|
Title: gui.c.Tr.CherryPick,
|
||||||
}
|
Prompt: gui.c.Tr.SureCherryPick,
|
||||||
|
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
|
||||||
Title: gui.Tr.CherryPick,
|
|
||||||
Prompt: gui.Tr.SureCherryPick,
|
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
return gui.PopupHandler.WithWaitingStatus(gui.Tr.CherryPickingStatus, func() error {
|
return gui.c.WithWaitingStatus(gui.c.Tr.CherryPickingStatus, func() error {
|
||||||
gui.logAction(gui.Tr.Actions.CherryPick)
|
gui.c.LogAction(gui.c.Tr.Actions.CherryPick)
|
||||||
err := gui.Git.Rebase.CherryPickCommits(gui.State.Modes.CherryPicking.CherryPickedCommits)
|
err := gui.git.Rebase.CherryPickCommits(gui.State.Modes.CherryPicking.CherryPickedCommits)
|
||||||
return gui.handleGenericMergeCommandResult(err)
|
return gui.checkMergeOrRebase(err)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) exitCherryPickingMode() error {
|
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.ContextKey = ""
|
||||||
gui.State.Modes.CherryPicking.CherryPickedCommits = nil
|
gui.State.Modes.CherryPicking.CherryPickedCommits = nil
|
||||||
|
|
||||||
if contextKey == "" {
|
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 nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.rerenderContextViewIfPresent(contextKey)
|
return gui.rerenderContextViewIfPresent(contextKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) rerenderContextViewIfPresent(contextKey ContextKey) error {
|
func (gui *Gui) rerenderContextViewIfPresent(contextKey types.ContextKey) error {
|
||||||
if contextKey == "" {
|
if contextKey == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -184,11 +173,11 @@ func (gui *Gui) rerenderContextViewIfPresent(contextKey ContextKey) error {
|
|||||||
|
|
||||||
view, err := gui.g.View(viewName)
|
view, err := gui.g.View(viewName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gui.Log.Error(err)
|
gui.c.Log.Error(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ContextKey(view.Context) == contextKey {
|
if types.ContextKey(view.Context) == contextKey {
|
||||||
if err := context.HandleRender(); err != nil {
|
if err := context.HandleRender(); err != nil {
|
||||||
return err
|
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.
|
// 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
|
// We pass logCommand to our OSCommand struct so that it can handle logging commands
|
||||||
// for us.
|
// for us.
|
||||||
func (gui *Gui) logAction(action string) {
|
func (gui *Gui) LogAction(action string) {
|
||||||
if gui.Views.Extras == nil {
|
if gui.Views.Extras == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -32,7 +32,7 @@ func (gui *Gui) logAction(action string) {
|
|||||||
fmt.Fprint(gui.Views.Extras, "\n"+style.FgYellow.Sprint(action))
|
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 {
|
if gui.Views.Extras == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -52,23 +52,23 @@ func (gui *Gui) logCommand(cmdStr string, commandLine bool) {
|
|||||||
|
|
||||||
func (gui *Gui) printCommandLogHeader() {
|
func (gui *Gui) printCommandLogHeader() {
|
||||||
introStr := fmt.Sprintf(
|
introStr := fmt.Sprintf(
|
||||||
gui.Tr.CommandLogHeader,
|
gui.c.Tr.CommandLogHeader,
|
||||||
gui.getKeyDisplay(gui.UserConfig.Keybinding.Universal.ExtrasMenu),
|
gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.ExtrasMenu),
|
||||||
)
|
)
|
||||||
fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr))
|
fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr))
|
||||||
|
|
||||||
if gui.UserConfig.Gui.ShowRandomTip {
|
if gui.c.UserConfig.Gui.ShowRandomTip {
|
||||||
fmt.Fprintf(
|
fmt.Fprintf(
|
||||||
gui.Views.Extras,
|
gui.Views.Extras,
|
||||||
"%s: %s",
|
"%s: %s",
|
||||||
style.FgYellow.Sprint(gui.Tr.RandomTip),
|
style.FgYellow.Sprint(gui.c.Tr.RandomTip),
|
||||||
style.FgGreen.Sprint(gui.getRandomTip()),
|
style.FgGreen.Sprint(gui.getRandomTip()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getRandomTip() string {
|
func (gui *Gui) getRandomTip() string {
|
||||||
config := gui.UserConfig.Keybinding
|
config := gui.c.UserConfig.Keybinding
|
||||||
|
|
||||||
formattedKey := func(key string) string {
|
formattedKey := func(key string) string {
|
||||||
return gui.getKeyDisplay(key)
|
return gui.getKeyDisplay(key)
|
||||||
|
@ -3,6 +3,7 @@ package gui
|
|||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
"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/filetree"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/popup"
|
"github.com/jesseduffield/lazygit/pkg/gui/popup"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
@ -47,7 +48,7 @@ func (gui *Gui) commitFilesRenderToMain() error {
|
|||||||
to := gui.State.CommitFileTreeViewModel.GetParent()
|
to := gui.State.CommitFileTreeViewModel.GetParent()
|
||||||
from, reverse := gui.getFromAndReverseArgsForDiff(to)
|
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())
|
task := NewRunPtyTask(cmdObj.GetCmd())
|
||||||
|
|
||||||
return gui.refreshMainViews(refreshMainOpts{
|
return gui.refreshMainViews(refreshMainOpts{
|
||||||
@ -65,12 +66,12 @@ func (gui *Gui) handleCheckoutCommitFile() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.logAction(gui.Tr.Actions.CheckoutFile)
|
gui.c.LogAction(gui.c.Tr.Actions.CheckoutFile)
|
||||||
if err := gui.Git.WorkingTree.CheckoutFile(gui.State.CommitFileTreeViewModel.GetParent(), node.GetPath()); err != nil {
|
if err := gui.git.WorkingTree.CheckoutFile(gui.State.CommitFileTreeViewModel.GetParent(), node.GetPath()); err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
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 {
|
func (gui *Gui) handleDiscardOldFileChange() error {
|
||||||
@ -80,19 +81,19 @@ func (gui *Gui) handleDiscardOldFileChange() error {
|
|||||||
|
|
||||||
fileName := gui.getSelectedCommitFileName()
|
fileName := gui.getSelectedCommitFileName()
|
||||||
|
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.DiscardFileChangesTitle,
|
Title: gui.c.Tr.DiscardFileChangesTitle,
|
||||||
Prompt: gui.Tr.DiscardFileChangesPrompt,
|
Prompt: gui.c.Tr.DiscardFileChangesPrompt,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
|
return gui.c.WithWaitingStatus(gui.c.Tr.RebasingStatus, func() error {
|
||||||
gui.logAction(gui.Tr.Actions.DiscardOldFileChange)
|
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.git.Rebase.DiscardOldFileChanges(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, fileName); err != nil {
|
||||||
if err := gui.handleGenericMergeCommandResult(err); err != nil {
|
if err := gui.checkMergeOrRebase(err); err != nil {
|
||||||
return err
|
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
|
to := gui.State.Panels.CommitFiles.refName
|
||||||
from, reverse := gui.getFromAndReverseArgsForDiff(to)
|
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 {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
gui.State.CommitFileTreeViewModel.SetParent(to)
|
gui.State.CommitFileTreeViewModel.SetParent(to)
|
||||||
gui.State.CommitFileTreeViewModel.SetFiles(files)
|
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 {
|
func (gui *Gui) handleOpenOldCommitFile() error {
|
||||||
@ -125,7 +126,7 @@ func (gui *Gui) handleOpenOldCommitFile() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.openFile(node.GetPath())
|
return gui.fileHelper.OpenFile(node.GetPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleEditCommitFile() error {
|
func (gui *Gui) handleEditCommitFile() error {
|
||||||
@ -135,10 +136,10 @@ func (gui *Gui) handleEditCommitFile() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if node.File == nil {
|
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 {
|
func (gui *Gui) handleToggleFileForPatch() error {
|
||||||
@ -148,7 +149,7 @@ func (gui *Gui) handleToggleFileForPatch() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleTheFile := func() error {
|
toggleTheFile := func() error {
|
||||||
if !gui.Git.Patch.PatchManager.Active() {
|
if !gui.git.Patch.PatchManager.Active() {
|
||||||
if err := gui.startPatchManager(); err != nil {
|
if err := gui.startPatchManager(); err != nil {
|
||||||
return err
|
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,
|
// if there is any file that hasn't been fully added we'll fully add everything,
|
||||||
// otherwise we'll remove everything
|
// otherwise we'll remove everything
|
||||||
adding := node.AnyFile(func(file *models.CommitFile) bool {
|
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 {
|
err := node.ForEachFile(func(file *models.CommitFile) error {
|
||||||
if adding {
|
if adding {
|
||||||
return gui.Git.Patch.PatchManager.AddFileWhole(file.Name)
|
return gui.git.Patch.PatchManager.AddFileWhole(file.Name)
|
||||||
} else {
|
} else {
|
||||||
return gui.Git.Patch.PatchManager.RemoveFile(file.Name)
|
return gui.git.Patch.PatchManager.RemoveFile(file.Name)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if gui.Git.Patch.PatchManager.IsEmpty() {
|
if gui.git.Patch.PatchManager.IsEmpty() {
|
||||||
gui.Git.Patch.PatchManager.Reset()
|
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() {
|
if gui.git.Patch.PatchManager.Active() && gui.git.Patch.PatchManager.To != gui.State.CommitFileTreeViewModel.GetParent() {
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.DiscardPatch,
|
Title: gui.c.Tr.DiscardPatch,
|
||||||
Prompt: gui.Tr.DiscardPatchConfirm,
|
Prompt: gui.c.Tr.DiscardPatchConfirm,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
gui.Git.Patch.PatchManager.Reset()
|
gui.git.Patch.PatchManager.Reset()
|
||||||
return toggleTheFile()
|
return toggleTheFile()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -199,15 +200,15 @@ func (gui *Gui) startPatchManager() error {
|
|||||||
to := gui.State.Panels.CommitFiles.refName
|
to := gui.State.Panels.CommitFiles.refName
|
||||||
from, reverse := gui.getFromAndReverseArgsForDiff(to)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleEnterCommitFile() error {
|
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()
|
node := gui.getSelectedCommitFileNode()
|
||||||
if node == nil {
|
if node == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -218,21 +219,21 @@ func (gui *Gui) enterCommitFile(opts OnFocusOpts) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enterTheFile := func() error {
|
enterTheFile := func() error {
|
||||||
if !gui.Git.Patch.PatchManager.Active() {
|
if !gui.git.Patch.PatchManager.Active() {
|
||||||
if err := gui.startPatchManager(); err != nil {
|
if err := gui.startPatchManager(); err != nil {
|
||||||
return err
|
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() {
|
if gui.git.Patch.PatchManager.Active() && gui.git.Patch.PatchManager.To != gui.State.CommitFileTreeViewModel.GetParent() {
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.DiscardPatch,
|
Title: gui.c.Tr.DiscardPatch,
|
||||||
Prompt: gui.Tr.DiscardPatchConfirm,
|
Prompt: gui.c.Tr.DiscardPatchConfirm,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
gui.Git.Patch.PatchManager.Reset()
|
gui.git.Patch.PatchManager.Reset()
|
||||||
return enterTheFile()
|
return enterTheFile()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -249,29 +250,29 @@ func (gui *Gui) handleToggleCommitFileDirCollapsed() error {
|
|||||||
|
|
||||||
gui.State.CommitFileTreeViewModel.ToggleCollapsed(node.GetPath())
|
gui.State.CommitFileTreeViewModel.ToggleCollapsed(node.GetPath())
|
||||||
|
|
||||||
if err := gui.postRefreshUpdate(gui.State.Contexts.CommitFiles); err != nil {
|
if err := gui.c.PostRefreshUpdate(gui.State.Contexts.CommitFiles); err != nil {
|
||||||
gui.Log.Error(err)
|
gui.c.Log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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
|
// 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.
|
// no longer considers the commitFiles view as its main view.
|
||||||
gui.resetWindowForView(gui.Views.CommitFiles)
|
gui.resetWindowForView(gui.Views.CommitFiles)
|
||||||
|
|
||||||
gui.State.Panels.CommitFiles.SelectedLineIdx = 0
|
gui.State.Panels.CommitFiles.SelectedLineIdx = 0
|
||||||
gui.State.Panels.CommitFiles.refName = refName
|
gui.State.Panels.CommitFiles.refName = opts.RefName
|
||||||
gui.State.Panels.CommitFiles.canRebase = canRebase
|
gui.State.Panels.CommitFiles.canRebase = opts.CanRebase
|
||||||
gui.State.Contexts.CommitFiles.SetParentContext(context)
|
gui.State.Contexts.CommitFiles.SetParentContext(opts.Context)
|
||||||
gui.State.Contexts.CommitFiles.SetWindowName(windowName)
|
gui.State.Contexts.CommitFiles.SetWindowName(opts.WindowName)
|
||||||
|
|
||||||
if err := gui.refreshCommitFilesView(); err != nil {
|
if err := gui.refreshCommitFilesView(); err != nil {
|
||||||
return err
|
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
|
// 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 gui.c.PostRefreshUpdate(gui.State.Contexts.CommitFiles)
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := gui.State.Contexts.CommitFiles.HandleFocus(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,14 @@ func (gui *Gui) handleCommitConfirm() error {
|
|||||||
message := strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent())
|
message := strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent())
|
||||||
gui.State.failedCommitMessage = message
|
gui.State.failedCommitMessage = message
|
||||||
if message == "" {
|
if message == "" {
|
||||||
return gui.PopupHandler.ErrorMsg(gui.Tr.CommitWithoutMessageErr)
|
return gui.c.ErrorMsg(gui.c.Tr.CommitWithoutMessageErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdObj := gui.Git.Commit.CommitCmdObj(message)
|
cmdObj := gui.git.Commit.CommitCmdObj(message)
|
||||||
gui.logAction(gui.Tr.Actions.Commit)
|
gui.c.LogAction(gui.c.Tr.Actions.Commit)
|
||||||
|
|
||||||
_ = gui.returnFromContext()
|
_ = 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.Views.CommitMessage.ClearTextArea()
|
||||||
gui.State.failedCommitMessage = ""
|
gui.State.failedCommitMessage = ""
|
||||||
return nil
|
return nil
|
||||||
@ -32,14 +32,16 @@ func (gui *Gui) handleCommitClose() error {
|
|||||||
|
|
||||||
func (gui *Gui) handleCommitMessageFocused() error {
|
func (gui *Gui) handleCommitMessageFocused() error {
|
||||||
message := utils.ResolvePlaceholderString(
|
message := utils.ResolvePlaceholderString(
|
||||||
gui.Tr.CommitMessageConfirm,
|
gui.c.Tr.CommitMessageConfirm,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"keyBindClose": gui.getKeyDisplay(gui.UserConfig.Keybinding.Universal.Return),
|
"keyBindClose": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.Return),
|
||||||
"keyBindConfirm": gui.getKeyDisplay(gui.UserConfig.Keybinding.Universal.Confirm),
|
"keyBindConfirm": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.Confirm),
|
||||||
"keyBindNewLine": gui.getKeyDisplay(gui.UserConfig.Keybinding.Universal.AppendNewline),
|
"keyBindNewLine": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.AppendNewline),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
gui.RenderCommitLength()
|
||||||
|
|
||||||
return gui.renderString(gui.Views.Options, message)
|
return gui.renderString(gui.Views.Options, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +51,7 @@ func (gui *Gui) getBufferLength(view *gocui.View) string {
|
|||||||
|
|
||||||
// RenderCommitLength is a function.
|
// RenderCommitLength is a function.
|
||||||
func (gui *Gui) RenderCommitLength() {
|
func (gui *Gui) RenderCommitLength() {
|
||||||
if !gui.UserConfig.Gui.CommitLength.Show {
|
if !gui.c.UserConfig.Gui.CommitLength.Show {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
package gui
|
package gui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"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"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,7 +28,7 @@ func (gui *Gui) onCommitFocus() error {
|
|||||||
state.LimitCommits = false
|
state.LimitCommits = false
|
||||||
go utils.Safe(func() {
|
go utils.Safe(func() {
|
||||||
if err := gui.refreshCommitsWithLimit(); err != nil {
|
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
|
var task updateTask
|
||||||
commit := gui.getSelectedLocalCommit()
|
commit := gui.getSelectedLocalCommit()
|
||||||
if commit == nil {
|
if commit == nil {
|
||||||
task = NewRenderStringTask(gui.Tr.NoCommitsThisBranch)
|
task = NewRenderStringTask(gui.c.Tr.NoCommitsThisBranch)
|
||||||
} else {
|
} 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())
|
task = NewRunPtyTask(cmdObj.GetCmd())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +115,7 @@ func (gui *Gui) refreshCommitsWithLimit() error {
|
|||||||
gui.Mutexes.BranchCommitsMutex.Lock()
|
gui.Mutexes.BranchCommitsMutex.Lock()
|
||||||
defer gui.Mutexes.BranchCommitsMutex.Unlock()
|
defer gui.Mutexes.BranchCommitsMutex.Unlock()
|
||||||
|
|
||||||
commits, err := gui.Git.Loaders.Commits.GetCommits(
|
commits, err := gui.git.Loaders.Commits.GetCommits(
|
||||||
loaders.GetCommitsOptions{
|
loaders.GetCommitsOptions{
|
||||||
Limit: gui.State.Panels.Commits.LimitCommits,
|
Limit: gui.State.Panels.Commits.LimitCommits,
|
||||||
FilterPath: gui.State.Modes.Filtering.GetPath(),
|
FilterPath: gui.State.Modes.Filtering.GetPath(),
|
||||||
@ -132,11 +129,11 @@ func (gui *Gui) refreshCommitsWithLimit() error {
|
|||||||
}
|
}
|
||||||
gui.State.Commits = commits
|
gui.State.Commits = commits
|
||||||
|
|
||||||
return gui.postRefreshUpdate(gui.State.Contexts.BranchCommits)
|
return gui.c.PostRefreshUpdate(gui.State.Contexts.BranchCommits)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) refForLog() string {
|
func (gui *Gui) refForLog() string {
|
||||||
bisectInfo := gui.Git.Bisect.GetInfo()
|
bisectInfo := gui.git.Bisect.GetInfo()
|
||||||
gui.State.BisectInfo = bisectInfo
|
gui.State.BisectInfo = bisectInfo
|
||||||
|
|
||||||
if !bisectInfo.Started() {
|
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.
|
// 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()
|
return bisectInfo.GetNewSha()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,691 +152,11 @@ func (gui *Gui) refreshRebaseCommits() error {
|
|||||||
gui.Mutexes.BranchCommitsMutex.Lock()
|
gui.Mutexes.BranchCommitsMutex.Lock()
|
||||||
defer gui.Mutexes.BranchCommitsMutex.Unlock()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
gui.State.Commits = updatedCommits
|
gui.State.Commits = updatedCommits
|
||||||
|
|
||||||
return gui.postRefreshUpdate(gui.State.Contexts.BranchCommits)
|
return gui.c.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
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ func (gui *Gui) wrappedConfirmationFunction(handlersManageFocus bool, function f
|
|||||||
|
|
||||||
if function != nil {
|
if function != nil {
|
||||||
if err := function(); err != 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 function != nil {
|
||||||
if err := function(getResponse()); err != 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
|
suggestionsView.FgColor = theme.GocuiDefaultTextColor
|
||||||
gui.setSuggestions(findSuggestionsFunc(""))
|
gui.setSuggestions(findSuggestionsFunc(""))
|
||||||
suggestionsView.Visible = true
|
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
|
return nil
|
||||||
@ -171,12 +171,12 @@ func (gui *Gui) createPopupPanel(opts popup.CreatePopupPanelOpts) error {
|
|||||||
return err
|
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 {
|
func (gui *Gui) setKeyBindings(opts popup.CreatePopupPanelOpts) error {
|
||||||
actions := utils.ResolvePlaceholderString(
|
actions := utils.ResolvePlaceholderString(
|
||||||
gui.Tr.CloseConfirm,
|
gui.c.Tr.CloseConfirm,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"keyBindClose": "esc",
|
"keyBindClose": "esc",
|
||||||
"keyBindConfirm": "enter",
|
"keyBindConfirm": "enter",
|
||||||
@ -197,7 +197,7 @@ func (gui *Gui) setKeyBindings(opts popup.CreatePopupPanelOpts) error {
|
|||||||
handler func() error
|
handler func() error
|
||||||
}
|
}
|
||||||
|
|
||||||
keybindingConfig := gui.UserConfig.Keybinding
|
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||||
onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(
|
onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(
|
||||||
opts.HandlersManageFocus,
|
opts.HandlersManageFocus,
|
||||||
opts.HandleConfirmPrompt,
|
opts.HandleConfirmPrompt,
|
||||||
@ -262,7 +262,7 @@ func (gui *Gui) setKeyBindings(opts popup.CreatePopupPanelOpts) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) clearConfirmationViewKeyBindings() {
|
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.Confirm), gocui.ModNone)
|
||||||
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
|
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
|
||||||
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Return), 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()
|
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"
|
"fmt"
|
||||||
|
|
||||||
"github.com/jesseduffield/gocui"
|
"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 {
|
func (gui *Gui) popupViewNames() []string {
|
||||||
result := []string{}
|
result := []string{}
|
||||||
for _, context := range gui.allContexts() {
|
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())
|
result = append(result, context.GetViewName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,7 +19,7 @@ func (gui *Gui) popupViewNames() []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) currentContextKeyIgnoringPopups() ContextKey {
|
func (gui *Gui) currentContextKeyIgnoringPopups() types.ContextKey {
|
||||||
gui.State.ContextManager.RLock()
|
gui.State.ContextManager.RLock()
|
||||||
defer gui.State.ContextManager.RUnlock()
|
defer gui.State.ContextManager.RUnlock()
|
||||||
|
|
||||||
@ -60,7 +29,7 @@ func (gui *Gui) currentContextKeyIgnoringPopups() ContextKey {
|
|||||||
reversedIndex := len(stack) - 1 - i
|
reversedIndex := len(stack) - 1 - i
|
||||||
context := stack[reversedIndex]
|
context := stack[reversedIndex]
|
||||||
kind := stack[reversedIndex].GetKind()
|
kind := stack[reversedIndex].GetKind()
|
||||||
if kind != TEMPORARY_POPUP && kind != PERSISTENT_POPUP {
|
if kind != types.TEMPORARY_POPUP && kind != types.PERSISTENT_POPUP {
|
||||||
return context.GetKey()
|
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
|
// 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.
|
// 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()
|
gui.State.ContextManager.Lock()
|
||||||
defer gui.State.ContextManager.Unlock()
|
defer gui.State.ContextManager.Unlock()
|
||||||
|
|
||||||
if len(gui.State.ContextManager.ContextStack) == 0 {
|
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||||
gui.State.ContextManager.ContextStack = []Context{c}
|
gui.State.ContextManager.ContextStack = []types.Context{c}
|
||||||
} else {
|
} else {
|
||||||
// replace the last item with the given item
|
// 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)
|
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)
|
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
|
// using triple dot but you should only ever pass one of these opt structs
|
||||||
if len(opts) > 1 {
|
if len(opts) > 1 {
|
||||||
return errors.New("cannot pass multiple opts to pushContext")
|
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
|
// push onto stack
|
||||||
// if we are switching to a side context, remove all other contexts in the 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 {
|
for _, stackContext := range gui.State.ContextManager.ContextStack {
|
||||||
if stackContext.GetKey() != c.GetKey() {
|
if stackContext.GetKey() != c.GetKey() {
|
||||||
if err := gui.deactivateContext(stackContext); err != nil {
|
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() {
|
} 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)
|
// 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.
|
// 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
|
// 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
|
// look up the context currently active for that view and switch to that context
|
||||||
func (gui *Gui) pushContextWithView(viewName string) error {
|
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 {
|
func (gui *Gui) returnFromContext() error {
|
||||||
@ -151,7 +120,7 @@ func (gui *Gui) returnFromContext() error {
|
|||||||
return gui.activateContext(newContext)
|
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())
|
view, _ := gui.g.View(c.GetViewName())
|
||||||
|
|
||||||
if view != nil && view.IsSearching() {
|
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 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
|
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
|
// 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 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.
|
// 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())
|
v, err := gui.g.View(c.GetViewName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ContextKey(v.Context) != c.GetKey() {
|
if types.ContextKey(v.Context) != c.GetKey() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,13 +167,13 @@ func (gui *Gui) postRefreshUpdate(c Context) error {
|
|||||||
return nil
|
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()
|
viewName := c.GetViewName()
|
||||||
v, err := gui.g.View(viewName)
|
v, err := gui.g.View(viewName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.
|
// ensure that any other window for which this view was active is now set to the default for that window.
|
||||||
gui.setViewAsActiveForWindow(v)
|
gui.setViewAsActiveForWindow(v)
|
||||||
@ -260,14 +229,14 @@ func (gui *Gui) activateContext(c Context, opts ...OnFocusOpts) error {
|
|||||||
// return result
|
// return result
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func (gui *Gui) currentContext() Context {
|
func (gui *Gui) currentContext() types.Context {
|
||||||
gui.State.ContextManager.RLock()
|
gui.State.ContextManager.RLock()
|
||||||
defer gui.State.ContextManager.RUnlock()
|
defer gui.State.ContextManager.RUnlock()
|
||||||
|
|
||||||
return gui.currentContextWithoutLock()
|
return gui.currentContextWithoutLock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) currentContextWithoutLock() Context {
|
func (gui *Gui) currentContextWithoutLock() types.Context {
|
||||||
if len(gui.State.ContextManager.ContextStack) == 0 {
|
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||||
return gui.defaultSideContext()
|
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
|
// the status panel is not yet a list context (and may never be), so this method is not
|
||||||
// quite the same as currentSideContext()
|
// quite the same as currentSideContext()
|
||||||
func (gui *Gui) currentSideListContext() IListContext {
|
func (gui *Gui) currentSideListContext() types.IListContext {
|
||||||
context := gui.currentSideContext()
|
context := gui.currentSideContext()
|
||||||
listContext, ok := context.(IListContext)
|
listContext, ok := context.(types.IListContext)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return listContext
|
return listContext
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) currentSideContext() Context {
|
func (gui *Gui) currentSideContext() types.Context {
|
||||||
gui.State.ContextManager.RLock()
|
gui.State.ContextManager.RLock()
|
||||||
defer gui.State.ContextManager.RUnlock()
|
defer gui.State.ContextManager.RUnlock()
|
||||||
|
|
||||||
@ -297,11 +266,11 @@ func (gui *Gui) currentSideContext() Context {
|
|||||||
return gui.defaultSideContext()
|
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 {
|
for i := range stack {
|
||||||
context := stack[len(stack)-1-i]
|
context := stack[len(stack)-1-i]
|
||||||
|
|
||||||
if context.GetKind() == SIDE_CONTEXT {
|
if context.GetKind() == types.SIDE_CONTEXT {
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,7 +279,7 @@ func (gui *Gui) currentSideContext() Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// static as opposed to popup
|
// static as opposed to popup
|
||||||
func (gui *Gui) currentStaticContext() Context {
|
func (gui *Gui) currentStaticContext() types.Context {
|
||||||
gui.State.ContextManager.RLock()
|
gui.State.ContextManager.RLock()
|
||||||
defer gui.State.ContextManager.RUnlock()
|
defer gui.State.ContextManager.RUnlock()
|
||||||
|
|
||||||
@ -324,7 +293,7 @@ func (gui *Gui) currentStaticContext() Context {
|
|||||||
for i := range stack {
|
for i := range stack {
|
||||||
context := stack[len(stack)-1-i]
|
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
|
return context
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -332,7 +301,7 @@ func (gui *Gui) currentStaticContext() Context {
|
|||||||
return gui.defaultSideContext()
|
return gui.defaultSideContext()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) defaultSideContext() Context {
|
func (gui *Gui) defaultSideContext() types.Context {
|
||||||
if gui.State.Modes.Filtering.Active() {
|
if gui.State.Modes.Filtering.Active() {
|
||||||
return gui.State.Contexts.BranchCommits
|
return gui.State.Contexts.BranchCommits
|
||||||
} else {
|
} 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
|
// 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
|
// other views can have their context changed directly but this function helps
|
||||||
// keep the main and secondary views in sync
|
// 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 {
|
if gui.State.MainContext == contextKey {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -432,13 +401,13 @@ func (gui *Gui) viewTabNames(viewName string) []string {
|
|||||||
|
|
||||||
result := make([]string, len(tabContexts))
|
result := make([]string, len(tabContexts))
|
||||||
for i, tabContext := range tabContexts {
|
for i, tabContext := range tabContexts {
|
||||||
result[i] = tabContext.tab
|
result[i] = tabContext.Tab
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
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
|
// 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()]
|
tabContexts, ok := gui.State.ViewTabContextMap[c.GetViewName()]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -446,12 +415,12 @@ func (gui *Gui) setViewTabForContext(c Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for tabIndex, tabContext := range tabContexts {
|
for tabIndex, tabContext := range tabContexts {
|
||||||
for _, context := range tabContext.contexts {
|
for _, context := range tabContext.Contexts {
|
||||||
if context.GetKey() == c.GetKey() {
|
if context.GetKey() == c.GetKey() {
|
||||||
// get the view, set the tab
|
// get the view, set the tab
|
||||||
v, err := gui.g.View(c.GetViewName())
|
v, err := gui.g.View(c.GetViewName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gui.Log.Error(err)
|
gui.c.Log.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
v.TabIndex = tabIndex
|
v.TabIndex = tabIndex
|
||||||
@ -461,12 +430,7 @@ func (gui *Gui) setViewTabForContext(c Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type tabContext struct {
|
func (gui *Gui) mustContextForContextKey(contextKey types.ContextKey) types.Context {
|
||||||
tab string
|
|
||||||
contexts []Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) mustContextForContextKey(contextKey ContextKey) Context {
|
|
||||||
context, ok := gui.contextForContextKey(contextKey)
|
context, ok := gui.contextForContextKey(contextKey)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -476,7 +440,7 @@ func (gui *Gui) mustContextForContextKey(contextKey ContextKey) Context {
|
|||||||
return 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() {
|
for _, context := range gui.allContexts() {
|
||||||
if context.GetKey() == contextKey {
|
if context.GetKey() == contextKey {
|
||||||
return context, true
|
return context, true
|
||||||
@ -487,7 +451,7 @@ func (gui *Gui) contextForContextKey(contextKey ContextKey) (Context, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) rerenderView(view *gocui.View) error {
|
func (gui *Gui) rerenderView(view *gocui.View) error {
|
||||||
contextKey := ContextKey(view.Context)
|
contextKey := types.ContextKey(view.Context)
|
||||||
context := gui.mustContextForContextKey(contextKey)
|
context := gui.mustContextForContextKey(contextKey)
|
||||||
|
|
||||||
return context.HandleRender()
|
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
|
package gui
|
||||||
|
|
||||||
type ContextKey string
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||||
const (
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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,
|
STATUS_CONTEXT_KEY,
|
||||||
FILES_CONTEXT_KEY,
|
FILES_CONTEXT_KEY,
|
||||||
LOCAL_BRANCHES_CONTEXT_KEY,
|
LOCAL_BRANCHES_CONTEXT_KEY,
|
||||||
@ -54,34 +57,8 @@ var allContextKeys = []ContextKey{
|
|||||||
COMMAND_LOG_CONTEXT_KEY,
|
COMMAND_LOG_CONTEXT_KEY,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContextTree struct {
|
func (gui *Gui) allContexts() []types.Context {
|
||||||
Status Context
|
return []types.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{
|
|
||||||
gui.State.Contexts.Status,
|
gui.State.Contexts.Status,
|
||||||
gui.State.Contexts.Files,
|
gui.State.Contexts.Files,
|
||||||
gui.State.Contexts.Submodules,
|
gui.State.Contexts.Submodules,
|
||||||
@ -107,11 +84,11 @@ func (gui *Gui) allContexts() []Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) contextTree() ContextTree {
|
func (gui *Gui) contextTree() context.ContextTree {
|
||||||
return ContextTree{
|
return context.ContextTree{
|
||||||
Status: &BasicContext{
|
Status: &BasicContext{
|
||||||
OnRenderToMain: OnFocusWrapper(gui.statusRenderToMain),
|
OnRenderToMain: OnFocusWrapper(gui.statusRenderToMain),
|
||||||
Kind: SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
ViewName: "status",
|
ViewName: "status",
|
||||||
Key: STATUS_CONTEXT_KEY,
|
Key: STATUS_CONTEXT_KEY,
|
||||||
},
|
},
|
||||||
@ -128,15 +105,15 @@ func (gui *Gui) contextTree() ContextTree {
|
|||||||
Tags: gui.tagsListContext(),
|
Tags: gui.tagsListContext(),
|
||||||
Stash: gui.stashListContext(),
|
Stash: gui.stashListContext(),
|
||||||
Normal: &BasicContext{
|
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
|
return nil // TODO: should we do something here? We should allow for scrolling the panel
|
||||||
},
|
},
|
||||||
Kind: MAIN_CONTEXT,
|
Kind: types.MAIN_CONTEXT,
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Key: MAIN_NORMAL_CONTEXT_KEY,
|
Key: MAIN_NORMAL_CONTEXT_KEY,
|
||||||
},
|
},
|
||||||
Staging: &BasicContext{
|
Staging: &BasicContext{
|
||||||
OnFocus: func(opts ...OnFocusOpts) error {
|
OnFocus: func(opts ...types.OnFocusOpts) error {
|
||||||
forceSecondaryFocused := false
|
forceSecondaryFocused := false
|
||||||
selectedLineIdx := -1
|
selectedLineIdx := -1
|
||||||
if len(opts) > 0 && opts[0].ClickedViewName != "" {
|
if len(opts) > 0 && opts[0].ClickedViewName != "" {
|
||||||
@ -149,12 +126,12 @@ func (gui *Gui) contextTree() ContextTree {
|
|||||||
}
|
}
|
||||||
return gui.onStagingFocus(forceSecondaryFocused, selectedLineIdx)
|
return gui.onStagingFocus(forceSecondaryFocused, selectedLineIdx)
|
||||||
},
|
},
|
||||||
Kind: MAIN_CONTEXT,
|
Kind: types.MAIN_CONTEXT,
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Key: MAIN_STAGING_CONTEXT_KEY,
|
Key: MAIN_STAGING_CONTEXT_KEY,
|
||||||
},
|
},
|
||||||
PatchBuilding: &BasicContext{
|
PatchBuilding: &BasicContext{
|
||||||
OnFocus: func(opts ...OnFocusOpts) error {
|
OnFocus: func(opts ...types.OnFocusOpts) error {
|
||||||
selectedLineIdx := -1
|
selectedLineIdx := -1
|
||||||
if len(opts) > 0 && (opts[0].ClickedViewName == "main" || opts[0].ClickedViewName == "secondary") {
|
if len(opts) > 0 && (opts[0].ClickedViewName == "main" || opts[0].ClickedViewName == "secondary") {
|
||||||
selectedLineIdx = opts[0].ClickedViewLineIdx
|
selectedLineIdx = opts[0].ClickedViewLineIdx
|
||||||
@ -162,7 +139,7 @@ func (gui *Gui) contextTree() ContextTree {
|
|||||||
|
|
||||||
return gui.onPatchBuildingFocus(selectedLineIdx)
|
return gui.onPatchBuildingFocus(selectedLineIdx)
|
||||||
},
|
},
|
||||||
Kind: MAIN_CONTEXT,
|
Kind: types.MAIN_CONTEXT,
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Key: MAIN_PATCH_BUILDING_CONTEXT_KEY,
|
Key: MAIN_PATCH_BUILDING_CONTEXT_KEY,
|
||||||
},
|
},
|
||||||
@ -175,30 +152,30 @@ func (gui *Gui) contextTree() ContextTree {
|
|||||||
},
|
},
|
||||||
Credentials: &BasicContext{
|
Credentials: &BasicContext{
|
||||||
OnFocus: OnFocusWrapper(gui.handleAskFocused),
|
OnFocus: OnFocusWrapper(gui.handleAskFocused),
|
||||||
Kind: PERSISTENT_POPUP,
|
Kind: types.PERSISTENT_POPUP,
|
||||||
ViewName: "credentials",
|
ViewName: "credentials",
|
||||||
Key: CREDENTIALS_CONTEXT_KEY,
|
Key: CREDENTIALS_CONTEXT_KEY,
|
||||||
},
|
},
|
||||||
Confirmation: &BasicContext{
|
Confirmation: &BasicContext{
|
||||||
OnFocus: OnFocusWrapper(gui.handleAskFocused),
|
OnFocus: OnFocusWrapper(gui.handleAskFocused),
|
||||||
Kind: TEMPORARY_POPUP,
|
Kind: types.TEMPORARY_POPUP,
|
||||||
ViewName: "confirmation",
|
ViewName: "confirmation",
|
||||||
Key: CONFIRMATION_CONTEXT_KEY,
|
Key: CONFIRMATION_CONTEXT_KEY,
|
||||||
},
|
},
|
||||||
Suggestions: gui.suggestionsListContext(),
|
Suggestions: gui.suggestionsListContext(),
|
||||||
CommitMessage: &BasicContext{
|
CommitMessage: &BasicContext{
|
||||||
OnFocus: OnFocusWrapper(gui.handleCommitMessageFocused),
|
OnFocus: OnFocusWrapper(gui.handleCommitMessageFocused),
|
||||||
Kind: PERSISTENT_POPUP,
|
Kind: types.PERSISTENT_POPUP,
|
||||||
ViewName: "commitMessage",
|
ViewName: "commitMessage",
|
||||||
Key: COMMIT_MESSAGE_CONTEXT_KEY,
|
Key: COMMIT_MESSAGE_CONTEXT_KEY,
|
||||||
},
|
},
|
||||||
Search: &BasicContext{
|
Search: &BasicContext{
|
||||||
Kind: PERSISTENT_POPUP,
|
Kind: types.PERSISTENT_POPUP,
|
||||||
ViewName: "search",
|
ViewName: "search",
|
||||||
Key: SEARCH_CONTEXT_KEY,
|
Key: SEARCH_CONTEXT_KEY,
|
||||||
},
|
},
|
||||||
CommandLog: &BasicContext{
|
CommandLog: &BasicContext{
|
||||||
Kind: EXTRAS_CONTEXT,
|
Kind: types.EXTRAS_CONTEXT,
|
||||||
ViewName: "extras",
|
ViewName: "extras",
|
||||||
Key: COMMAND_LOG_CONTEXT_KEY,
|
Key: COMMAND_LOG_CONTEXT_KEY,
|
||||||
OnGetOptionsMap: gui.getMergingOptions,
|
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
|
// using this wrapper for when an onFocus function doesn't care about any potential
|
||||||
// props that could be passed
|
// props that could be passed
|
||||||
func OnFocusWrapper(f func() error) func(opts ...OnFocusOpts) error {
|
func OnFocusWrapper(f func() error) func(opts ...types.OnFocusOpts) error {
|
||||||
return func(opts ...OnFocusOpts) error {
|
return func(opts ...types.OnFocusOpts) error {
|
||||||
return f()
|
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"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/jesseduffield/lazygit/pkg/common"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/config"
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/popup"
|
"github.com/jesseduffield/lazygit/pkg/gui/popup"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// if Go let me do private struct embedding of structs with public fields (which it should)
|
type SubmodulesController struct {
|
||||||
// I would just do that. But alas.
|
c *ControllerCommon
|
||||||
type ControllerCommon struct {
|
context types.IListContext
|
||||||
*common.Common
|
git *commands.GitCommand
|
||||||
IGuiCommon
|
|
||||||
|
enterSubmodule func(submodule *models.SubmoduleConfig) error
|
||||||
|
getSelectedSubmodule func() *models.SubmoduleConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubmodulesController struct {
|
var _ types.IController = &SubmodulesController{}
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSubmodulesController(
|
func NewSubmodulesController(
|
||||||
c *ControllerCommon,
|
c *ControllerCommon,
|
||||||
enterSubmoduleFn func(submodule *models.SubmoduleConfig) error,
|
context types.IListContext,
|
||||||
git *commands.GitCommand,
|
git *commands.GitCommand,
|
||||||
submodules []*models.SubmoduleConfig,
|
enterSubmodule func(submodule *models.SubmoduleConfig) error,
|
||||||
getSelectedSubmodule func() *models.SubmoduleConfig,
|
getSelectedSubmodule func() *models.SubmoduleConfig,
|
||||||
) *SubmodulesController {
|
) *SubmodulesController {
|
||||||
return &SubmodulesController{
|
return &SubmodulesController{
|
||||||
c: c,
|
c: c,
|
||||||
enterSubmoduleFn: enterSubmoduleFn,
|
context: context,
|
||||||
git: git,
|
git: git,
|
||||||
submodules: submodules,
|
enterSubmodule: enterSubmodule,
|
||||||
getSelectedSubmodule: getSelectedSubmodule,
|
getSelectedSubmodule: getSelectedSubmodule,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *SubmodulesController) Keybindings(getKey func(key string) interface{}, config config.KeybindingConfig) []*types.Binding {
|
func (self *SubmodulesController) Keybindings(getKey func(key string) interface{}, config config.KeybindingConfig, guards types.KeybindingGuards) []*types.Binding {
|
||||||
return []*types.Binding{
|
bindings := []*types.Binding{
|
||||||
{
|
{
|
||||||
Key: getKey(config.Universal.GoInto),
|
Key: getKey(config.Universal.GoInto),
|
||||||
Handler: self.forSubmodule(self.enter),
|
Handler: self.checkSelected(self.enter),
|
||||||
Description: self.c.Tr.LcEnterSubmodule,
|
Description: self.c.Tr.LcEnterSubmodule,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: getKey(config.Universal.Remove),
|
Key: getKey(config.Universal.Remove),
|
||||||
Handler: self.forSubmodule(self.remove),
|
Handler: self.checkSelected(self.remove),
|
||||||
Description: self.c.Tr.LcRemoveSubmodule,
|
Description: self.c.Tr.LcRemoveSubmodule,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: getKey(config.Submodules.Update),
|
Key: getKey(config.Submodules.Update),
|
||||||
Handler: self.forSubmodule(self.update),
|
Handler: self.checkSelected(self.update),
|
||||||
Description: self.c.Tr.LcSubmoduleUpdate,
|
Description: self.c.Tr.LcSubmoduleUpdate,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -73,12 +65,12 @@ func (self *SubmodulesController) Keybindings(getKey func(key string) interface{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: getKey(config.Universal.Edit),
|
Key: getKey(config.Universal.Edit),
|
||||||
Handler: self.forSubmodule(self.editURL),
|
Handler: self.checkSelected(self.editURL),
|
||||||
Description: self.c.Tr.LcEditSubmoduleUrl,
|
Description: self.c.Tr.LcEditSubmoduleUrl,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: getKey(config.Submodules.Init),
|
Key: getKey(config.Submodules.Init),
|
||||||
Handler: self.forSubmodule(self.init),
|
Handler: self.checkSelected(self.init),
|
||||||
Description: self.c.Tr.LcInitSubmodule,
|
Description: self.c.Tr.LcInitSubmodule,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -87,11 +79,17 @@ func (self *SubmodulesController) Keybindings(getKey func(key string) interface{
|
|||||||
Description: self.c.Tr.LcViewBulkSubmoduleOptions,
|
Description: self.c.Tr.LcViewBulkSubmoduleOptions,
|
||||||
OpensMenu: true,
|
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 {
|
func (self *SubmodulesController) enter(submodule *models.SubmoduleConfig) error {
|
||||||
return self.enterSubmoduleFn(submodule)
|
return self.enterSubmodule(submodule)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *SubmodulesController) add() error {
|
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 {
|
return func() error {
|
||||||
submodule := self.getSelectedSubmodule()
|
submodule := self.getSelectedSubmodule()
|
||||||
if submodule == nil {
|
if submodule == nil {
|
||||||
@ -241,3 +239,7 @@ func (self *SubmodulesController) forSubmodule(callback func(*models.SubmoduleCo
|
|||||||
return callback(submodule)
|
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
|
package controllers
|
||||||
|
|
||||||
import (
|
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/popup"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
)
|
)
|
||||||
@ -8,6 +11,54 @@ import (
|
|||||||
type IGuiCommon interface {
|
type IGuiCommon interface {
|
||||||
popup.IPopupHandler
|
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
|
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 (
|
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/commands/types/enums"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/popup"
|
"github.com/jesseduffield/lazygit/pkg/gui/popup"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"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
|
// 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.
|
// 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
|
type ReflogActionKind int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -32,15 +65,113 @@ type reflogAction struct {
|
|||||||
to string
|
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
|
// 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
|
// 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.
|
// 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
|
// 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.
|
// 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.
|
// 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
|
counter := 0
|
||||||
reflogCommits := gui.State.FilteredReflogCommits
|
reflogCommits := self.getFilteredReflogCommits()
|
||||||
rebaseFinishCommitSha := ""
|
rebaseFinishCommitSha := ""
|
||||||
var action *reflogAction
|
var action *reflogAction
|
||||||
for reflogCommitIdx, reflogCommit := range reflogCommits {
|
for reflogCommitIdx, reflogCommit := range reflogCommits {
|
||||||
@ -86,115 +217,42 @@ func (gui *Gui) parseReflogForActions(onUserAction func(counter int, action refl
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) reflogUndo() error {
|
type hardResetOptions struct {
|
||||||
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 {
|
|
||||||
WaitingStatus string
|
WaitingStatus string
|
||||||
EnvVars []string
|
EnvVars []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// only to be used in the undo flow for now
|
// only to be used in the undo flow for now (does an autostash)
|
||||||
func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHardResetWithAutoStashOptions) error {
|
func (self *UndoController) hardResetWithAutoStash(commitSha string, options hardResetOptions) error {
|
||||||
reset := func() error {
|
reset := func() error {
|
||||||
if err := gui.resetToRef(commitSha, "hard", options.EnvVars); err != nil {
|
if err := self.refHelper.ResetToRef(commitSha, "hard", options.EnvVars); err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return self.c.Error(err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we have any modified tracked files we need to ask the user if they want us to stash for them
|
// 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 {
|
if dirtyWorkingTree {
|
||||||
// offer to autostash changes
|
// offer to autostash changes
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return self.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.AutoStashTitle,
|
Title: self.c.Tr.AutoStashTitle,
|
||||||
Prompt: gui.Tr.AutoStashPrompt,
|
Prompt: self.c.Tr.AutoStashPrompt,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
return gui.PopupHandler.WithWaitingStatus(options.WaitingStatus, func() error {
|
return self.c.WithWaitingStatus(options.WaitingStatus, func() error {
|
||||||
if err := gui.Git.Stash.Save(gui.Tr.StashPrefix + commitSha); err != nil {
|
if err := self.git.Stash.Save(self.c.Tr.StashPrefix + commitSha); err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return self.c.Error(err)
|
||||||
}
|
}
|
||||||
if err := reset(); err != nil {
|
if err := reset(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := gui.Git.Stash.Pop(0)
|
err := self.git.Stash.Pop(0)
|
||||||
if err := gui.refreshSidePanels(types.RefreshOptions{}); err != nil {
|
if err := self.c.Refresh(types.RefreshOptions{}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return self.c.Error(err)
|
||||||
}
|
}
|
||||||
return nil
|
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()
|
return reset()
|
||||||
})
|
})
|
||||||
}
|
}
|
@ -17,21 +17,20 @@ func (gui *Gui) promptUserForCredential(passOrUname oscommands.CredentialType) s
|
|||||||
credentialsView := gui.Views.Credentials
|
credentialsView := gui.Views.Credentials
|
||||||
switch passOrUname {
|
switch passOrUname {
|
||||||
case oscommands.Username:
|
case oscommands.Username:
|
||||||
credentialsView.Title = gui.Tr.CredentialsUsername
|
credentialsView.Title = gui.c.Tr.CredentialsUsername
|
||||||
credentialsView.Mask = 0
|
credentialsView.Mask = 0
|
||||||
case oscommands.Password:
|
case oscommands.Password:
|
||||||
credentialsView.Title = gui.Tr.CredentialsPassword
|
credentialsView.Title = gui.c.Tr.CredentialsPassword
|
||||||
credentialsView.Mask = '*'
|
credentialsView.Mask = '*'
|
||||||
case oscommands.Passphrase:
|
case oscommands.Passphrase:
|
||||||
credentialsView.Title = gui.Tr.CredentialsPassphrase
|
credentialsView.Title = gui.c.Tr.CredentialsPassphrase
|
||||||
credentialsView.Mask = '*'
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.RenderCommitLength()
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -49,7 +48,7 @@ func (gui *Gui) handleSubmitCredential() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC})
|
return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCloseCredentialsView() error {
|
func (gui *Gui) handleCloseCredentialsView() error {
|
||||||
@ -59,10 +58,10 @@ func (gui *Gui) handleCloseCredentialsView() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleAskFocused() error {
|
func (gui *Gui) handleAskFocused() error {
|
||||||
keybindingConfig := gui.UserConfig.Keybinding
|
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||||
|
|
||||||
message := utils.ResolvePlaceholderString(
|
message := utils.ResolvePlaceholderString(
|
||||||
gui.Tr.CloseConfirm,
|
gui.c.Tr.CloseConfirm,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"keyBindClose": gui.getKeyDisplay(keybindingConfig.Universal.Return),
|
"keyBindClose": gui.getKeyDisplay(keybindingConfig.Universal.Return),
|
||||||
"keyBindConfirm": gui.getKeyDisplay(keybindingConfig.Universal.Confirm),
|
"keyBindConfirm": gui.getKeyDisplay(keybindingConfig.Universal.Confirm),
|
||||||
|
@ -54,7 +54,7 @@ func (gui *Gui) resolveTemplate(templateStr string, promptResponses []string) (s
|
|||||||
SelectedCommitFile: gui.getSelectedCommitFile(),
|
SelectedCommitFile: gui.getSelectedCommitFile(),
|
||||||
SelectedCommitFilePath: gui.getSelectedCommitFilePath(),
|
SelectedCommitFilePath: gui.getSelectedCommitFilePath(),
|
||||||
SelectedSubCommit: gui.getSelectedSubCommit(),
|
SelectedSubCommit: gui.getSelectedSubCommit(),
|
||||||
CheckedOutBranch: gui.currentBranch(),
|
CheckedOutBranch: gui.getCheckedOutBranch(),
|
||||||
PromptResponses: promptResponses,
|
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 {
|
func (gui *Gui) inputPrompt(prompt config.CustomCommandPrompt, promptResponses []string, responseIdx int, wrappedF func() error) error {
|
||||||
title, err := gui.resolveTemplate(prompt.Title, promptResponses)
|
title, err := gui.resolveTemplate(prompt.Title, promptResponses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
initialValue, err := gui.resolveTemplate(prompt.InitialValue, promptResponses)
|
initialValue, err := gui.resolveTemplate(prompt.InitialValue, promptResponses)
|
||||||
if err != nil {
|
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,
|
Title: title,
|
||||||
InitialContent: initialValue,
|
InitialContent: initialValue,
|
||||||
HandleConfirm: func(str string) error {
|
HandleConfirm: func(str string) error {
|
||||||
@ -95,17 +95,17 @@ func (gui *Gui) menuPrompt(prompt config.CustomCommandPrompt, promptResponses []
|
|||||||
}
|
}
|
||||||
name, err := gui.resolveTemplate(nameTemplate, promptResponses)
|
name, err := gui.resolveTemplate(nameTemplate, promptResponses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
description, err := gui.resolveTemplate(option.Description, promptResponses)
|
description, err := gui.resolveTemplate(option.Description, promptResponses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err := gui.resolveTemplate(option.Value, promptResponses)
|
value, err := gui.resolveTemplate(option.Value, promptResponses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
menuItems[i] = &popup.MenuItem{
|
menuItems[i] = &popup.MenuItem{
|
||||||
@ -119,30 +119,30 @@ func (gui *Gui) menuPrompt(prompt config.CustomCommandPrompt, promptResponses []
|
|||||||
|
|
||||||
title, err := gui.resolveTemplate(prompt.Title, promptResponses)
|
title, err := gui.resolveTemplate(prompt.Title, promptResponses)
|
||||||
if err != nil {
|
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) {
|
func (gui *Gui) GenerateMenuCandidates(commandOutput, filter, valueFormat, labelFormat string) ([]commandMenuEntry, error) {
|
||||||
reg, err := regexp.Compile(filter)
|
reg, err := regexp.Compile(filter)
|
||||||
if err != nil {
|
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)
|
buff := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
valueTemp, err := template.New("format").Parse(valueFormat)
|
valueTemp, err := template.New("format").Parse(valueFormat)
|
||||||
if err != nil {
|
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{})
|
colorFuncMap := style.TemplateFuncMapAddColors(template.FuncMap{})
|
||||||
|
|
||||||
descTemp, err := template.New("format").Funcs(colorFuncMap).Parse(labelFormat)
|
descTemp, err := template.New("format").Funcs(colorFuncMap).Parse(labelFormat)
|
||||||
if err != nil {
|
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{}
|
candidates := []commandMenuEntry{}
|
||||||
@ -167,7 +167,7 @@ func (gui *Gui) GenerateMenuCandidates(commandOutput, filter, valueFormat, label
|
|||||||
|
|
||||||
err = valueTemp.Execute(buff, tmplData)
|
err = valueTemp.Execute(buff, tmplData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return candidates, gui.PopupHandler.Error(err)
|
return candidates, gui.c.Error(err)
|
||||||
}
|
}
|
||||||
entry := commandMenuEntry{
|
entry := commandMenuEntry{
|
||||||
value: strings.TrimSpace(buff.String()),
|
value: strings.TrimSpace(buff.String()),
|
||||||
@ -177,7 +177,7 @@ func (gui *Gui) GenerateMenuCandidates(commandOutput, filter, valueFormat, label
|
|||||||
buff.Reset()
|
buff.Reset()
|
||||||
err = descTemp.Execute(buff, tmplData)
|
err = descTemp.Execute(buff, tmplData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return candidates, gui.PopupHandler.Error(err)
|
return candidates, gui.c.Error(err)
|
||||||
}
|
}
|
||||||
entry.label = strings.TrimSpace(buff.String())
|
entry.label = strings.TrimSpace(buff.String())
|
||||||
} else {
|
} else {
|
||||||
@ -195,25 +195,25 @@ func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptR
|
|||||||
// Collect cmd to run from config
|
// Collect cmd to run from config
|
||||||
cmdStr, err := gui.resolveTemplate(prompt.Command, promptResponses)
|
cmdStr, err := gui.resolveTemplate(prompt.Command, promptResponses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect Filter regexp
|
// Collect Filter regexp
|
||||||
filter, err := gui.resolveTemplate(prompt.Filter, promptResponses)
|
filter, err := gui.resolveTemplate(prompt.Filter, promptResponses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run and save output
|
// Run and save output
|
||||||
message, err := gui.Git.Custom.RunWithOutput(cmdStr)
|
message, err := gui.git.Custom.RunWithOutput(cmdStr)
|
||||||
if err != nil {
|
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
|
// Need to make a menu out of what the cmd has displayed
|
||||||
candidates, err := gui.GenerateMenuCandidates(message, filter, prompt.ValueFormat, prompt.LabelFormat)
|
candidates, err := gui.GenerateMenuCandidates(message, filter, prompt.ValueFormat, prompt.LabelFormat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
menuItems := make([]*popup.MenuItem, len(candidates))
|
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)
|
title, err := gui.resolveTemplate(prompt.Title, promptResponses)
|
||||||
if err != nil {
|
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 {
|
func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand) func() error {
|
||||||
@ -243,7 +243,7 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand
|
|||||||
f := func() error {
|
f := func() error {
|
||||||
cmdStr, err := gui.resolveTemplate(customCommand.Command, promptResponses)
|
cmdStr, err := gui.resolveTemplate(customCommand.Command, promptResponses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if customCommand.Subprocess {
|
if customCommand.Subprocess {
|
||||||
@ -252,19 +252,19 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand
|
|||||||
|
|
||||||
loadingText := customCommand.LoadingText
|
loadingText := customCommand.LoadingText
|
||||||
if loadingText == "" {
|
if loadingText == "" {
|
||||||
loadingText = gui.Tr.LcRunningCustomCommandStatus
|
loadingText = gui.c.Tr.LcRunningCustomCommandStatus
|
||||||
}
|
}
|
||||||
return gui.PopupHandler.WithWaitingStatus(loadingText, func() error {
|
return gui.c.WithWaitingStatus(loadingText, func() error {
|
||||||
gui.logAction(gui.Tr.Actions.CustomCommand)
|
gui.c.LogAction(gui.c.Tr.Actions.CustomCommand)
|
||||||
cmdObj := gui.OSCommand.Cmd.NewShell(cmdStr)
|
cmdObj := gui.OSCommand.Cmd.NewShell(cmdStr)
|
||||||
if customCommand.Stream {
|
if customCommand.Stream {
|
||||||
cmdObj.StreamOutput()
|
cmdObj.StreamOutput()
|
||||||
}
|
}
|
||||||
err := cmdObj.Run()
|
err := cmdObj.Run()
|
||||||
if err != nil {
|
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)
|
return gui.menuPromptFromCommand(prompt, promptResponses, idx, wrappedF)
|
||||||
}
|
}
|
||||||
default:
|
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 {
|
func (gui *Gui) GetCustomCommandKeybindings() []*types.Binding {
|
||||||
bindings := []*types.Binding{}
|
bindings := []*types.Binding{}
|
||||||
customCommands := gui.UserConfig.CustomCommands
|
customCommands := gui.c.UserConfig.CustomCommands
|
||||||
|
|
||||||
for _, customCommand := range customCommands {
|
for _, customCommand := range customCommands {
|
||||||
var viewName string
|
var viewName string
|
||||||
@ -315,11 +315,11 @@ func (gui *Gui) GetCustomCommandKeybindings() []*types.Binding {
|
|||||||
case "":
|
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)
|
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:
|
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.
|
// stupid golang making me build an array of strings for this.
|
||||||
allContextKeyStrings := make([]string, len(allContextKeys))
|
allContextKeyStrings := make([]string, len(AllContextKeys))
|
||||||
for i := range allContextKeys {
|
for i := range AllContextKeys {
|
||||||
allContextKeyStrings[i] = string(allContextKeys[i])
|
allContextKeyStrings[i] = string(AllContextKeys[i])
|
||||||
}
|
}
|
||||||
if !ok {
|
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, ", "))
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var CONTEXT_KEYS_SHOWING_DIFFS = []ContextKey{
|
var CONTEXT_KEYS_SHOWING_DIFFS = []types.ContextKey{
|
||||||
FILES_CONTEXT_KEY,
|
FILES_CONTEXT_KEY,
|
||||||
COMMIT_FILES_CONTEXT_KEY,
|
COMMIT_FILES_CONTEXT_KEY,
|
||||||
STASH_CONTEXT_KEY,
|
STASH_CONTEXT_KEY,
|
||||||
@ -28,10 +30,10 @@ func isShowingDiff(gui *Gui) bool {
|
|||||||
func (gui *Gui) IncreaseContextInDiffView() error {
|
func (gui *Gui) IncreaseContextInDiffView() error {
|
||||||
if isShowingDiff(gui) {
|
if isShowingDiff(gui) {
|
||||||
if err := gui.CheckCanChangeContext(); err != nil {
|
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()
|
return gui.handleDiffContextSizeChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,14 +41,14 @@ func (gui *Gui) IncreaseContextInDiffView() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) DecreaseContextInDiffView() 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 isShowingDiff(gui) && old_size > 1 {
|
||||||
if err := gui.CheckCanChangeContext(); err != nil {
|
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()
|
return gui.handleDiffContextSizeChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,8 +69,8 @@ func (gui *Gui) handleDiffContextSizeChange() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) CheckCanChangeContext() error {
|
func (gui *Gui) CheckCanChangeContext() error {
|
||||||
if gui.Git.Patch.PatchManager.Active() {
|
if gui.git.Patch.PatchManager.Active() {
|
||||||
return errors.New(gui.Tr.CantChangeContextSizeError)
|
return errors.New(gui.c.Tr.CantChangeContextSizeError)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -29,7 +29,7 @@ func setupGuiForTest(gui *Gui) {
|
|||||||
gui.Views.Main, _ = gui.prepareView("main")
|
gui.Views.Main, _ = gui.prepareView("main")
|
||||||
gui.Views.Secondary, _ = gui.prepareView("secondary")
|
gui.Views.Secondary, _ = gui.prepareView("secondary")
|
||||||
gui.Views.Options, _ = gui.prepareView("options")
|
gui.Views.Options, _ = gui.prepareView("options")
|
||||||
gui.Git.Patch.PatchManager = &patch.PatchManager{}
|
gui.git.Patch.PatchManager = &patch.PatchManager{}
|
||||||
_, _ = gui.refreshLineByLinePanel(diffForTest, "", false, 11)
|
_, _ = gui.refreshLineByLinePanel(diffForTest, "", false, 11)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,12 +48,12 @@ func TestIncreasesContextInDiffViewByOneInContextWithDiff(t *testing.T) {
|
|||||||
gui := NewDummyGui()
|
gui := NewDummyGui()
|
||||||
context := c(gui)
|
context := c(gui)
|
||||||
setupGuiForTest(gui)
|
setupGuiForTest(gui)
|
||||||
gui.UserConfig.Git.DiffContextSize = 1
|
gui.c.UserConfig.Git.DiffContextSize = 1
|
||||||
_ = gui.pushContext(context)
|
_ = gui.c.PushContext(context)
|
||||||
|
|
||||||
_ = gui.IncreaseContextInDiffView()
|
_ = 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()
|
gui := NewDummyGui()
|
||||||
context := c(gui)
|
context := c(gui)
|
||||||
setupGuiForTest(gui)
|
setupGuiForTest(gui)
|
||||||
gui.UserConfig.Git.DiffContextSize = 1
|
gui.c.UserConfig.Git.DiffContextSize = 1
|
||||||
_ = gui.pushContext(context)
|
_ = gui.c.PushContext(context)
|
||||||
|
|
||||||
_ = gui.IncreaseContextInDiffView()
|
_ = 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()
|
gui := NewDummyGui()
|
||||||
context := c(gui)
|
context := c(gui)
|
||||||
setupGuiForTest(gui)
|
setupGuiForTest(gui)
|
||||||
gui.UserConfig.Git.DiffContextSize = 2
|
gui.c.UserConfig.Git.DiffContextSize = 2
|
||||||
_ = gui.pushContext(context)
|
_ = gui.c.PushContext(context)
|
||||||
|
|
||||||
_ = gui.DecreaseContextInDiffView()
|
_ = 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()
|
gui := NewDummyGui()
|
||||||
context := c(gui)
|
context := c(gui)
|
||||||
setupGuiForTest(gui)
|
setupGuiForTest(gui)
|
||||||
gui.UserConfig.Git.DiffContextSize = 2
|
gui.c.UserConfig.Git.DiffContextSize = 2
|
||||||
_ = gui.pushContext(context)
|
_ = gui.c.PushContext(context)
|
||||||
|
|
||||||
_ = gui.DecreaseContextInDiffView()
|
_ = 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) {
|
func TestDoesntIncreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *testing.T) {
|
||||||
gui := NewDummyGui()
|
gui := NewDummyGui()
|
||||||
setupGuiForTest(gui)
|
setupGuiForTest(gui)
|
||||||
gui.UserConfig.Git.DiffContextSize = 2
|
gui.c.UserConfig.Git.DiffContextSize = 2
|
||||||
_ = gui.pushContext(gui.State.Contexts.CommitFiles)
|
_ = gui.c.PushContext(gui.State.Contexts.CommitFiles)
|
||||||
gui.Git.Patch.PatchManager.Start("from", "to", false, false)
|
gui.git.Patch.PatchManager.Start("from", "to", false, false)
|
||||||
|
|
||||||
errorCount := 0
|
errorCount := 0
|
||||||
gui.PopupHandler = &popup.TestPopupHandler{
|
gui.PopupHandler = &popup.TestPopupHandler{
|
||||||
OnErrorMsg: func(message string) error {
|
OnErrorMsg: func(message string) error {
|
||||||
assert.Equal(t, gui.Tr.CantChangeContextSizeError, message)
|
assert.Equal(t, gui.c.Tr.CantChangeContextSizeError, message)
|
||||||
errorCount += 1
|
errorCount += 1
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -156,20 +156,20 @@ func TestDoesntIncreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *test
|
|||||||
_ = gui.IncreaseContextInDiffView()
|
_ = gui.IncreaseContextInDiffView()
|
||||||
|
|
||||||
assert.Equal(t, 1, errorCount)
|
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) {
|
func TestDoesntDecreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *testing.T) {
|
||||||
gui := NewDummyGui()
|
gui := NewDummyGui()
|
||||||
setupGuiForTest(gui)
|
setupGuiForTest(gui)
|
||||||
gui.UserConfig.Git.DiffContextSize = 2
|
gui.c.UserConfig.Git.DiffContextSize = 2
|
||||||
_ = gui.pushContext(gui.State.Contexts.CommitFiles)
|
_ = gui.c.PushContext(gui.State.Contexts.CommitFiles)
|
||||||
gui.Git.Patch.PatchManager.Start("from", "to", false, false)
|
gui.git.Patch.PatchManager.Start("from", "to", false, false)
|
||||||
|
|
||||||
errorCount := 0
|
errorCount := 0
|
||||||
gui.PopupHandler = &popup.TestPopupHandler{
|
gui.PopupHandler = &popup.TestPopupHandler{
|
||||||
OnErrorMsg: func(message string) error {
|
OnErrorMsg: func(message string) error {
|
||||||
assert.Equal(t, gui.Tr.CantChangeContextSizeError, message)
|
assert.Equal(t, gui.c.Tr.CantChangeContextSizeError, message)
|
||||||
errorCount += 1
|
errorCount += 1
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -177,15 +177,15 @@ func TestDoesntDecreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *test
|
|||||||
|
|
||||||
_ = gui.DecreaseContextInDiffView()
|
_ = gui.DecreaseContextInDiffView()
|
||||||
|
|
||||||
assert.Equal(t, 2, gui.UserConfig.Git.DiffContextSize)
|
assert.Equal(t, 2, gui.c.UserConfig.Git.DiffContextSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecreasesContextInDiffViewNoFurtherThanOne(t *testing.T) {
|
func TestDecreasesContextInDiffViewNoFurtherThanOne(t *testing.T) {
|
||||||
gui := NewDummyGui()
|
gui := NewDummyGui()
|
||||||
setupGuiForTest(gui)
|
setupGuiForTest(gui)
|
||||||
gui.UserConfig.Git.DiffContextSize = 1
|
gui.c.UserConfig.Git.DiffContextSize = 1
|
||||||
|
|
||||||
_ = gui.DecreaseContextInDiffView()
|
_ = 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 {
|
func (gui *Gui) exitDiffMode() error {
|
||||||
gui.State.Modes.Diffing = diffing.New()
|
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 {
|
func (gui *Gui) renderDiff() error {
|
||||||
@ -112,11 +112,11 @@ func (gui *Gui) handleCreateDiffingMenuPanel() error {
|
|||||||
name := name
|
name := name
|
||||||
menuItems = append(menuItems, []*popup.MenuItem{
|
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 {
|
OnPress: func() error {
|
||||||
gui.State.Modes.Diffing.Ref = name
|
gui.State.Modes.Diffing.Ref = name
|
||||||
// can scope this down based on current view but too lazy right now
|
// 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{
|
menuItems = append(menuItems, []*popup.MenuItem{
|
||||||
{
|
{
|
||||||
DisplayString: gui.Tr.LcEnterRefToDiff,
|
DisplayString: gui.c.Tr.LcEnterRefToDiff,
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
return gui.PopupHandler.Prompt(popup.PromptOpts{
|
return gui.c.Prompt(popup.PromptOpts{
|
||||||
Title: gui.Tr.LcEnteRefName,
|
Title: gui.c.Tr.LcEnteRefName,
|
||||||
FindSuggestionsFunc: gui.getRefsSuggestionsFunc(),
|
FindSuggestionsFunc: gui.suggestionsHelper.GetRefsSuggestionsFunc(),
|
||||||
HandleConfirm: func(response string) error {
|
HandleConfirm: func(response string) error {
|
||||||
gui.State.Modes.Diffing.Ref = strings.TrimSpace(response)
|
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() {
|
if gui.State.Modes.Diffing.Active() {
|
||||||
menuItems = append(menuItems, []*popup.MenuItem{
|
menuItems = append(menuItems, []*popup.MenuItem{
|
||||||
{
|
{
|
||||||
DisplayString: gui.Tr.LcSwapDiff,
|
DisplayString: gui.c.Tr.LcSwapDiff,
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
gui.State.Modes.Diffing.Reverse = !gui.State.Modes.Diffing.Reverse
|
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 {
|
OnPress: func() error {
|
||||||
gui.State.Modes.Diffing = diffing.New()
|
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 {
|
if node.File == nil {
|
||||||
menuItems = []*popup.MenuItem{
|
menuItems = []*popup.MenuItem{
|
||||||
{
|
{
|
||||||
DisplayString: gui.Tr.LcDiscardAllChanges,
|
DisplayString: gui.c.Tr.LcDiscardAllChanges,
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
gui.logAction(gui.Tr.Actions.DiscardAllChangesInDirectory)
|
gui.c.LogAction(gui.c.Tr.Actions.DiscardAllChangesInDirectory)
|
||||||
if err := gui.Git.WorkingTree.DiscardAllDirChanges(node); err != nil {
|
if err := gui.git.WorkingTree.DiscardAllDirChanges(node); err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
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() {
|
if node.GetHasStagedChanges() && node.GetHasUnstagedChanges() {
|
||||||
menuItems = append(menuItems, &popup.MenuItem{
|
menuItems = append(menuItems, &popup.MenuItem{
|
||||||
DisplayString: gui.Tr.LcDiscardUnstagedChanges,
|
DisplayString: gui.c.Tr.LcDiscardUnstagedChanges,
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
gui.logAction(gui.Tr.Actions.DiscardUnstagedChangesInDirectory)
|
gui.c.LogAction(gui.c.Tr.Actions.DiscardUnstagedChangesInDirectory)
|
||||||
if err := gui.Git.WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
|
if err := gui.git.WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
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{
|
menuItems = []*popup.MenuItem{
|
||||||
{
|
{
|
||||||
DisplayString: gui.Tr.LcSubmoduleStashAndReset,
|
DisplayString: gui.c.Tr.LcSubmoduleStashAndReset,
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
return gui.resetSubmodule(submodule)
|
return gui.Controllers.Files.ResetSubmodule(submodule)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
menuItems = []*popup.MenuItem{
|
menuItems = []*popup.MenuItem{
|
||||||
{
|
{
|
||||||
DisplayString: gui.Tr.LcDiscardAllChanges,
|
DisplayString: gui.c.Tr.LcDiscardAllChanges,
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
gui.logAction(gui.Tr.Actions.DiscardAllChangesInFile)
|
gui.c.LogAction(gui.c.Tr.Actions.DiscardAllChangesInFile)
|
||||||
if err := gui.Git.WorkingTree.DiscardAllFileChanges(file); err != nil {
|
if err := gui.git.WorkingTree.DiscardAllFileChanges(file); err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
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 {
|
if file.HasStagedChanges && file.HasUnstagedChanges {
|
||||||
menuItems = append(menuItems, &popup.MenuItem{
|
menuItems = append(menuItems, &popup.MenuItem{
|
||||||
DisplayString: gui.Tr.LcDiscardUnstagedChanges,
|
DisplayString: gui.c.Tr.LcDiscardUnstagedChanges,
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
gui.logAction(gui.Tr.Actions.DiscardAllUnstagedChangesInFile)
|
gui.c.LogAction(gui.c.Tr.Actions.DiscardAllUnstagedChangesInFile)
|
||||||
if err := gui.Git.WorkingTree.DiscardUnstagedFileChanges(file); err != nil {
|
if err := gui.git.WorkingTree.DiscardUnstagedFileChanges(file); err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
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 {
|
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 {
|
if !ok {
|
||||||
newlineKey = gocui.KeyAltEnter
|
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.
|
// considered out of bounds to add a newline, meaning we can avoid unnecessary scrolling.
|
||||||
err := gui.resizePopupPanel(v, v.TextArea.GetContent())
|
err := gui.resizePopupPanel(v, v.TextArea.GetContent())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gui.Log.Error(err)
|
gui.c.Log.Error(err)
|
||||||
}
|
}
|
||||||
v.RenderTextArea()
|
v.RenderTextArea()
|
||||||
gui.RenderCommitLength()
|
gui.RenderCommitLength()
|
||||||
|
@ -8,11 +8,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (gui *Gui) handleCreateExtrasMenuPanel() error {
|
func (gui *Gui) handleCreateExtrasMenuPanel() error {
|
||||||
return gui.PopupHandler.Menu(popup.CreateMenuOptions{
|
return gui.c.Menu(popup.CreateMenuOptions{
|
||||||
Title: gui.Tr.CommandLog,
|
Title: gui.c.Tr.CommandLog,
|
||||||
Items: []*popup.MenuItem{
|
Items: []*popup.MenuItem{
|
||||||
{
|
{
|
||||||
DisplayString: gui.Tr.ToggleShowCommandLog,
|
DisplayString: gui.c.Tr.ToggleShowCommandLog,
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
currentContext := gui.currentStaticContext()
|
currentContext := gui.currentStaticContext()
|
||||||
if gui.ShowExtrasWindow && currentContext.GetKey() == COMMAND_LOG_CONTEXT_KEY {
|
if gui.ShowExtrasWindow && currentContext.GetKey() == COMMAND_LOG_CONTEXT_KEY {
|
||||||
@ -22,13 +22,13 @@ func (gui *Gui) handleCreateExtrasMenuPanel() error {
|
|||||||
}
|
}
|
||||||
show := !gui.ShowExtrasWindow
|
show := !gui.ShowExtrasWindow
|
||||||
gui.ShowExtrasWindow = show
|
gui.ShowExtrasWindow = show
|
||||||
gui.Config.GetAppState().HideCommandLog = !show
|
gui.c.GetAppState().HideCommandLog = !show
|
||||||
_ = gui.Config.SaveAppState()
|
_ = gui.c.SaveAppState()
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayString: gui.Tr.FocusCommandLog,
|
DisplayString: gui.c.Tr.FocusCommandLog,
|
||||||
OnPress: gui.handleFocusCommandLog,
|
OnPress: gui.handleFocusCommandLog,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -37,8 +37,9 @@ func (gui *Gui) handleCreateExtrasMenuPanel() error {
|
|||||||
|
|
||||||
func (gui *Gui) handleFocusCommandLog() error {
|
func (gui *Gui) handleFocusCommandLog() error {
|
||||||
gui.ShowExtrasWindow = true
|
gui.ShowExtrasWindow = true
|
||||||
|
// TODO: is this necessary? Can't I just call 'return from context'?
|
||||||
gui.State.Contexts.CommandLog.SetParentContext(gui.currentSideContext())
|
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 {
|
func (gui *Gui) scrollUpExtra() error {
|
||||||
@ -58,7 +59,7 @@ func (gui *Gui) scrollDownExtra() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getCmdWriter() io.Writer {
|
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.
|
// 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
|
// only refresh if we're not already
|
||||||
if !gui.State.IsRefreshingFiles {
|
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
|
// watch for errors
|
||||||
case err := <-gui.fileWatcher.Watcher.Errors:
|
case err := <-gui.fileWatcher.Watcher.Errors:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gui.Log.Error(err)
|
gui.c.Log.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,10 @@
|
|||||||
package gui
|
package gui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/jesseduffield/gocui"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
"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/filetree"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/popup"
|
"github.com/jesseduffield/lazygit/pkg/gui/popup"
|
||||||
@ -52,7 +47,7 @@ func (gui *Gui) filesRenderToMain() error {
|
|||||||
return gui.refreshMainViews(refreshMainOpts{
|
return gui.refreshMainViews(refreshMainOpts{
|
||||||
main: &viewUpdateOpts{
|
main: &viewUpdateOpts{
|
||||||
title: "",
|
title: "",
|
||||||
task: NewRenderStringTask(gui.Tr.NoChangedFiles),
|
task: NewRenderStringTask(gui.c.Tr.NoChangedFiles),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -69,24 +64,24 @@ func (gui *Gui) filesRenderToMain() error {
|
|||||||
|
|
||||||
gui.resetMergeStateWithLock()
|
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{
|
refreshOpts := refreshMainOpts{main: &viewUpdateOpts{
|
||||||
title: gui.Tr.UnstagedChanges,
|
title: gui.c.Tr.UnstagedChanges,
|
||||||
task: NewRunPtyTask(cmdObj.GetCmd()),
|
task: NewRunPtyTask(cmdObj.GetCmd()),
|
||||||
}}
|
}}
|
||||||
|
|
||||||
if node.GetHasUnstagedChanges() {
|
if node.GetHasUnstagedChanges() {
|
||||||
if node.GetHasStagedChanges() {
|
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{
|
refreshOpts.secondary = &viewUpdateOpts{
|
||||||
title: gui.Tr.StagedChanges,
|
title: gui.c.Tr.StagedChanges,
|
||||||
task: NewRunPtyTask(cmdObj.GetCmd()),
|
task: NewRunPtyTask(cmdObj.GetCmd()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
refreshOpts.main.title = gui.Tr.StagedChanges
|
refreshOpts.main.title = gui.c.Tr.StagedChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.refreshMainViews(refreshOpts)
|
return gui.refreshMainViews(refreshOpts)
|
||||||
@ -115,12 +110,12 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gui.OnUIThread(func() error {
|
gui.OnUIThread(func() error {
|
||||||
if err := gui.postRefreshUpdate(gui.State.Contexts.Submodules); err != nil {
|
if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Submodules); err != nil {
|
||||||
gui.Log.Error(err)
|
gui.c.Log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ContextKey(gui.Views.Files.Context) == FILES_CONTEXT_KEY {
|
if types.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
|
// 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 {
|
if err := gui.State.Contexts.Files.HandleRender(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -143,418 +138,6 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
|
|||||||
return nil
|
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 {
|
func (gui *Gui) refreshStateFiles() error {
|
||||||
state := gui.State
|
state := gui.State
|
||||||
|
|
||||||
@ -591,13 +174,13 @@ func (gui *Gui) refreshStateFiles() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(pathsToStage) > 0 {
|
if len(pathsToStage) > 0 {
|
||||||
gui.logAction(gui.Tr.Actions.StageResolvedFiles)
|
gui.c.LogAction(gui.Tr.Actions.StageResolvedFiles)
|
||||||
if err := gui.Git.WorkingTree.StageFiles(pathsToStage); err != nil {
|
if err := gui.git.WorkingTree.StageFiles(pathsToStage); err != nil {
|
||||||
return gui.surfaceError(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
files := gui.Git.Loaders.Files.
|
files := gui.git.Loaders.Files.
|
||||||
GetStatusFiles(loaders.GetStatusFileOptions{})
|
GetStatusFiles(loaders.GetStatusFileOptions{})
|
||||||
|
|
||||||
conflictFileCount := 0
|
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() })
|
gui.OnUIThread(func() error { return gui.promptToContinueRebase() })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -716,218 +299,7 @@ func (gui *Gui) findNewSelectedIdx(prevNodes []*filetree.FileNode, currNodes []*
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handlePullFiles() error {
|
func (gui *Gui) onFocusFile() 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
|
|
||||||
}
|
|
||||||
|
|
||||||
gui.takeOverMergeConflictScrolling()
|
gui.takeOverMergeConflictScrolling()
|
||||||
|
|
||||||
if gui.State.Panels.Merging.GetPath() != file.Name {
|
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)
|
return gui.pushContext(gui.State.Contexts.Merging)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) openFile(filename string) error {
|
func (gui *Gui) getSetTextareaTextFn(view *gocui.View) func(string) {
|
||||||
gui.logAction(gui.Tr.Actions.OpenFile)
|
return func(text string) {
|
||||||
if err := gui.OSCommand.OpenFile(filename); err != nil {
|
view.ClearTextArea()
|
||||||
return gui.PopupHandler.Error(err)
|
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 (
|
import (
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommitFileNode struct {
|
type CommitFileNode struct {
|
||||||
@ -12,8 +13,7 @@ type CommitFileNode struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _ INode = &CommitFileNode{}
|
var _ INode = &CommitFileNode{}
|
||||||
|
var _ types.ListItem = &CommitFileNode{}
|
||||||
// methods satisfying ListItem interface
|
|
||||||
|
|
||||||
func (s *CommitFileNode) ID() string {
|
func (s *CommitFileNode) ID() string {
|
||||||
return s.GetPath()
|
return s.GetPath()
|
||||||
|
@ -2,6 +2,7 @@ package filetree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileNode struct {
|
type FileNode struct {
|
||||||
@ -12,8 +13,7 @@ type FileNode struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _ INode = &FileNode{}
|
var _ INode = &FileNode{}
|
||||||
|
var _ types.ListItem = &FileNode{}
|
||||||
// methods satisfying ListItem interface
|
|
||||||
|
|
||||||
func (s *FileNode) ID() string {
|
func (s *FileNode) ID() string {
|
||||||
return s.GetPath()
|
return s.GetPath()
|
||||||
|
@ -5,17 +5,27 @@ import (
|
|||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gui *Gui) validateNotInFilterMode() (bool, error) {
|
func (gui *Gui) validateNotInFilterMode() bool {
|
||||||
if gui.State.Modes.Filtering.Active() {
|
if gui.State.Modes.Filtering.Active() {
|
||||||
err := gui.PopupHandler.Ask(popup.AskOpts{
|
_ = gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.MustExitFilterModeTitle,
|
Title: gui.c.Tr.MustExitFilterModeTitle,
|
||||||
Prompt: gui.Tr.MustExitFilterModePrompt,
|
Prompt: gui.c.Tr.MustExitFilterModePrompt,
|
||||||
HandleConfirm: gui.exitFilterMode,
|
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 {
|
func (gui *Gui) exitFilterMode() error {
|
||||||
@ -28,7 +38,7 @@ func (gui *Gui) clearFiltering() error {
|
|||||||
gui.State.ScreenMode = SCREEN_NORMAL
|
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 {
|
func (gui *Gui) setFiltering(path string) error {
|
||||||
@ -37,11 +47,11 @@ func (gui *Gui) setFiltering(path string) error {
|
|||||||
gui.State.ScreenMode = SCREEN_HALF
|
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 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)
|
gui.State.Contexts.BranchCommits.GetPanelState().SetSelectedLineIdx(0)
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ func (gui *Gui) handleCreateFilteringMenuPanel() error {
|
|||||||
|
|
||||||
if fileName != "" {
|
if fileName != "" {
|
||||||
menuItems = append(menuItems, &popup.MenuItem{
|
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 {
|
OnPress: func() error {
|
||||||
return gui.setFiltering(fileName)
|
return gui.setFiltering(fileName)
|
||||||
},
|
},
|
||||||
@ -34,11 +34,11 @@ func (gui *Gui) handleCreateFilteringMenuPanel() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
menuItems = append(menuItems, &popup.MenuItem{
|
menuItems = append(menuItems, &popup.MenuItem{
|
||||||
DisplayString: gui.Tr.LcFilterPathOption,
|
DisplayString: gui.c.Tr.LcFilterPathOption,
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
return gui.PopupHandler.Prompt(popup.PromptOpts{
|
return gui.c.Prompt(popup.PromptOpts{
|
||||||
FindSuggestionsFunc: gui.getFilePathSuggestionsFunc(),
|
FindSuggestionsFunc: gui.suggestionsHelper.GetFilePathSuggestionsFunc(),
|
||||||
Title: gui.Tr.EnterFileName,
|
Title: gui.c.Tr.EnterFileName,
|
||||||
HandleConfirm: func(response string) error {
|
HandleConfirm: func(response string) error {
|
||||||
return gui.setFiltering(strings.TrimSpace(response))
|
return gui.setFiltering(strings.TrimSpace(response))
|
||||||
},
|
},
|
||||||
@ -48,10 +48,10 @@ func (gui *Gui) handleCreateFilteringMenuPanel() error {
|
|||||||
|
|
||||||
if gui.State.Modes.Filtering.Active() {
|
if gui.State.Modes.Filtering.Active() {
|
||||||
menuItems = append(menuItems, &popup.MenuItem{
|
menuItems = append(menuItems, &popup.MenuItem{
|
||||||
DisplayString: gui.Tr.LcExitFilterMode,
|
DisplayString: gui.c.Tr.LcExitFilterMode,
|
||||||
OnPress: gui.clearFiltering,
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !gui.Git.Flow.GitFlowEnabled() {
|
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")
|
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 {
|
startHandler := func(branchType string) func() error {
|
||||||
return 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,
|
Title: title,
|
||||||
HandleConfirm: func(name string) error {
|
HandleConfirm: func(name string) error {
|
||||||
gui.logAction(gui.Tr.Actions.GitFlowStart)
|
gui.c.LogAction(gui.c.Tr.Actions.GitFlowStart)
|
||||||
return gui.runSubprocessWithSuspenseAndRefresh(
|
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",
|
Title: "git flow",
|
||||||
Items: []*popup.MenuItem{
|
Items: []*popup.MenuItem{
|
||||||
{
|
{
|
||||||
@ -64,11 +64,11 @@ func (gui *Gui) handleCreateGitFlowMenu() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) gitFlowFinishBranch(branchName string) 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 {
|
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)
|
return gui.runSubprocessWithSuspenseAndRefresh(cmdObj)
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ func (gui *Gui) prevScreenMode() error {
|
|||||||
|
|
||||||
func (gui *Gui) scrollUpView(view *gocui.View) error {
|
func (gui *Gui) scrollUpView(view *gocui.View) error {
|
||||||
ox, oy := view.Origin()
|
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)
|
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 {
|
func (gui *Gui) linesToScrollDown(view *gocui.View) int {
|
||||||
_, oy := view.Origin()
|
_, oy := view.Origin()
|
||||||
y := oy
|
y := oy
|
||||||
canScrollPastBottom := gui.UserConfig.Gui.ScrollPastBottom
|
canScrollPastBottom := gui.c.UserConfig.Gui.ScrollPastBottom
|
||||||
if !canScrollPastBottom {
|
if !canScrollPastBottom {
|
||||||
_, sy := view.Size()
|
_, sy := view.Size()
|
||||||
y += sy
|
y += sy
|
||||||
}
|
}
|
||||||
scrollHeight := gui.UserConfig.Gui.ScrollHeight
|
scrollHeight := gui.c.UserConfig.Gui.ScrollHeight
|
||||||
scrollableLines := view.ViewLinesHeight() - y
|
scrollableLines := view.ViewLinesHeight() - y
|
||||||
if scrollableLines < 0 {
|
if scrollableLines < 0 {
|
||||||
return 0
|
return 0
|
||||||
@ -177,7 +177,7 @@ func (gui *Gui) scrollDownConfirmationPanel() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleRefresh() 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 {
|
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
|
// set filename, set primary/secondary selected, set line number, then switch context
|
||||||
// I'll need to know it was changed though.
|
// I'll need to know it was changed though.
|
||||||
// Could I pass something along to the context change?
|
// 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:
|
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
|
return nil
|
||||||
@ -205,35 +205,29 @@ func (gui *Gui) handleMouseDownSecondary() error {
|
|||||||
|
|
||||||
switch gui.g.CurrentView() {
|
switch gui.g.CurrentView() {
|
||||||
case gui.Views.Files:
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) fetch() (err error) {
|
func (gui *Gui) fetch() (err error) {
|
||||||
gui.Mutexes.FetchMutex.Lock()
|
gui.c.LogAction("Fetch")
|
||||||
defer gui.Mutexes.FetchMutex.Unlock()
|
err = gui.git.Sync.Fetch(git_commands.FetchOptions{})
|
||||||
|
|
||||||
gui.logAction("Fetch")
|
|
||||||
err = gui.Git.Sync.Fetch(git_commands.FetchOptions{})
|
|
||||||
|
|
||||||
if err != nil && strings.Contains(err.Error(), "exit status 128") {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) backgroundFetch() (err error) {
|
func (gui *Gui) backgroundFetch() (err error) {
|
||||||
gui.Mutexes.FetchMutex.Lock()
|
err = gui.git.Sync.Fetch(git_commands.FetchOptions{Background: true})
|
||||||
defer gui.Mutexes.FetchMutex.Unlock()
|
|
||||||
|
|
||||||
err = gui.Git.Sync.Fetch(git_commands.FetchOptions{Background: true})
|
_ = gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
|
||||||
|
|
||||||
_ = gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -246,14 +240,14 @@ func (gui *Gui) handleCopySelectedSideContextItemToClipboard() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.logAction(gui.Tr.Actions.CopyToClipboard)
|
gui.c.LogAction(gui.c.Tr.Actions.CopyToClipboard)
|
||||||
if err := gui.OSCommand.CopyToClipboard(itemId); err != nil {
|
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)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,9 @@ import (
|
|||||||
// we don't need to see a loading status if we're in a subprocess.
|
// 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
|
// 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 {
|
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 {
|
if useSubprocess {
|
||||||
success, err := gui.runSubprocessWithSuspense(gui.OSCommand.Cmd.NewShell(cmdObj.ToString()))
|
success, err := gui.runSubprocessWithSuspense(gui.OSCommand.Cmd.NewShell(cmdObj.ToString()))
|
||||||
if success && onSuccess != nil {
|
if success && onSuccess != nil {
|
||||||
@ -24,7 +24,7 @@ func (gui *Gui) withGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string,
|
|||||||
return err
|
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
|
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 {
|
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 := gui.OSCommand.Cmd.NewShell(cmdObj.ToString())
|
||||||
cmdObj.AddEnvVars("TERM=dumb")
|
cmdObj.AddEnvVars("TERM=dumb")
|
||||||
cmdWriter := gui.getCmdWriter()
|
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.Run(); err != nil {
|
||||||
if _, err := cmd.Stdout.Write([]byte(fmt.Sprintf("%s\n", style.FgRed.Sprint(err.Error())))); 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})
|
_ = gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||||
return gui.PopupHandler.Error(
|
return gui.c.Error(
|
||||||
fmt.Errorf(
|
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/commands/oscommands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/common"
|
"github.com/jesseduffield/lazygit/pkg/common"
|
||||||
"github.com/jesseduffield/lazygit/pkg/config"
|
"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/controllers"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/lbl"
|
"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/cherrypicking"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
|
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/filtering"
|
"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"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/authors"
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation/authors"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/graph"
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation/graph"
|
||||||
@ -55,13 +57,13 @@ const StartupPopupVersion = 5
|
|||||||
var OverlappingEdges = false
|
var OverlappingEdges = false
|
||||||
|
|
||||||
type ContextManager struct {
|
type ContextManager struct {
|
||||||
ContextStack []Context
|
ContextStack []types.Context
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContextManager(initialContext Context) ContextManager {
|
func NewContextManager(initialContext types.Context) ContextManager {
|
||||||
return ContextManager{
|
return ContextManager{
|
||||||
ContextStack: []Context{initialContext},
|
ContextStack: []types.Context{initialContext},
|
||||||
RWMutex: sync.RWMutex{},
|
RWMutex: sync.RWMutex{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,7 +74,7 @@ type Repo string
|
|||||||
type Gui struct {
|
type Gui struct {
|
||||||
*common.Common
|
*common.Common
|
||||||
g *gocui.Gui
|
g *gocui.Gui
|
||||||
Git *commands.GitCommand
|
git *commands.GitCommand
|
||||||
OSCommand *oscommands.OSCommand
|
OSCommand *oscommands.OSCommand
|
||||||
|
|
||||||
// this is the state of the GUI for the current repo
|
// this is the state of the GUI for the current repo
|
||||||
@ -126,6 +128,7 @@ type Gui struct {
|
|||||||
|
|
||||||
IsNewRepo bool
|
IsNewRepo bool
|
||||||
|
|
||||||
|
// controllers define keybindings for a given context
|
||||||
Controllers Controllers
|
Controllers Controllers
|
||||||
|
|
||||||
// flag as to whether or not the diff view should ignore whitespace
|
// 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`
|
// if this is true, we'll load our commits using `git log --all`
|
||||||
ShowWholeGitGraph bool
|
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
|
RetainOriginalDir bool
|
||||||
|
|
||||||
PrevLayout PrevLayout
|
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
|
// 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
|
// in case we want to restore it before quitting for users who have set up
|
||||||
// the feature for changing directory upon quit.
|
// the feature for changing directory upon quit.
|
||||||
@ -182,7 +194,7 @@ type GuiRepoState struct {
|
|||||||
Updating bool
|
Updating bool
|
||||||
Panels *panelStates
|
Panels *panelStates
|
||||||
SplitMainPanel bool
|
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
|
IsRefreshingFiles bool
|
||||||
Searching searchingState
|
Searching searchingState
|
||||||
@ -192,9 +204,9 @@ type GuiRepoState struct {
|
|||||||
Modes Modes
|
Modes Modes
|
||||||
|
|
||||||
ContextManager ContextManager
|
ContextManager ContextManager
|
||||||
Contexts ContextTree
|
Contexts context.ContextTree
|
||||||
ViewContextMap map[string]Context
|
ViewContextMap map[string]types.Context
|
||||||
ViewTabContextMap map[string][]tabContext
|
ViewTabContextMap map[string][]context.TabContext
|
||||||
|
|
||||||
// WindowViewNameMap is a mapping of windows to the current view of that window.
|
// 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
|
// 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
|
// this is the message of the last failed commit attempt
|
||||||
failedCommitMessage string
|
failedCommitMessage string
|
||||||
|
|
||||||
// TODO: move these into the gui struct
|
|
||||||
ScreenMode WindowMaximisation
|
ScreenMode WindowMaximisation
|
||||||
}
|
}
|
||||||
|
|
||||||
type Controllers struct {
|
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 {
|
type listPanelState struct {
|
||||||
@ -373,13 +392,15 @@ type Modes struct {
|
|||||||
Diffing diffing.Diffing
|
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 {
|
type guiMutexes struct {
|
||||||
RefreshingFilesMutex sync.Mutex
|
RefreshingFilesMutex *sync.Mutex
|
||||||
RefreshingStatusMutex sync.Mutex
|
RefreshingStatusMutex *sync.Mutex
|
||||||
FetchMutex sync.Mutex
|
FetchMutex *sync.Mutex
|
||||||
BranchCommitsMutex sync.Mutex
|
BranchCommitsMutex *sync.Mutex
|
||||||
LineByLinePanelMutex sync.Mutex
|
LineByLinePanelMutex *sync.Mutex
|
||||||
SubprocessMutex sync.Mutex
|
SubprocessMutex *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// reuseState determines if we pull the repo state from our repo state map or
|
// 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
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} 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),
|
FilteredReflogCommits: make([]*models.Commit, 0),
|
||||||
ReflogCommits: make([]*models.Commit, 0),
|
ReflogCommits: make([]*models.Commit, 0),
|
||||||
StashEntries: make([]*models.StashEntry, 0),
|
StashEntries: make([]*models.StashEntry, 0),
|
||||||
|
BisectInfo: git_commands.NewNullBisectInfo(),
|
||||||
Panels: &panelStates{
|
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
|
// 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}},
|
Files: &filePanelState{listPanelState{SelectedLineIdx: -1}},
|
||||||
@ -450,8 +472,8 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
|
|||||||
CherryPicking: cherrypicking.New(),
|
CherryPicking: cherrypicking.New(),
|
||||||
Diffing: diffing.New(),
|
Diffing: diffing.New(),
|
||||||
},
|
},
|
||||||
ViewContextMap: contexts.initialViewContextMap(),
|
ViewContextMap: contexts.InitialViewContextMap(),
|
||||||
ViewTabContextMap: contexts.initialViewTabContextMap(),
|
ViewTabContextMap: contexts.InitialViewTabContextMap(),
|
||||||
ScreenMode: screenMode,
|
ScreenMode: screenMode,
|
||||||
// TODO: put contexts in the context manager
|
// TODO: put contexts in the context manager
|
||||||
ContextManager: NewContextManager(initialContext),
|
ContextManager: NewContextManager(initialContext),
|
||||||
@ -462,21 +484,6 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
|
|||||||
gui.RepoStateMap[Repo(currentDir)] = gui.State
|
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
|
// for now the split view will always be on
|
||||||
// NewGui builds a new gui handler
|
// NewGui builds a new gui handler
|
||||||
func NewGui(
|
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
|
// 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
|
// sake of backwards compatibility. We're making use of short circuiting here
|
||||||
ShowExtrasWindow: cmn.UserConfig.Gui.ShowCommandLog && !config.GetAppState().HideCommandLog,
|
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,
|
InitialDir: initialDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
guiIO := oscommands.NewGuiIO(
|
guiIO := oscommands.NewGuiIO(
|
||||||
cmn.Log,
|
cmn.Log,
|
||||||
gui.logCommand,
|
gui.LogCommand,
|
||||||
gui.getCmdWriter,
|
gui.getCmdWriter,
|
||||||
gui.promptUserForCredential,
|
gui.promptUserForCredential,
|
||||||
)
|
)
|
||||||
@ -519,42 +533,151 @@ func NewGui(
|
|||||||
|
|
||||||
gui.OSCommand = osCommand
|
gui.OSCommand = osCommand
|
||||||
var err error
|
var err error
|
||||||
gui.Git, err = commands.NewGitCommand(
|
gui.git, err = commands.NewGitCommand(
|
||||||
cmn,
|
cmn,
|
||||||
osCommand,
|
osCommand,
|
||||||
gitConfig,
|
gitConfig,
|
||||||
|
gui.Mutexes.FetchMutex,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.resetState(filterPath, false)
|
|
||||||
|
|
||||||
gui.watchFilesForChanges()
|
gui.watchFilesForChanges()
|
||||||
|
|
||||||
gui.PopupHandler = popup.NewPopupHandler(
|
gui.PopupHandler = popup.NewPopupHandler(
|
||||||
cmn,
|
cmn,
|
||||||
gui.createPopupPanel,
|
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) },
|
func() error { return gui.closeConfirmationPrompt(false) },
|
||||||
gui.createMenu,
|
gui.createMenu,
|
||||||
gui.withWaitingStatus,
|
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}
|
guiCommon := &guiCommon{gui: gui, IPopupHandler: gui.PopupHandler}
|
||||||
controllerCommon := &controllers.ControllerCommon{IGuiCommon: guiCommon, Common: cmn}
|
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{
|
gui.Controllers = Controllers{
|
||||||
Submodules: controllers.NewSubmodulesController(
|
Submodules: controllers.NewSubmodulesController(
|
||||||
controllerCommon,
|
controllerCommon,
|
||||||
|
gui.State.Contexts.Submodules,
|
||||||
|
gui.git,
|
||||||
gui.enterSubmodule,
|
gui.enterSubmodule,
|
||||||
gui.Git,
|
|
||||||
gui.State.Submodules,
|
|
||||||
gui.getSelectedSubmodule,
|
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
|
return gui, nil
|
||||||
@ -621,7 +744,7 @@ func (gui *Gui) Run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gui.waitForIntro.Add(1)
|
gui.waitForIntro.Add(1)
|
||||||
if gui.UserConfig.Git.AutoFetch {
|
if gui.c.UserConfig.Git.AutoFetch {
|
||||||
go utils.Safe(gui.startBackgroundFetch)
|
go utils.Safe(gui.startBackgroundFetch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -629,7 +752,7 @@ func (gui *Gui) Run() error {
|
|||||||
|
|
||||||
g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout()))
|
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()
|
err = g.MainLoop()
|
||||||
return err
|
return err
|
||||||
@ -684,7 +807,7 @@ func (gui *Gui) runSubprocessWithSuspenseAndRefresh(subprocess oscommands.ICmdOb
|
|||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -705,7 +828,7 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := gui.g.Suspend(); err != nil {
|
if err := gui.g.Suspend(); err != nil {
|
||||||
return false, gui.PopupHandler.Error(err)
|
return false, gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.PauseBackgroundThreads = true
|
gui.PauseBackgroundThreads = true
|
||||||
@ -719,14 +842,14 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool,
|
|||||||
gui.PauseBackgroundThreads = false
|
gui.PauseBackgroundThreads = false
|
||||||
|
|
||||||
if cmdErr != nil {
|
if cmdErr != nil {
|
||||||
return false, gui.PopupHandler.Error(cmdErr)
|
return false, gui.c.Error(cmdErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) runSubprocess(cmdObj oscommands.ICmdObj) error { //nolint:unparam
|
func (gui *Gui) runSubprocess(cmdObj oscommands.ICmdObj) error { //nolint:unparam
|
||||||
gui.logCommand(cmdObj.ToString(), true)
|
gui.LogCommand(cmdObj.ToString(), true)
|
||||||
|
|
||||||
subprocess := cmdObj.GetCmd()
|
subprocess := cmdObj.GetCmd()
|
||||||
subprocess.Stdout = os.Stdout
|
subprocess.Stdout = os.Stdout
|
||||||
@ -754,7 +877,7 @@ func (gui *Gui) loadNewRepo() error {
|
|||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -774,7 +897,7 @@ func (gui *Gui) showInitialPopups(tasks []func(chan struct{}) error) {
|
|||||||
task := task
|
task := task
|
||||||
go utils.Safe(func() {
|
go utils.Safe(func() {
|
||||||
if err := task(done); err != nil {
|
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 {
|
func (gui *Gui) showIntroPopupMessage(done chan struct{}) error {
|
||||||
onConfirm := func() error {
|
onConfirm := func() error {
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
gui.Config.GetAppState().StartupPopupVersion = StartupPopupVersion
|
gui.c.GetAppState().StartupPopupVersion = StartupPopupVersion
|
||||||
return gui.Config.SaveAppState()
|
return gui.c.SaveAppState()
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: "",
|
Title: "",
|
||||||
Prompt: gui.Tr.IntroPopupMessage,
|
Prompt: gui.c.Tr.IntroPopupMessage,
|
||||||
HandleConfirm: onConfirm,
|
HandleConfirm: onConfirm,
|
||||||
HandleClose: onConfirm,
|
HandleClose: onConfirm,
|
||||||
})
|
})
|
||||||
@ -826,9 +949,9 @@ func (gui *Gui) startBackgroundFetch() {
|
|||||||
}
|
}
|
||||||
err := gui.backgroundFetch()
|
err := gui.backgroundFetch()
|
||||||
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
|
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
|
||||||
_ = gui.PopupHandler.Ask(popup.AskOpts{
|
_ = gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.NoAutomaticGitFetchTitle,
|
Title: gui.c.Tr.NoAutomaticGitFetchTitle,
|
||||||
Prompt: gui.Tr.NoAutomaticGitFetchBody,
|
Prompt: gui.c.Tr.NoAutomaticGitFetchBody,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
gui.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), gui.stopChan, func() error {
|
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 {
|
if gui.g.Mouse {
|
||||||
donate := style.FgMagenta.SetUnderline().Sprint(gui.Tr.Donate)
|
donate := style.FgMagenta.SetUnderline().Sprint(gui.c.Tr.Donate)
|
||||||
askQuestion := style.FgYellow.SetUnderline().Sprint(gui.Tr.AskQuestion)
|
askQuestion := style.FgYellow.SetUnderline().Sprint(gui.c.Tr.AskQuestion)
|
||||||
return fmt.Sprintf("%s %s %s", donate, askQuestion, gui.Config.GetVersion())
|
return fmt.Sprintf("%s %s %s", donate, askQuestion, gui.Config.GetVersion())
|
||||||
} else {
|
} else {
|
||||||
return gui.Config.GetVersion()
|
return gui.Config.GetVersion()
|
||||||
@ -35,7 +35,7 @@ func (gui *Gui) handleInfoClick() error {
|
|||||||
|
|
||||||
for _, mode := range gui.modeStatuses() {
|
for _, mode := range gui.modeStatuses() {
|
||||||
if mode.isActive() {
|
if mode.isActive() {
|
||||||
if width-cx > len(gui.Tr.ResetInParentheses) {
|
if width-cx > len(gui.c.Tr.ResetInParentheses) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return mode.reset()
|
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 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)
|
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 gui.OSCommand.OpenLink(constants.Links.Discussions)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@ package gui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -50,36 +51,36 @@ func (gui *Gui) createAllViews() error {
|
|||||||
gui.Views.SearchPrefix.Frame = false
|
gui.Views.SearchPrefix.Frame = false
|
||||||
gui.setViewContent(gui.Views.SearchPrefix, SEARCH_PREFIX)
|
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.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.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.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.Branches.FgColor = theme.GocuiDefaultTextColor
|
||||||
|
|
||||||
gui.Views.Files.Highlight = true
|
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.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.Wrap = true
|
||||||
gui.Views.Secondary.FgColor = theme.GocuiDefaultTextColor
|
gui.Views.Secondary.FgColor = theme.GocuiDefaultTextColor
|
||||||
gui.Views.Secondary.IgnoreCarriageReturns = true
|
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.Wrap = true
|
||||||
gui.Views.Main.FgColor = theme.GocuiDefaultTextColor
|
gui.Views.Main.FgColor = theme.GocuiDefaultTextColor
|
||||||
gui.Views.Main.IgnoreCarriageReturns = true
|
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.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.Status.FgColor = theme.GocuiDefaultTextColor
|
||||||
|
|
||||||
gui.Views.Search.BgColor = gocui.ColorDefault
|
gui.Views.Search.BgColor = gocui.ColorDefault
|
||||||
@ -93,7 +94,7 @@ func (gui *Gui) createAllViews() error {
|
|||||||
gui.Views.AppStatus.Visible = false
|
gui.Views.AppStatus.Visible = false
|
||||||
|
|
||||||
gui.Views.CommitMessage.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.FgColor = theme.GocuiDefaultTextColor
|
||||||
gui.Views.CommitMessage.Editable = true
|
gui.Views.CommitMessage.Editable = true
|
||||||
gui.Views.CommitMessage.Editor = gocui.EditorFunc(gui.commitMessageEditor)
|
gui.Views.CommitMessage.Editor = gocui.EditorFunc(gui.commitMessageEditor)
|
||||||
@ -101,7 +102,7 @@ func (gui *Gui) createAllViews() error {
|
|||||||
gui.Views.Confirmation.Visible = false
|
gui.Views.Confirmation.Visible = false
|
||||||
|
|
||||||
gui.Views.Credentials.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.FgColor = theme.GocuiDefaultTextColor
|
||||||
gui.Views.Credentials.Editable = true
|
gui.Views.Credentials.Editable = true
|
||||||
|
|
||||||
@ -113,7 +114,7 @@ func (gui *Gui) createAllViews() error {
|
|||||||
gui.Views.Information.FgColor = gocui.ColorGreen
|
gui.Views.Information.FgColor = gocui.ColorGreen
|
||||||
gui.Views.Information.Frame = false
|
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.FgColor = theme.GocuiDefaultTextColor
|
||||||
gui.Views.Extras.Autoscroll = true
|
gui.Views.Extras.Autoscroll = true
|
||||||
gui.Views.Extras.Wrap = 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
|
// 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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,7 +272,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
view.SelBgColor = theme.GocuiSelectedLineBgColor
|
view.SelBgColor = theme.GocuiSelectedLineBgColor
|
||||||
|
|
||||||
// I doubt this is expensive though it's admittedly redundant after the first render
|
// 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))
|
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
|
// here is a good place log some stuff
|
||||||
// if you run `lazygit --logs`
|
// if you run `lazygit --logs`
|
||||||
// this will let you see these branches as prettified json
|
// 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()
|
return gui.resizeCurrentPopupPanel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,7 +311,7 @@ func (gui *Gui) onInitialViewsCreationForRepo() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initialContext := gui.currentSideContext()
|
initialContext := gui.currentSideContext()
|
||||||
if err := gui.pushContext(initialContext); err != nil {
|
if err := gui.c.PushContext(initialContext); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,9 +373,9 @@ func (gui *Gui) onInitialViewsCreation() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !gui.UserConfig.DisableStartupPopups {
|
if !gui.c.UserConfig.DisableStartupPopups {
|
||||||
popupTasks := []func(chan struct{}) error{}
|
popupTasks := []func(chan struct{}) error{}
|
||||||
storedPopupVersion := gui.Config.GetAppState().StartupPopupVersion
|
storedPopupVersion := gui.c.GetAppState().StartupPopupVersion
|
||||||
if storedPopupVersion < StartupPopupVersion {
|
if storedPopupVersion < StartupPopupVersion {
|
||||||
popupTasks = append(popupTasks, gui.showIntroPopupMessage)
|
popupTasks = append(popupTasks, gui.showIntroPopupMessage)
|
||||||
}
|
}
|
||||||
|
@ -87,9 +87,9 @@ func (gui *Gui) copySelectedToClipboard() error {
|
|||||||
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
|
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
|
||||||
selected := state.PlainRenderSelected()
|
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 {
|
if err := gui.OSCommand.CopyToClipboard(selected); err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -141,7 +141,7 @@ func (gui *Gui) refreshMainViewForLineByLine(state *LblPanelState) error {
|
|||||||
if gui.currentContext().GetKey() == gui.State.Contexts.PatchBuilding.GetKey() {
|
if gui.currentContext().GetKey() == gui.State.Contexts.PatchBuilding.GetKey() {
|
||||||
filename := gui.getSelectedCommitFileName()
|
filename := gui.getSelectedCommitFileName()
|
||||||
var err error
|
var err error
|
||||||
includedLineIndices, err = gui.Git.Patch.PatchManager.GetFileIncLineIndices(filename)
|
includedLineIndices, err = gui.git.Patch.PatchManager.GetFileIncLineIndices(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -285,5 +285,5 @@ func (gui *Gui) handleLineByLineEdit() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lineNumber := gui.State.Panels.LineByLine.CurrentLineNumber()
|
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"
|
"fmt"
|
||||||
|
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ListContext struct {
|
type ListContext struct {
|
||||||
GetItemsLength func() int
|
GetItemsLength func() int
|
||||||
GetDisplayStrings func(startIdx int, length int) [][]string
|
GetDisplayStrings func(startIdx int, length int) [][]string
|
||||||
OnFocus func(...OnFocusOpts) error
|
OnFocus func(...types.OnFocusOpts) error
|
||||||
OnRenderToMain func(...OnFocusOpts) error
|
OnRenderToMain func(...types.OnFocusOpts) error
|
||||||
OnFocusLost func() error
|
OnFocusLost func() error
|
||||||
OnClickSelectedItem 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)
|
// 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)
|
SelectedItem func() (types.ListItem, bool)
|
||||||
OnGetPanelState func() IListPanelState
|
OnGetPanelState func() types.IListPanelState
|
||||||
// if this is true, we'll call GetDisplayStrings for just the visible part of the
|
// 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
|
// 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)
|
// content based on the selection (e.g. for showing the selected commit)
|
||||||
@ -27,45 +28,12 @@ type ListContext struct {
|
|||||||
*BasicContext
|
*BasicContext
|
||||||
}
|
}
|
||||||
|
|
||||||
type IListContext interface {
|
var _ types.IListContext = &ListContext{}
|
||||||
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
|
|
||||||
|
|
||||||
GetPanelState() IListPanelState
|
func (self *ListContext) GetPanelState() types.IListPanelState {
|
||||||
|
|
||||||
Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ListContext) GetPanelState() IListPanelState {
|
|
||||||
return self.OnGetPanelState()
|
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() {
|
func (self *ListContext) FocusLine() {
|
||||||
view, err := self.Gui.g.View(self.ViewName)
|
view, err := self.Gui.g.View(self.ViewName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -87,7 +55,7 @@ func formatListFooter(selectedLineIdx int, length int) string {
|
|||||||
return fmt.Sprintf("%d of %d", selectedLineIdx+1, length)
|
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()
|
return self.SelectedItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +100,7 @@ func (self *ListContext) HandleFocusLost() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ListContext) HandleFocus(opts ...OnFocusOpts) error {
|
func (self *ListContext) HandleFocus(opts ...types.OnFocusOpts) error {
|
||||||
if self.Gui.popupPanelFocused() {
|
if self.Gui.popupPanelFocused() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -158,19 +126,19 @@ func (self *ListContext) HandleFocus(opts ...OnFocusOpts) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ListContext) handlePrevLine() error {
|
func (self *ListContext) HandlePrevLine() error {
|
||||||
return self.handleLineChange(-1)
|
return self.handleLineChange(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ListContext) handleNextLine() error {
|
func (self *ListContext) HandleNextLine() error {
|
||||||
return self.handleLineChange(1)
|
return self.handleLineChange(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ListContext) handleScrollLeft() error {
|
func (self *ListContext) HandleScrollLeft() error {
|
||||||
return self.scroll(self.Gui.scrollLeft)
|
return self.scroll(self.Gui.scrollLeft)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ListContext) handleScrollRight() error {
|
func (self *ListContext) HandleScrollRight() error {
|
||||||
return self.scroll(self.Gui.scrollRight)
|
return self.scroll(self.Gui.scrollRight)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +177,7 @@ func (self *ListContext) handleLineChange(change int) error {
|
|||||||
return self.HandleFocus()
|
return self.HandleFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ListContext) handleNextPage() error {
|
func (self *ListContext) HandleNextPage() error {
|
||||||
view, err := self.Gui.g.View(self.ViewName)
|
view, err := self.Gui.g.View(self.ViewName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
@ -219,15 +187,15 @@ func (self *ListContext) handleNextPage() error {
|
|||||||
return self.handleLineChange(delta)
|
return self.handleLineChange(delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ListContext) handleGotoTop() error {
|
func (self *ListContext) HandleGotoTop() error {
|
||||||
return self.handleLineChange(-self.GetItemsLength())
|
return self.handleLineChange(-self.GetItemsLength())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ListContext) handleGotoBottom() error {
|
func (self *ListContext) HandleGotoBottom() error {
|
||||||
return self.handleLineChange(self.GetItemsLength())
|
return self.handleLineChange(self.GetItemsLength())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ListContext) handlePrevPage() error {
|
func (self *ListContext) HandlePrevPage() error {
|
||||||
view, err := self.Gui.g.View(self.ViewName)
|
view, err := self.Gui.g.View(self.ViewName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
@ -238,7 +206,7 @@ func (self *ListContext) handlePrevPage() error {
|
|||||||
return self.handleLineChange(-delta)
|
return self.handleLineChange(-delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ListContext) handleClick() error {
|
func (self *ListContext) HandleClick(onClick func() error) error {
|
||||||
if self.ignoreKeybinding() {
|
if self.ignoreKeybinding() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -252,7 +220,7 @@ func (self *ListContext) handleClick() error {
|
|||||||
newSelectedLineIdx := view.SelectedLineIdx()
|
newSelectedLineIdx := view.SelectedLineIdx()
|
||||||
|
|
||||||
// we need to focus the view
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,13 +231,13 @@ func (self *ListContext) handleClick() error {
|
|||||||
self.GetPanelState().SetSelectedLineIdx(newSelectedLineIdx)
|
self.GetPanelState().SetSelectedLineIdx(newSelectedLineIdx)
|
||||||
|
|
||||||
prevViewName := self.Gui.currentViewName()
|
prevViewName := self.Gui.currentViewName()
|
||||||
if prevSelectedLineIdx == newSelectedLineIdx && prevViewName == self.ViewName && self.OnClickSelectedItem != nil {
|
if prevSelectedLineIdx == newSelectedLineIdx && prevViewName == self.ViewName && onClick != nil {
|
||||||
return self.OnClickSelectedItem()
|
return onClick()
|
||||||
}
|
}
|
||||||
return self.HandleFocus()
|
return self.HandleFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ListContext) onSearchSelect(selectedLineIdx int) error {
|
func (self *ListContext) OnSearchSelect(selectedLineIdx int) error {
|
||||||
self.GetPanelState().SetSelectedLineIdx(selectedLineIdx)
|
self.GetPanelState().SetSelectedLineIdx(selectedLineIdx)
|
||||||
return self.HandleFocus()
|
return self.HandleFocus()
|
||||||
}
|
}
|
||||||
@ -281,3 +249,35 @@ func (self *ListContext) HandleRenderToMain() error {
|
|||||||
|
|
||||||
return nil
|
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 (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/jesseduffield/gocui"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gui *Gui) menuListContext() IListContext {
|
func (gui *Gui) menuListContext() types.IListContext {
|
||||||
return &ListContext{
|
return &ListContext{
|
||||||
BasicContext: &BasicContext{
|
BasicContext: &BasicContext{
|
||||||
ViewName: "menu",
|
ViewName: "menu",
|
||||||
Key: "menu",
|
Key: "menu",
|
||||||
Kind: PERSISTENT_POPUP,
|
Kind: types.PERSISTENT_POPUP,
|
||||||
OnGetOptionsMap: gui.getMenuOptions,
|
OnGetOptionsMap: gui.getMenuOptions,
|
||||||
},
|
},
|
||||||
GetItemsLength: func() int { return gui.Views.Menu.LinesHeight() },
|
GetItemsLength: func() int { return gui.Views.Menu.LinesHeight() },
|
||||||
OnGetPanelState: func() IListPanelState { return gui.State.Panels.Menu },
|
OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Menu },
|
||||||
OnClickSelectedItem: gui.onMenuPress,
|
Gui: gui,
|
||||||
Gui: gui,
|
|
||||||
|
|
||||||
// no GetDisplayStrings field because we do a custom render on menu creation
|
// 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{
|
return &ListContext{
|
||||||
BasicContext: &BasicContext{
|
BasicContext: &BasicContext{
|
||||||
ViewName: "files",
|
ViewName: "files",
|
||||||
WindowName: "files",
|
WindowName: "files",
|
||||||
Key: FILES_CONTEXT_KEY,
|
Key: FILES_CONTEXT_KEY,
|
||||||
Kind: SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
},
|
},
|
||||||
GetItemsLength: func() int { return gui.State.FileTreeViewModel.GetItemsLength() },
|
GetItemsLength: func() int { return gui.State.FileTreeViewModel.GetItemsLength() },
|
||||||
OnGetPanelState: func() IListPanelState { return gui.State.Panels.Files },
|
OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Files },
|
||||||
OnFocus: OnFocusWrapper(gui.onFocusFile),
|
OnFocus: OnFocusWrapper(gui.onFocusFile),
|
||||||
OnRenderToMain: OnFocusWrapper(gui.filesRenderToMain),
|
OnRenderToMain: OnFocusWrapper(gui.filesRenderToMain),
|
||||||
OnClickSelectedItem: gui.handleFilePress,
|
Gui: gui,
|
||||||
Gui: gui,
|
|
||||||
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
||||||
lines := presentation.RenderFileTree(gui.State.FileTreeViewModel, gui.State.Modes.Diffing.Ref, gui.State.Submodules)
|
lines := presentation.RenderFileTree(gui.State.FileTreeViewModel, gui.State.Modes.Diffing.Ref, gui.State.Submodules)
|
||||||
mappedLines := make([][]string, len(lines))
|
mappedLines := make([][]string, len(lines))
|
||||||
@ -50,117 +47,115 @@ func (gui *Gui) filesListContext() IListContext {
|
|||||||
|
|
||||||
return mappedLines
|
return mappedLines
|
||||||
},
|
},
|
||||||
SelectedItem: func() (ListItem, bool) {
|
SelectedItem: func() (types.ListItem, bool) {
|
||||||
item := gui.getSelectedFileNode()
|
item := gui.getSelectedFileNode()
|
||||||
return item, item != nil
|
return item, item != nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) branchesListContext() IListContext {
|
func (gui *Gui) branchesListContext() types.IListContext {
|
||||||
return &ListContext{
|
return &ListContext{
|
||||||
BasicContext: &BasicContext{
|
BasicContext: &BasicContext{
|
||||||
ViewName: "branches",
|
ViewName: "branches",
|
||||||
WindowName: "branches",
|
WindowName: "branches",
|
||||||
Key: LOCAL_BRANCHES_CONTEXT_KEY,
|
Key: LOCAL_BRANCHES_CONTEXT_KEY,
|
||||||
Kind: SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
},
|
},
|
||||||
GetItemsLength: func() int { return len(gui.State.Branches) },
|
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),
|
OnRenderToMain: OnFocusWrapper(gui.branchesRenderToMain),
|
||||||
Gui: gui,
|
Gui: gui,
|
||||||
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
||||||
return presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.State.ScreenMode != SCREEN_NORMAL, gui.State.Modes.Diffing.Ref)
|
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()
|
item := gui.getSelectedBranch()
|
||||||
return item, item != nil
|
return item, item != nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) remotesListContext() IListContext {
|
func (gui *Gui) remotesListContext() types.IListContext {
|
||||||
return &ListContext{
|
return &ListContext{
|
||||||
BasicContext: &BasicContext{
|
BasicContext: &BasicContext{
|
||||||
ViewName: "branches",
|
ViewName: "branches",
|
||||||
WindowName: "branches",
|
WindowName: "branches",
|
||||||
Key: REMOTES_CONTEXT_KEY,
|
Key: REMOTES_CONTEXT_KEY,
|
||||||
Kind: SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
},
|
},
|
||||||
GetItemsLength: func() int { return len(gui.State.Remotes) },
|
GetItemsLength: func() int { return len(gui.State.Remotes) },
|
||||||
OnGetPanelState: func() IListPanelState { return gui.State.Panels.Remotes },
|
OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Remotes },
|
||||||
OnRenderToMain: OnFocusWrapper(gui.remotesRenderToMain),
|
OnRenderToMain: OnFocusWrapper(gui.remotesRenderToMain),
|
||||||
OnClickSelectedItem: gui.handleRemoteEnter,
|
Gui: gui,
|
||||||
Gui: gui,
|
|
||||||
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
||||||
return presentation.GetRemoteListDisplayStrings(gui.State.Remotes, gui.State.Modes.Diffing.Ref)
|
return presentation.GetRemoteListDisplayStrings(gui.State.Remotes, gui.State.Modes.Diffing.Ref)
|
||||||
},
|
},
|
||||||
SelectedItem: func() (ListItem, bool) {
|
SelectedItem: func() (types.ListItem, bool) {
|
||||||
item := gui.getSelectedRemote()
|
item := gui.getSelectedRemote()
|
||||||
return item, item != nil
|
return item, item != nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) remoteBranchesListContext() IListContext {
|
func (gui *Gui) remoteBranchesListContext() types.IListContext {
|
||||||
return &ListContext{
|
return &ListContext{
|
||||||
BasicContext: &BasicContext{
|
BasicContext: &BasicContext{
|
||||||
ViewName: "branches",
|
ViewName: "branches",
|
||||||
WindowName: "branches",
|
WindowName: "branches",
|
||||||
Key: REMOTE_BRANCHES_CONTEXT_KEY,
|
Key: REMOTE_BRANCHES_CONTEXT_KEY,
|
||||||
Kind: SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
},
|
},
|
||||||
GetItemsLength: func() int { return len(gui.State.RemoteBranches) },
|
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),
|
OnRenderToMain: OnFocusWrapper(gui.remoteBranchesRenderToMain),
|
||||||
Gui: gui,
|
Gui: gui,
|
||||||
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
||||||
return presentation.GetRemoteBranchListDisplayStrings(gui.State.RemoteBranches, gui.State.Modes.Diffing.Ref)
|
return presentation.GetRemoteBranchListDisplayStrings(gui.State.RemoteBranches, gui.State.Modes.Diffing.Ref)
|
||||||
},
|
},
|
||||||
SelectedItem: func() (ListItem, bool) {
|
SelectedItem: func() (types.ListItem, bool) {
|
||||||
item := gui.getSelectedRemoteBranch()
|
item := gui.getSelectedRemoteBranch()
|
||||||
return item, item != nil
|
return item, item != nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) tagsListContext() IListContext {
|
func (gui *Gui) tagsListContext() types.IListContext {
|
||||||
return &ListContext{
|
return &ListContext{
|
||||||
BasicContext: &BasicContext{
|
BasicContext: &BasicContext{
|
||||||
ViewName: "branches",
|
ViewName: "branches",
|
||||||
WindowName: "branches",
|
WindowName: "branches",
|
||||||
Key: TAGS_CONTEXT_KEY,
|
Key: TAGS_CONTEXT_KEY,
|
||||||
Kind: SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
},
|
},
|
||||||
GetItemsLength: func() int { return len(gui.State.Tags) },
|
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),
|
OnRenderToMain: OnFocusWrapper(gui.tagsRenderToMain),
|
||||||
Gui: gui,
|
Gui: gui,
|
||||||
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
||||||
return presentation.GetTagListDisplayStrings(gui.State.Tags, gui.State.Modes.Diffing.Ref)
|
return presentation.GetTagListDisplayStrings(gui.State.Tags, gui.State.Modes.Diffing.Ref)
|
||||||
},
|
},
|
||||||
SelectedItem: func() (ListItem, bool) {
|
SelectedItem: func() (types.ListItem, bool) {
|
||||||
item := gui.getSelectedTag()
|
item := gui.getSelectedTag()
|
||||||
return item, item != nil
|
return item, item != nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) branchCommitsListContext() IListContext {
|
func (gui *Gui) branchCommitsListContext() types.IListContext {
|
||||||
parseEmoji := gui.UserConfig.Git.ParseEmoji
|
parseEmoji := gui.c.UserConfig.Git.ParseEmoji
|
||||||
return &ListContext{
|
return &ListContext{
|
||||||
BasicContext: &BasicContext{
|
BasicContext: &BasicContext{
|
||||||
ViewName: "commits",
|
ViewName: "commits",
|
||||||
WindowName: "commits",
|
WindowName: "commits",
|
||||||
Key: BRANCH_COMMITS_CONTEXT_KEY,
|
Key: BRANCH_COMMITS_CONTEXT_KEY,
|
||||||
Kind: SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
},
|
},
|
||||||
GetItemsLength: func() int { return len(gui.State.Commits) },
|
GetItemsLength: func() int { return len(gui.State.Commits) },
|
||||||
OnGetPanelState: func() IListPanelState { return gui.State.Panels.Commits },
|
OnGetPanelState: func() types.IListPanelState { return gui.State.Panels.Commits },
|
||||||
OnFocus: OnFocusWrapper(gui.onCommitFocus),
|
OnFocus: OnFocusWrapper(gui.onCommitFocus),
|
||||||
OnRenderToMain: OnFocusWrapper(gui.branchCommitsRenderToMain),
|
OnRenderToMain: OnFocusWrapper(gui.branchCommitsRenderToMain),
|
||||||
OnClickSelectedItem: gui.handleViewCommitFiles,
|
Gui: gui,
|
||||||
Gui: gui,
|
|
||||||
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
||||||
selectedCommitSha := ""
|
selectedCommitSha := ""
|
||||||
if gui.currentContext().GetKey() == BRANCH_COMMITS_CONTEXT_KEY {
|
if gui.currentContext().GetKey() == BRANCH_COMMITS_CONTEXT_KEY {
|
||||||
@ -182,7 +177,7 @@ func (gui *Gui) branchCommitsListContext() IListContext {
|
|||||||
gui.State.BisectInfo,
|
gui.State.BisectInfo,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
SelectedItem: func() (ListItem, bool) {
|
SelectedItem: func() (types.ListItem, bool) {
|
||||||
item := gui.getSelectedLocalCommit()
|
item := gui.getSelectedLocalCommit()
|
||||||
return item, item != nil
|
return item, item != nil
|
||||||
},
|
},
|
||||||
@ -190,17 +185,17 @@ func (gui *Gui) branchCommitsListContext() IListContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) subCommitsListContext() IListContext {
|
func (gui *Gui) subCommitsListContext() types.IListContext {
|
||||||
parseEmoji := gui.UserConfig.Git.ParseEmoji
|
parseEmoji := gui.c.UserConfig.Git.ParseEmoji
|
||||||
return &ListContext{
|
return &ListContext{
|
||||||
BasicContext: &BasicContext{
|
BasicContext: &BasicContext{
|
||||||
ViewName: "branches",
|
ViewName: "branches",
|
||||||
WindowName: "branches",
|
WindowName: "branches",
|
||||||
Key: SUB_COMMITS_CONTEXT_KEY,
|
Key: SUB_COMMITS_CONTEXT_KEY,
|
||||||
Kind: SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
},
|
},
|
||||||
GetItemsLength: func() int { return len(gui.State.SubCommits) },
|
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),
|
OnRenderToMain: OnFocusWrapper(gui.subCommitsRenderToMain),
|
||||||
Gui: gui,
|
Gui: gui,
|
||||||
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
||||||
@ -224,7 +219,7 @@ func (gui *Gui) subCommitsListContext() IListContext {
|
|||||||
git_commands.NewNullBisectInfo(),
|
git_commands.NewNullBisectInfo(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
SelectedItem: func() (ListItem, bool) {
|
SelectedItem: func() (types.ListItem, bool) {
|
||||||
item := gui.getSelectedSubCommit()
|
item := gui.getSelectedSubCommit()
|
||||||
return item, item != nil
|
return item, item != nil
|
||||||
},
|
},
|
||||||
@ -237,7 +232,7 @@ func (gui *Gui) shouldShowGraph() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
value := gui.UserConfig.Git.Log.ShowGraph
|
value := gui.c.UserConfig.Git.Log.ShowGraph
|
||||||
switch value {
|
switch value {
|
||||||
case "always":
|
case "always":
|
||||||
return true
|
return true
|
||||||
@ -251,17 +246,17 @@ func (gui *Gui) shouldShowGraph() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) reflogCommitsListContext() IListContext {
|
func (gui *Gui) reflogCommitsListContext() types.IListContext {
|
||||||
parseEmoji := gui.UserConfig.Git.ParseEmoji
|
parseEmoji := gui.c.UserConfig.Git.ParseEmoji
|
||||||
return &ListContext{
|
return &ListContext{
|
||||||
BasicContext: &BasicContext{
|
BasicContext: &BasicContext{
|
||||||
ViewName: "commits",
|
ViewName: "commits",
|
||||||
WindowName: "commits",
|
WindowName: "commits",
|
||||||
Key: REFLOG_COMMITS_CONTEXT_KEY,
|
Key: REFLOG_COMMITS_CONTEXT_KEY,
|
||||||
Kind: SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
},
|
},
|
||||||
GetItemsLength: func() int { return len(gui.State.FilteredReflogCommits) },
|
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),
|
OnRenderToMain: OnFocusWrapper(gui.reflogCommitsRenderToMain),
|
||||||
Gui: gui,
|
Gui: gui,
|
||||||
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
||||||
@ -273,45 +268,45 @@ func (gui *Gui) reflogCommitsListContext() IListContext {
|
|||||||
parseEmoji,
|
parseEmoji,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
SelectedItem: func() (ListItem, bool) {
|
SelectedItem: func() (types.ListItem, bool) {
|
||||||
item := gui.getSelectedReflogCommit()
|
item := gui.getSelectedReflogCommit()
|
||||||
return item, item != nil
|
return item, item != nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) stashListContext() IListContext {
|
func (gui *Gui) stashListContext() types.IListContext {
|
||||||
return &ListContext{
|
return &ListContext{
|
||||||
BasicContext: &BasicContext{
|
BasicContext: &BasicContext{
|
||||||
ViewName: "stash",
|
ViewName: "stash",
|
||||||
WindowName: "stash",
|
WindowName: "stash",
|
||||||
Key: STASH_CONTEXT_KEY,
|
Key: STASH_CONTEXT_KEY,
|
||||||
Kind: SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
},
|
},
|
||||||
GetItemsLength: func() int { return len(gui.State.StashEntries) },
|
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),
|
OnRenderToMain: OnFocusWrapper(gui.stashRenderToMain),
|
||||||
Gui: gui,
|
Gui: gui,
|
||||||
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
||||||
return presentation.GetStashEntryListDisplayStrings(gui.State.StashEntries, gui.State.Modes.Diffing.Ref)
|
return presentation.GetStashEntryListDisplayStrings(gui.State.StashEntries, gui.State.Modes.Diffing.Ref)
|
||||||
},
|
},
|
||||||
SelectedItem: func() (ListItem, bool) {
|
SelectedItem: func() (types.ListItem, bool) {
|
||||||
item := gui.getSelectedStashEntry()
|
item := gui.getSelectedStashEntry()
|
||||||
return item, item != nil
|
return item, item != nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) commitFilesListContext() IListContext {
|
func (gui *Gui) commitFilesListContext() types.IListContext {
|
||||||
return &ListContext{
|
return &ListContext{
|
||||||
BasicContext: &BasicContext{
|
BasicContext: &BasicContext{
|
||||||
ViewName: "commitFiles",
|
ViewName: "commitFiles",
|
||||||
WindowName: "commits",
|
WindowName: "commits",
|
||||||
Key: COMMIT_FILES_CONTEXT_KEY,
|
Key: COMMIT_FILES_CONTEXT_KEY,
|
||||||
Kind: SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
},
|
},
|
||||||
GetItemsLength: func() int { return gui.State.CommitFileTreeViewModel.GetItemsLength() },
|
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),
|
OnFocus: OnFocusWrapper(gui.onCommitFileFocus),
|
||||||
OnRenderToMain: OnFocusWrapper(gui.commitFilesRenderToMain),
|
OnRenderToMain: OnFocusWrapper(gui.commitFilesRenderToMain),
|
||||||
Gui: gui,
|
Gui: gui,
|
||||||
@ -320,7 +315,7 @@ func (gui *Gui) commitFilesListContext() IListContext {
|
|||||||
return [][]string{{style.FgRed.Sprint("(none)")}}
|
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))
|
mappedLines := make([][]string, len(lines))
|
||||||
for i, line := range lines {
|
for i, line := range lines {
|
||||||
mappedLines[i] = []string{line}
|
mappedLines[i] = []string{line}
|
||||||
@ -328,45 +323,45 @@ func (gui *Gui) commitFilesListContext() IListContext {
|
|||||||
|
|
||||||
return mappedLines
|
return mappedLines
|
||||||
},
|
},
|
||||||
SelectedItem: func() (ListItem, bool) {
|
SelectedItem: func() (types.ListItem, bool) {
|
||||||
item := gui.getSelectedCommitFileNode()
|
item := gui.getSelectedCommitFileNode()
|
||||||
return item, item != nil
|
return item, item != nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) submodulesListContext() IListContext {
|
func (gui *Gui) submodulesListContext() types.IListContext {
|
||||||
return &ListContext{
|
return &ListContext{
|
||||||
BasicContext: &BasicContext{
|
BasicContext: &BasicContext{
|
||||||
ViewName: "files",
|
ViewName: "files",
|
||||||
WindowName: "files",
|
WindowName: "files",
|
||||||
Key: SUBMODULES_CONTEXT_KEY,
|
Key: SUBMODULES_CONTEXT_KEY,
|
||||||
Kind: SIDE_CONTEXT,
|
Kind: types.SIDE_CONTEXT,
|
||||||
},
|
},
|
||||||
GetItemsLength: func() int { return len(gui.State.Submodules) },
|
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),
|
OnRenderToMain: OnFocusWrapper(gui.submodulesRenderToMain),
|
||||||
Gui: gui,
|
Gui: gui,
|
||||||
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
||||||
return presentation.GetSubmoduleListDisplayStrings(gui.State.Submodules)
|
return presentation.GetSubmoduleListDisplayStrings(gui.State.Submodules)
|
||||||
},
|
},
|
||||||
SelectedItem: func() (ListItem, bool) {
|
SelectedItem: func() (types.ListItem, bool) {
|
||||||
item := gui.getSelectedSubmodule()
|
item := gui.getSelectedSubmodule()
|
||||||
return item, item != nil
|
return item, item != nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) suggestionsListContext() IListContext {
|
func (gui *Gui) suggestionsListContext() types.IListContext {
|
||||||
return &ListContext{
|
return &ListContext{
|
||||||
BasicContext: &BasicContext{
|
BasicContext: &BasicContext{
|
||||||
ViewName: "suggestions",
|
ViewName: "suggestions",
|
||||||
WindowName: "suggestions",
|
WindowName: "suggestions",
|
||||||
Key: SUGGESTIONS_CONTEXT_KEY,
|
Key: SUGGESTIONS_CONTEXT_KEY,
|
||||||
Kind: PERSISTENT_POPUP,
|
Kind: types.PERSISTENT_POPUP,
|
||||||
},
|
},
|
||||||
GetItemsLength: func() int { return len(gui.State.Suggestions) },
|
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,
|
Gui: gui,
|
||||||
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
GetDisplayStrings: func(startIdx int, length int) [][]string {
|
||||||
return presentation.GetSuggestionListDisplayStrings(gui.State.Suggestions)
|
return presentation.GetSuggestionListDisplayStrings(gui.State.Suggestions)
|
||||||
@ -374,8 +369,8 @@ func (gui *Gui) suggestionsListContext() IListContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getListContexts() []IListContext {
|
func (gui *Gui) getListContexts() []types.IListContext {
|
||||||
return []IListContext{
|
return []types.IListContext{
|
||||||
gui.State.Contexts.Menu,
|
gui.State.Contexts.Menu,
|
||||||
gui.State.Contexts.Files,
|
gui.State.Contexts.Files,
|
||||||
gui.State.Contexts.Branches,
|
gui.State.Contexts.Branches,
|
||||||
@ -391,58 +386,3 @@ func (gui *Gui) getListContexts() []IListContext {
|
|||||||
gui.State.Contexts.Suggestions,
|
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
|
view.Highlight = opts.highlight
|
||||||
|
|
||||||
if err := gui.runTaskForView(view, opts.task); err != nil {
|
if err := gui.runTaskForView(view, opts.task); err != nil {
|
||||||
gui.Log.Error(err)
|
gui.c.Log.Error(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,12 +10,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (gui *Gui) getMenuOptions() map[string]string {
|
func (gui *Gui) getMenuOptions() map[string]string {
|
||||||
keybindingConfig := gui.UserConfig.Keybinding
|
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||||
|
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
gui.getKeyDisplay(keybindingConfig.Universal.Return): gui.Tr.LcClose,
|
gui.getKeyDisplay(keybindingConfig.Universal.Return): gui.c.Tr.LcClose,
|
||||||
fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.Tr.LcNavigate,
|
fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
|
||||||
gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.Tr.LcExecute,
|
gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.c.Tr.LcExecute,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ func (gui *Gui) createMenu(opts popup.CreateMenuOptions) error {
|
|||||||
if !opts.HideCancel {
|
if !opts.HideCancel {
|
||||||
// this is mutative but I'm okay with that for now
|
// this is mutative but I'm okay with that for now
|
||||||
opts.Items = append(opts.Items, &popup.MenuItem{
|
opts.Items = append(opts.Items, &popup.MenuItem{
|
||||||
DisplayStrings: []string{gui.Tr.LcCancel},
|
DisplayStrings: []string{gui.c.Tr.LcCancel},
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -66,18 +66,13 @@ func (gui *Gui) createMenu(opts popup.CreateMenuOptions) error {
|
|||||||
menuView.SetContent(list)
|
menuView.SetContent(list)
|
||||||
gui.State.Panels.Menu.SelectedLineIdx = 0
|
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 {
|
func (gui *Gui) getSelectedMenuItem() *popup.MenuItem {
|
||||||
selectedLine := gui.State.Panels.Menu.SelectedLineIdx
|
if len(gui.State.MenuItems) == 0 {
|
||||||
if err := gui.returnFromContext(); err != nil {
|
return nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gui.State.MenuItems[selectedLine].OnPress(); err != nil {
|
return gui.State.MenuItems[gui.State.Panels.Menu.SelectedLineIdx]
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -52,8 +52,8 @@ func (gui *Gui) handleMergeConflictUndo() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.logAction("Restoring file to previous state")
|
gui.c.LogAction("Restoring file to previous state")
|
||||||
gui.logCommand("Undoing last conflict resolution", false)
|
gui.LogCommand("Undoing last conflict resolution", false)
|
||||||
if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0644); err != nil {
|
if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -124,8 +124,8 @@ func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error
|
|||||||
case mergeconflicts.ALL:
|
case mergeconflicts.ALL:
|
||||||
logStr = "Picking all hunks"
|
logStr = "Picking all hunks"
|
||||||
}
|
}
|
||||||
gui.logAction("Resolve merge conflict")
|
gui.c.LogAction("Resolve merge conflict")
|
||||||
gui.logCommand(logStr, false)
|
gui.LogCommand(logStr, false)
|
||||||
state.PushContent(content)
|
state.PushContent(content)
|
||||||
return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0644)
|
return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0644)
|
||||||
}
|
}
|
||||||
@ -153,7 +153,7 @@ func (gui *Gui) renderConflicts(hasFocus bool) error {
|
|||||||
|
|
||||||
return gui.refreshMainViews(refreshMainOpts{
|
return gui.refreshMainViews(refreshMainOpts{
|
||||||
main: &viewUpdateOpts{
|
main: &viewUpdateOpts{
|
||||||
title: gui.Tr.MergeConflictsTitle,
|
title: gui.c.Tr.MergeConflictsTitle,
|
||||||
task: NewRenderStringWithoutScrollTask(content),
|
task: NewRenderStringWithoutScrollTask(content),
|
||||||
noWrap: true,
|
noWrap: true,
|
||||||
},
|
},
|
||||||
@ -178,19 +178,19 @@ func (gui *Gui) centerYPos(view *gocui.View, y int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getMergingOptions() map[string]string {
|
func (gui *Gui) getMergingOptions() map[string]string {
|
||||||
keybindingConfig := gui.UserConfig.Keybinding
|
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||||
|
|
||||||
return map[string]string{
|
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.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.Tr.LcNavigateConflicts,
|
fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevBlock), gui.getKeyDisplay(keybindingConfig.Universal.NextBlock)): gui.c.Tr.LcNavigateConflicts,
|
||||||
gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.Tr.LcPickHunk,
|
gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.c.Tr.LcPickHunk,
|
||||||
gui.getKeyDisplay(keybindingConfig.Main.PickBothHunks): gui.Tr.LcPickAllHunks,
|
gui.getKeyDisplay(keybindingConfig.Main.PickBothHunks): gui.c.Tr.LcPickAllHunks,
|
||||||
gui.getKeyDisplay(keybindingConfig.Universal.Undo): gui.Tr.LcUndo,
|
gui.getKeyDisplay(keybindingConfig.Universal.Undo): gui.c.Tr.LcUndo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleEscapeMerge() error {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,7 +200,7 @@ func (gui *Gui) handleEscapeMerge() error {
|
|||||||
func (gui *Gui) onLastConflictResolved() error {
|
func (gui *Gui) onLastConflictResolved() error {
|
||||||
// as part of refreshing files, we handle the situation where a file has had
|
// as part of refreshing files, we handle the situation where a file has had
|
||||||
// its merge conflicts resolved.
|
// 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() {
|
func (gui *Gui) resetMergeState() {
|
||||||
@ -209,7 +209,7 @@ func (gui *Gui) resetMergeState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) setMergeState(path string) (bool, error) {
|
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 {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -269,7 +269,6 @@ func (gui *Gui) setConflictsAndRender(path string, hasFocus bool) (bool, error)
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we don't have conflicts we'll fall through and show the diff
|
|
||||||
if hasConflicts {
|
if hasConflicts {
|
||||||
return true, gui.renderConflicts(hasFocus)
|
return true, gui.renderConflicts(hasFocus)
|
||||||
}
|
}
|
||||||
@ -294,7 +293,7 @@ func (gui *Gui) refreshMergeState() error {
|
|||||||
|
|
||||||
hasConflicts, err := gui.setConflictsAndRender(gui.State.Panels.Merging.GetPath(), true)
|
hasConflicts, err := gui.setConflictsAndRender(gui.State.Panels.Merging.GetPath(), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.surfaceError(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasConflicts {
|
if !hasConflicts {
|
||||||
@ -303,3 +302,19 @@ func (gui *Gui) refreshMergeState() error {
|
|||||||
|
|
||||||
return nil
|
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(
|
return gui.withResetButton(
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"%s %s",
|
"%s %s",
|
||||||
gui.Tr.LcShowingGitDiff,
|
gui.c.Tr.LcShowingGitDiff,
|
||||||
"git diff "+gui.diffStr(),
|
"git diff "+gui.diffStr(),
|
||||||
),
|
),
|
||||||
style.FgMagenta,
|
style.FgMagenta,
|
||||||
@ -30,9 +30,9 @@ func (gui *Gui) modeStatuses() []modeStatus {
|
|||||||
reset: gui.exitDiffMode,
|
reset: gui.exitDiffMode,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isActive: gui.Git.Patch.PatchManager.Active,
|
isActive: gui.git.Patch.PatchManager.Active,
|
||||||
description: func() string {
|
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,
|
reset: gui.handleResetPatch,
|
||||||
},
|
},
|
||||||
@ -42,7 +42,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
|
|||||||
return gui.withResetButton(
|
return gui.withResetButton(
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"%s '%s'",
|
"%s '%s'",
|
||||||
gui.Tr.LcFilteringBy,
|
gui.c.Tr.LcFilteringBy,
|
||||||
gui.State.Modes.Filtering.GetPath(),
|
gui.State.Modes.Filtering.GetPath(),
|
||||||
),
|
),
|
||||||
style.FgRed,
|
style.FgRed,
|
||||||
@ -65,10 +65,10 @@ func (gui *Gui) modeStatuses() []modeStatus {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
isActive: func() bool {
|
isActive: func() bool {
|
||||||
return gui.Git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE
|
return gui.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE
|
||||||
},
|
},
|
||||||
description: func() string {
|
description: func() string {
|
||||||
workingTreeState := gui.Git.Status.WorkingTreeState()
|
workingTreeState := gui.git.Status.WorkingTreeState()
|
||||||
return gui.withResetButton(
|
return gui.withResetButton(
|
||||||
formatWorkingTreeState(workingTreeState), style.FgYellow,
|
formatWorkingTreeState(workingTreeState), style.FgYellow,
|
||||||
)
|
)
|
||||||
@ -82,7 +82,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
|
|||||||
description: func() string {
|
description: func() string {
|
||||||
return gui.withResetButton("bisecting", style.FgGreen)
|
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(
|
return textStyle.Sprintf(
|
||||||
"%s %s",
|
"%s %s",
|
||||||
content,
|
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{
|
return gui.c.Menu(popup.CreateMenuOptions{
|
||||||
Title: strings.Title(gui.Tr.LcMenu),
|
Title: strings.Title(gui.c.Tr.LcMenu),
|
||||||
Items: menuItems,
|
Items: menuItems,
|
||||||
HideCancel: true,
|
HideCancel: true,
|
||||||
})
|
})
|
||||||
|
@ -18,7 +18,7 @@ func (gui *Gui) getFromAndReverseArgsForDiff(to string) (string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
|
func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
|
||||||
if !gui.Git.Patch.PatchManager.Active() {
|
if !gui.git.Patch.PatchManager.Active() {
|
||||||
return gui.handleEscapePatchBuildingPanel()
|
return gui.handleEscapePatchBuildingPanel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,12 +33,12 @@ func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
|
|||||||
|
|
||||||
to := gui.State.CommitFileTreeViewModel.GetParent()
|
to := gui.State.CommitFileTreeViewModel.GetParent()
|
||||||
from, reverse := gui.getFromAndReverseArgsForDiff(to)
|
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 {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -75,15 +75,15 @@ func (gui *Gui) onPatchBuildingFocus(selectedLineIdx int) error {
|
|||||||
|
|
||||||
func (gui *Gui) handleToggleSelectionForPatch() error {
|
func (gui *Gui) handleToggleSelectionForPatch() error {
|
||||||
err := gui.withLBLActiveCheck(func(state *LblPanelState) error {
|
err := gui.withLBLActiveCheck(func(state *LblPanelState) error {
|
||||||
toggleFunc := gui.Git.Patch.PatchManager.AddFileLineRange
|
toggleFunc := gui.git.Patch.PatchManager.AddFileLineRange
|
||||||
filename := gui.getSelectedCommitFileName()
|
filename := gui.getSelectedCommitFileName()
|
||||||
includedLineIndices, err := gui.Git.Patch.PatchManager.GetFileIncLineIndices(filename)
|
includedLineIndices, err := gui.git.Patch.PatchManager.GetFileIncLineIndices(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
currentLineIsStaged := utils.IncludesInt(includedLineIndices, state.GetSelectedLineIdx())
|
currentLineIsStaged := utils.IncludesInt(includedLineIndices, state.GetSelectedLineIdx())
|
||||||
if currentLineIsStaged {
|
if currentLineIsStaged {
|
||||||
toggleFunc = gui.Git.Patch.PatchManager.RemoveFileLineRange
|
toggleFunc = gui.git.Patch.PatchManager.RemoveFileLineRange
|
||||||
}
|
}
|
||||||
|
|
||||||
// add range of lines to those set for the file
|
// 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 {
|
if err := toggleFunc(node.GetPath(), firstLineIdx, lastLineIdx); err != nil {
|
||||||
// might actually want to return an error here
|
// might actually want to return an error here
|
||||||
gui.Log.Error(err)
|
gui.c.Log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -116,12 +116,12 @@ func (gui *Gui) handleToggleSelectionForPatch() error {
|
|||||||
func (gui *Gui) handleEscapePatchBuildingPanel() error {
|
func (gui *Gui) handleEscapePatchBuildingPanel() error {
|
||||||
gui.escapeLineByLinePanel()
|
gui.escapeLineByLinePanel()
|
||||||
|
|
||||||
if gui.Git.Patch.PatchManager.IsEmpty() {
|
if gui.git.Patch.PatchManager.IsEmpty() {
|
||||||
gui.Git.Patch.PatchManager.Reset()
|
gui.git.Patch.PatchManager.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
if gui.currentContext().GetKey() == gui.State.Contexts.PatchBuilding.GetKey() {
|
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 {
|
} else {
|
||||||
// need to re-focus in case the secondary view should now be hidden
|
// need to re-focus in case the secondary view should now be hidden
|
||||||
return gui.currentContext().HandleFocus()
|
return gui.currentContext().HandleFocus()
|
||||||
@ -129,8 +129,8 @@ func (gui *Gui) handleEscapePatchBuildingPanel() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) secondaryPatchPanelUpdateOpts() *viewUpdateOpts {
|
func (gui *Gui) secondaryPatchPanelUpdateOpts() *viewUpdateOpts {
|
||||||
if gui.Git.Patch.PatchManager.Active() {
|
if gui.git.Patch.PatchManager.Active() {
|
||||||
patch := gui.Git.Patch.PatchManager.RenderAggregatedPatchColored(false)
|
patch := gui.git.Patch.PatchManager.RenderAggregatedPatchColored(false)
|
||||||
|
|
||||||
return &viewUpdateOpts{
|
return &viewUpdateOpts{
|
||||||
title: "Custom Patch",
|
title: "Custom Patch",
|
||||||
|
@ -9,8 +9,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (gui *Gui) handleCreatePatchOptionsMenu() error {
|
func (gui *Gui) handleCreatePatchOptionsMenu() error {
|
||||||
if !gui.Git.Patch.PatchManager.Active() {
|
if !gui.git.Patch.PatchManager.Active() {
|
||||||
return gui.PopupHandler.ErrorMsg(gui.Tr.NoPatchError)
|
return gui.c.ErrorMsg(gui.c.Tr.NoPatchError)
|
||||||
}
|
}
|
||||||
|
|
||||||
menuItems := []*popup.MenuItem{
|
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{
|
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,
|
OnPress: gui.handleDeletePatchFromCommit,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -46,7 +46,7 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error {
|
|||||||
|
|
||||||
if gui.currentContext().GetKey() == gui.State.Contexts.BranchCommits.GetKey() {
|
if gui.currentContext().GetKey() == gui.State.Contexts.BranchCommits.GetKey() {
|
||||||
selectedCommit := gui.getSelectedLocalCommit()
|
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
|
// adding this option to index 1
|
||||||
menuItems = append(
|
menuItems = append(
|
||||||
menuItems[:1],
|
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 {
|
func (gui *Gui) getPatchCommitIndex() int {
|
||||||
for index, commit := range gui.State.Commits {
|
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
|
return index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,8 +76,8 @@ func (gui *Gui) getPatchCommitIndex() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) validateNormalWorkingTreeState() (bool, error) {
|
func (gui *Gui) validateNormalWorkingTreeState() (bool, error) {
|
||||||
if gui.Git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
if gui.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||||
return false, gui.PopupHandler.ErrorMsg(gui.Tr.CantPatchWhileRebasingError)
|
return false, gui.c.ErrorMsg(gui.c.Tr.CantPatchWhileRebasingError)
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
@ -98,11 +98,11 @@ func (gui *Gui) handleDeletePatchFromCommit() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
|
return gui.c.WithWaitingStatus(gui.c.Tr.RebasingStatus, func() error {
|
||||||
commitIndex := gui.getPatchCommitIndex()
|
commitIndex := gui.getPatchCommitIndex()
|
||||||
gui.logAction(gui.Tr.Actions.RemovePatchFromCommit)
|
gui.c.LogAction(gui.c.Tr.Actions.RemovePatchFromCommit)
|
||||||
err := gui.Git.Patch.DeletePatchesFromCommit(gui.State.Commits, commitIndex)
|
err := gui.git.Patch.DeletePatchesFromCommit(gui.State.Commits, commitIndex)
|
||||||
return gui.handleGenericMergeCommandResult(err)
|
return gui.checkMergeOrRebase(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,11 +115,11 @@ func (gui *Gui) handleMovePatchToSelectedCommit() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
|
return gui.c.WithWaitingStatus(gui.c.Tr.RebasingStatus, func() error {
|
||||||
commitIndex := gui.getPatchCommitIndex()
|
commitIndex := gui.getPatchCommitIndex()
|
||||||
gui.logAction(gui.Tr.Actions.MovePatchToSelectedCommit)
|
gui.c.LogAction(gui.c.Tr.Actions.MovePatchToSelectedCommit)
|
||||||
err := gui.Git.Patch.MovePatchToSelectedCommit(gui.State.Commits, commitIndex, gui.State.Panels.Commits.SelectedLineIdx)
|
err := gui.git.Patch.MovePatchToSelectedCommit(gui.State.Commits, commitIndex, gui.State.Panels.Commits.SelectedLineIdx)
|
||||||
return gui.handleGenericMergeCommandResult(err)
|
return gui.checkMergeOrRebase(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,18 +133,18 @@ func (gui *Gui) handleMovePatchIntoWorkingTree() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pull := func(stash bool) 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()
|
commitIndex := gui.getPatchCommitIndex()
|
||||||
gui.logAction(gui.Tr.Actions.MovePatchIntoIndex)
|
gui.c.LogAction(gui.c.Tr.Actions.MovePatchIntoIndex)
|
||||||
err := gui.Git.Patch.MovePatchIntoIndex(gui.State.Commits, commitIndex, stash)
|
err := gui.git.Patch.MovePatchIntoIndex(gui.State.Commits, commitIndex, stash)
|
||||||
return gui.handleGenericMergeCommandResult(err)
|
return gui.checkMergeOrRebase(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(gui.trackedFiles()) > 0 {
|
if gui.workingTreeHelper.IsWorkingTreeDirty() {
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.MustStashTitle,
|
Title: gui.c.Tr.MustStashTitle,
|
||||||
Prompt: gui.Tr.MustStashWarning,
|
Prompt: gui.c.Tr.MustStashWarning,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
return pull(true)
|
return pull(true)
|
||||||
},
|
},
|
||||||
@ -163,11 +163,11 @@ func (gui *Gui) handlePullPatchIntoNewCommit() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.PopupHandler.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
|
return gui.c.WithWaitingStatus(gui.c.Tr.RebasingStatus, func() error {
|
||||||
commitIndex := gui.getPatchCommitIndex()
|
commitIndex := gui.getPatchCommitIndex()
|
||||||
gui.logAction(gui.Tr.Actions.MovePatchIntoNewCommit)
|
gui.c.LogAction(gui.c.Tr.Actions.MovePatchIntoNewCommit)
|
||||||
err := gui.Git.Patch.PullPatchIntoNewCommit(gui.State.Commits, commitIndex)
|
err := gui.git.Patch.PullPatchIntoNewCommit(gui.State.Commits, commitIndex)
|
||||||
return gui.handleGenericMergeCommandResult(err)
|
return gui.checkMergeOrRebase(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,21 +176,21 @@ func (gui *Gui) handleApplyPatch(reverse bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
action := gui.Tr.Actions.ApplyPatch
|
action := gui.c.Tr.Actions.ApplyPatch
|
||||||
if reverse {
|
if reverse {
|
||||||
action = "Apply patch in reverse"
|
action = "Apply patch in reverse"
|
||||||
}
|
}
|
||||||
gui.logAction(action)
|
gui.c.LogAction(action)
|
||||||
if err := gui.Git.Patch.PatchManager.ApplyPatches(reverse); err != nil {
|
if err := gui.git.Patch.PatchManager.ApplyPatches(reverse); err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
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 {
|
func (gui *Gui) handleResetPatch() error {
|
||||||
gui.Git.Patch.PatchManager.Reset()
|
gui.git.Patch.PatchManager.Reset()
|
||||||
if gui.currentContextKeyIgnoringPopups() == MAIN_PATCH_BUILDING_CONTEXT_KEY {
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@ type IPopupHandler interface {
|
|||||||
WithLoaderPanel(message string, f func() error) error
|
WithLoaderPanel(message string, f func() error) error
|
||||||
WithWaitingStatus(message string, f func() error) error
|
WithWaitingStatus(message string, f func() error) error
|
||||||
Menu(opts CreateMenuOptions) error
|
Menu(opts CreateMenuOptions) error
|
||||||
|
Toast(message string)
|
||||||
|
GetPromptInput() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateMenuOptions struct {
|
type CreateMenuOptions struct {
|
||||||
@ -74,6 +76,8 @@ type RealPopupHandler struct {
|
|||||||
closePopupFn func() error
|
closePopupFn func() error
|
||||||
createMenuFn func(CreateMenuOptions) error
|
createMenuFn func(CreateMenuOptions) error
|
||||||
withWaitingStatusFn func(message string, f func() error) error
|
withWaitingStatusFn func(message string, f func() error) error
|
||||||
|
toastFn func(message string)
|
||||||
|
getPromptInputFn func() string
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ IPopupHandler = &RealPopupHandler{}
|
var _ IPopupHandler = &RealPopupHandler{}
|
||||||
@ -85,6 +89,8 @@ func NewPopupHandler(
|
|||||||
closePopupFn func() error,
|
closePopupFn func() error,
|
||||||
createMenuFn func(CreateMenuOptions) error,
|
createMenuFn func(CreateMenuOptions) error,
|
||||||
withWaitingStatusFn func(message string, f func() error) error,
|
withWaitingStatusFn func(message string, f func() error) error,
|
||||||
|
toastFn func(message string),
|
||||||
|
getPromptInputFn func() string,
|
||||||
) *RealPopupHandler {
|
) *RealPopupHandler {
|
||||||
return &RealPopupHandler{
|
return &RealPopupHandler{
|
||||||
Common: common,
|
Common: common,
|
||||||
@ -94,6 +100,8 @@ func NewPopupHandler(
|
|||||||
closePopupFn: closePopupFn,
|
closePopupFn: closePopupFn,
|
||||||
createMenuFn: createMenuFn,
|
createMenuFn: createMenuFn,
|
||||||
withWaitingStatusFn: withWaitingStatusFn,
|
withWaitingStatusFn: withWaitingStatusFn,
|
||||||
|
toastFn: toastFn,
|
||||||
|
getPromptInputFn: getPromptInputFn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,6 +109,10 @@ func (self *RealPopupHandler) Menu(opts CreateMenuOptions) error {
|
|||||||
return self.createMenuFn(opts)
|
return self.createMenuFn(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *RealPopupHandler) Toast(message string) {
|
||||||
|
self.toastFn(message)
|
||||||
|
}
|
||||||
|
|
||||||
func (self *RealPopupHandler) WithWaitingStatus(message string, f func() error) error {
|
func (self *RealPopupHandler) WithWaitingStatus(message string, f func() error) error {
|
||||||
return self.withWaitingStatusFn(message, f)
|
return self.withWaitingStatusFn(message, f)
|
||||||
}
|
}
|
||||||
@ -188,6 +200,12 @@ func (self *RealPopupHandler) WithLoaderPanel(message string, f func() error) er
|
|||||||
return nil
|
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 {
|
type TestPopupHandler struct {
|
||||||
OnErrorMsg func(message string) error
|
OnErrorMsg func(message string) error
|
||||||
OnAsk func(opts AskOpts) 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 {
|
func (self *TestPopupHandler) Menu(opts CreateMenuOptions) error {
|
||||||
panic("not yet implemented")
|
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.
|
// command.
|
||||||
func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error {
|
func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error {
|
||||||
width, _ := gui.Views.Main.Size()
|
width, _ := gui.Views.Main.Size()
|
||||||
pager := gui.Git.Config.GetPager(width)
|
pager := gui.git.Config.GetPager(width)
|
||||||
|
|
||||||
if pager == "" {
|
if pager == "" {
|
||||||
// if we're not using a custom pager we don't need to use a pty
|
// 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) {
|
start := func() (*exec.Cmd, io.Reader) {
|
||||||
ptmx, err := pty.StartWithSize(cmd, gui.desiredPtySize())
|
ptmx, err := pty.StartWithSize(cmd, gui.desiredPtySize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gui.Log.Error(err)
|
gui.c.Log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.State.Ptmx = ptmx
|
gui.State.Ptmx = ptmx
|
||||||
|
@ -18,17 +18,17 @@ func (gui *Gui) createPullRequestMenu(selectedBranch *models.Branch, checkedOutB
|
|||||||
menuItemsForBranch := func(branch *models.Branch) []*popup.MenuItem {
|
menuItemsForBranch := func(branch *models.Branch) []*popup.MenuItem {
|
||||||
return []*popup.MenuItem{
|
return []*popup.MenuItem{
|
||||||
{
|
{
|
||||||
DisplayStrings: fromToDisplayStrings(branch.Name, gui.Tr.LcDefaultBranch),
|
DisplayStrings: fromToDisplayStrings(branch.Name, gui.c.Tr.LcDefaultBranch),
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
return gui.createPullRequest(branch.Name, "")
|
return gui.createPullRequest(branch.Name, "")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayStrings: fromToDisplayStrings(branch.Name, gui.Tr.LcSelectBranch),
|
DisplayStrings: fromToDisplayStrings(branch.Name, gui.c.Tr.LcSelectBranch),
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
return gui.PopupHandler.Prompt(popup.PromptOpts{
|
return gui.c.Prompt(popup.PromptOpts{
|
||||||
Title: branch.Name + " →",
|
Title: branch.Name + " →",
|
||||||
FindSuggestionsFunc: gui.getBranchNameSuggestionsFunc(),
|
FindSuggestionsFunc: gui.suggestionsHelper.GetBranchNameSuggestionsFunc(),
|
||||||
HandleConfirm: func(targetBranchName string) error {
|
HandleConfirm: func(targetBranchName string) error {
|
||||||
return gui.createPullRequest(branch.Name, targetBranchName)
|
return gui.createPullRequest(branch.Name, targetBranchName)
|
||||||
}},
|
}},
|
||||||
@ -52,27 +52,27 @@ func (gui *Gui) createPullRequestMenu(selectedBranch *models.Branch, checkedOutB
|
|||||||
|
|
||||||
menuItems = append(menuItems, menuItemsForBranch(selectedBranch)...)
|
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 {
|
func (gui *Gui) createPullRequest(from string, to string) error {
|
||||||
hostingServiceMgr := gui.getHostingServiceMgr()
|
hostingServiceMgr := gui.getHostingServiceMgr()
|
||||||
url, err := hostingServiceMgr.GetPullRequestURL(from, to)
|
url, err := hostingServiceMgr.GetPullRequestURL(from, to)
|
||||||
if err != nil {
|
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 {
|
if err := gui.OSCommand.OpenLink(url); err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getHostingServiceMgr() *hosting_service.HostingServiceMgr {
|
func (gui *Gui) getHostingServiceMgr() *hosting_service.HostingServiceMgr {
|
||||||
remoteUrl := gui.Git.Config.GetRemoteURL()
|
remoteUrl := gui.git.Config.GetRemoteURL()
|
||||||
configServices := gui.UserConfig.Services
|
configServices := gui.c.UserConfig.Services
|
||||||
return hosting_service.NewHostingServiceMgr(gui.Log, gui.Tr, remoteUrl, configServices)
|
return hosting_service.NewHostingServiceMgr(gui.Log, gui.Tr, remoteUrl, configServices)
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ func (gui *Gui) handleTopLevelReturn() error {
|
|||||||
parentContext, hasParent := currentContext.GetParentContext()
|
parentContext, hasParent := currentContext.GetParentContext()
|
||||||
if hasParent && currentContext != nil && parentContext != nil {
|
if hasParent && currentContext != nil && parentContext != nil {
|
||||||
// TODO: think about whether this should be marked as a return rather than adding to the stack
|
// 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() {
|
for _, mode := range gui.modeStatuses() {
|
||||||
@ -60,7 +60,7 @@ func (gui *Gui) handleTopLevelReturn() error {
|
|||||||
return gui.dispatchSwitchToRepo(path, true)
|
return gui.dispatchSwitchToRepo(path, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if gui.UserConfig.QuitOnTopLevelReturn {
|
if gui.c.UserConfig.QuitOnTopLevelReturn {
|
||||||
return gui.handleQuit()
|
return gui.handleQuit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,10 +72,10 @@ func (gui *Gui) quit() error {
|
|||||||
return gui.createUpdateQuitConfirmation()
|
return gui.createUpdateQuitConfirmation()
|
||||||
}
|
}
|
||||||
|
|
||||||
if gui.UserConfig.ConfirmOnQuit {
|
if gui.c.UserConfig.ConfirmOnQuit {
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: "",
|
Title: "",
|
||||||
Prompt: gui.Tr.ConfirmQuit,
|
Prompt: gui.c.Tr.ConfirmQuit,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
return gocui.ErrQuit
|
return gocui.ErrQuit
|
||||||
},
|
},
|
||||||
|
@ -20,7 +20,7 @@ const (
|
|||||||
func (gui *Gui) handleCreateRebaseOptionsMenu() error {
|
func (gui *Gui) handleCreateRebaseOptionsMenu() error {
|
||||||
options := []string{REBASE_OPTION_CONTINUE, REBASE_OPTION_ABORT}
|
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)
|
options = append(options, REBASE_OPTION_SKIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,23 +37,23 @@ func (gui *Gui) handleCreateRebaseOptionsMenu() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var title string
|
var title string
|
||||||
if gui.Git.Status.WorkingTreeState() == enums.REBASE_MODE_MERGING {
|
if gui.git.Status.WorkingTreeState() == enums.REBASE_MODE_MERGING {
|
||||||
title = gui.Tr.MergeOptionsTitle
|
title = gui.c.Tr.MergeOptionsTitle
|
||||||
} else {
|
} 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 {
|
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 {
|
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 := ""
|
commandType := ""
|
||||||
switch status {
|
switch status {
|
||||||
@ -68,14 +68,14 @@ func (gui *Gui) genericMergeCommand(command string) error {
|
|||||||
// we should end up with a command like 'git merge --continue'
|
// 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
|
// 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
|
// TODO: see if we should be calling more of the code from gui.Git.Rebase.GenericMergeOrRebaseAction
|
||||||
return gui.runSubprocessWithSuspenseAndRefresh(
|
return gui.runSubprocessWithSuspenseAndRefresh(
|
||||||
gui.Git.Rebase.GenericMergeOrRebaseActionCmdObj(commandType, command),
|
gui.git.Rebase.GenericMergeOrRebaseActionCmdObj(commandType, command),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
result := gui.Git.Rebase.GenericMergeOrRebaseAction(commandType, command)
|
result := gui.git.Rebase.GenericMergeOrRebaseAction(commandType, command)
|
||||||
if err := gui.handleGenericMergeCommandResult(result); err != nil {
|
if err := gui.checkMergeOrRebase(result); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -98,8 +98,8 @@ func isMergeConflictErr(errStr string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleGenericMergeCommandResult(result error) error {
|
func (gui *Gui) checkMergeOrRebase(result error) error {
|
||||||
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
|
return err
|
||||||
}
|
}
|
||||||
if result == nil {
|
if result == nil {
|
||||||
@ -112,12 +112,12 @@ func (gui *Gui) handleGenericMergeCommandResult(result error) error {
|
|||||||
// assume in this case that we're already done
|
// assume in this case that we're already done
|
||||||
return nil
|
return nil
|
||||||
} else if isMergeConflictErr(result.Error()) {
|
} else if isMergeConflictErr(result.Error()) {
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.FoundConflictsTitle,
|
Title: gui.c.Tr.FoundConflictsTitle,
|
||||||
Prompt: gui.Tr.FoundConflicts,
|
Prompt: gui.c.Tr.FoundConflicts,
|
||||||
HandlersManageFocus: true,
|
HandlersManageFocus: true,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
return gui.pushContext(gui.State.Contexts.Files)
|
return gui.c.PushContext(gui.State.Contexts.Files)
|
||||||
},
|
},
|
||||||
HandleClose: func() error {
|
HandleClose: func() error {
|
||||||
if err := gui.returnFromContext(); err != nil {
|
if err := gui.returnFromContext(); err != nil {
|
||||||
@ -128,16 +128,16 @@ func (gui *Gui) handleGenericMergeCommandResult(result error) error {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return gui.PopupHandler.ErrorMsg(result.Error())
|
return gui.c.ErrorMsg(result.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) abortMergeOrRebaseWithConfirm() error {
|
func (gui *Gui) abortMergeOrRebaseWithConfirm() error {
|
||||||
// prompt user to confirm that they want to abort, then do it
|
// prompt user to confirm that they want to abort, then do it
|
||||||
mode := gui.workingTreeStateNoun()
|
mode := gui.workingTreeStateNoun()
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: fmt.Sprintf(gui.Tr.AbortTitle, mode),
|
Title: fmt.Sprintf(gui.c.Tr.AbortTitle, mode),
|
||||||
Prompt: fmt.Sprintf(gui.Tr.AbortPrompt, mode),
|
Prompt: fmt.Sprintf(gui.c.Tr.AbortPrompt, mode),
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
return gui.genericMergeCommand(REBASE_OPTION_ABORT)
|
return gui.genericMergeCommand(REBASE_OPTION_ABORT)
|
||||||
},
|
},
|
||||||
@ -145,7 +145,7 @@ func (gui *Gui) abortMergeOrRebaseWithConfirm() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) workingTreeStateNoun() string {
|
func (gui *Gui) workingTreeStateNoun() string {
|
||||||
workingTreeState := gui.Git.Status.WorkingTreeState()
|
workingTreeState := gui.git.Status.WorkingTreeState()
|
||||||
switch workingTreeState {
|
switch workingTreeState {
|
||||||
case enums.REBASE_MODE_NONE:
|
case enums.REBASE_MODE_NONE:
|
||||||
return ""
|
return ""
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (gui *Gui) handleCreateRecentReposMenu() error {
|
func (gui *Gui) handleCreateRecentReposMenu() error {
|
||||||
recentRepoPaths := gui.Config.GetAppState().RecentRepos
|
recentRepoPaths := gui.c.GetAppState().RecentRepos
|
||||||
reposCount := utils.Min(len(recentRepoPaths), 20)
|
reposCount := utils.Min(len(recentRepoPaths), 20)
|
||||||
|
|
||||||
// we won't show the current repo hence the -1
|
// 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 {
|
func (gui *Gui) handleShowAllBranchLogs() error {
|
||||||
cmdObj := gui.Git.Branch.AllBranchesLogCmdObj()
|
cmdObj := gui.git.Branch.AllBranchesLogCmdObj()
|
||||||
task := NewRunPtyTask(cmdObj.GetCmd())
|
task := NewRunPtyTask(cmdObj.GetCmd())
|
||||||
|
|
||||||
return gui.refreshMainViews(refreshMainOpts{
|
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 err := os.Chdir(path); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return gui.PopupHandler.ErrorMsg(gui.Tr.ErrRepositoryMovedOrDeleted)
|
return gui.c.ErrorMsg(gui.c.Tr.ErrRepositoryMovedOrDeleted)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -71,11 +71,16 @@ func (gui *Gui) dispatchSwitchToRepo(path string, reuse bool) error {
|
|||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
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
|
// 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
|
// 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,
|
// updateRecentRepoList registers the fact that we opened lazygit in this repo,
|
||||||
// so that we can open the same repo via the 'recent repos' menu
|
// so that we can open the same repo via the 'recent repos' menu
|
||||||
func (gui *Gui) updateRecentRepoList() error {
|
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
|
// 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
|
// worktree in our recent repos list, which is a change that would need to be
|
||||||
// backwards compatible
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
recentRepos := gui.Config.GetAppState().RecentRepos
|
recentRepos := gui.c.GetAppState().RecentRepos
|
||||||
currentRepo, err := os.Getwd()
|
currentRepo, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
known, recentRepos := newRecentReposList(recentRepos, currentRepo)
|
known, recentRepos := newRecentReposList(recentRepos, currentRepo)
|
||||||
gui.IsNewRepo = known
|
gui.IsNewRepo = known
|
||||||
gui.Config.GetAppState().RecentRepos = recentRepos
|
gui.c.GetAppState().RecentRepos = recentRepos
|
||||||
return gui.Config.SaveAppState()
|
return gui.c.SaveAppState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// newRecentReposList returns a new repo list with a new entry but only when it doesn't exist yet
|
// 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 (
|
import (
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"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/popup"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// list panel functions
|
// list panel functions
|
||||||
@ -23,7 +25,7 @@ func (gui *Gui) reflogCommitsRenderToMain() error {
|
|||||||
if commit == nil {
|
if commit == nil {
|
||||||
task = NewRenderStringTask("No reflog history")
|
task = NewRenderStringTask("No reflog history")
|
||||||
} else {
|
} 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())
|
task = NewRunPtyTask(cmdObj.GetCmd())
|
||||||
}
|
}
|
||||||
@ -53,10 +55,10 @@ func (gui *Gui) refreshReflogCommits() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refresh := func(stateCommits *[]*models.Commit, filterPath string) 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)
|
GetReflogCommits(lastReflogCommit, filterPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if onlyObtainedNewReflogCommits {
|
if onlyObtainedNewReflogCommits {
|
||||||
@ -79,21 +81,21 @@ func (gui *Gui) refreshReflogCommits() error {
|
|||||||
state.FilteredReflogCommits = state.ReflogCommits
|
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()
|
commit := gui.getSelectedReflogCommit()
|
||||||
if commit == nil {
|
if commit == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := gui.PopupHandler.Ask(popup.AskOpts{
|
err := gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.LcCheckoutCommit,
|
Title: gui.c.Tr.LcCheckoutCommit,
|
||||||
Prompt: gui.Tr.SureCheckoutThisCommit,
|
Prompt: gui.c.Tr.SureCheckoutThisCommit,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
gui.logAction(gui.Tr.Actions.CheckoutReflogCommit)
|
gui.c.LogAction(gui.c.Tr.Actions.CheckoutReflogCommit)
|
||||||
return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{})
|
return gui.refHelper.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -108,7 +110,7 @@ func (gui *Gui) handleCheckoutReflogCommit() error {
|
|||||||
func (gui *Gui) handleCreateReflogResetMenu() error {
|
func (gui *Gui) handleCreateReflogResetMenu() error {
|
||||||
commit := gui.getSelectedReflogCommit()
|
commit := gui.getSelectedReflogCommit()
|
||||||
|
|
||||||
return gui.createResetMenu(commit.Sha)
|
return gui.refHelper.CreateGitResetMenu(commit.Sha)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleViewReflogCommitFiles() error {
|
func (gui *Gui) handleViewReflogCommitFiles() error {
|
||||||
@ -117,5 +119,10 @@ func (gui *Gui) handleViewReflogCommitFiles() error {
|
|||||||
return nil
|
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 {
|
if remoteBranch == nil {
|
||||||
task = NewRenderStringTask("No branches for this remote")
|
task = NewRenderStringTask("No branches for this remote")
|
||||||
} else {
|
} else {
|
||||||
cmdObj := gui.Git.Branch.GetGraphCmdObj(remoteBranch.FullName())
|
cmdObj := gui.git.Branch.GetGraphCmdObj(remoteBranch.FullName())
|
||||||
task = NewRunCommandTask(cmdObj.GetCmd())
|
task = NewRunCommandTask(cmdObj.GetCmd())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ func (gui *Gui) remoteBranchesRenderToMain() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleRemoteBranchesEscape() 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 {
|
func (gui *Gui) handleMergeRemoteBranch() error {
|
||||||
@ -52,20 +52,20 @@ func (gui *Gui) handleDeleteRemoteBranch() error {
|
|||||||
if remoteBranch == nil {
|
if remoteBranch == nil {
|
||||||
return 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{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.DeleteRemoteBranch,
|
Title: gui.c.Tr.DeleteRemoteBranch,
|
||||||
Prompt: message,
|
Prompt: message,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
return gui.PopupHandler.WithWaitingStatus(gui.Tr.DeletingStatus, func() error {
|
return gui.c.WithWaitingStatus(gui.c.Tr.DeletingStatus, func() error {
|
||||||
gui.logAction(gui.Tr.Actions.DeleteRemoteBranch)
|
gui.c.LogAction(gui.c.Tr.Actions.DeleteRemoteBranch)
|
||||||
err := gui.Git.Remote.DeleteRemoteBranch(remoteBranch.RemoteName, remoteBranch.Name)
|
err := gui.git.Remote.DeleteRemoteBranch(remoteBranch.RemoteName, remoteBranch.Name)
|
||||||
if err != nil {
|
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()
|
checkedOutBranch := gui.getCheckedOutBranch()
|
||||||
|
|
||||||
message := utils.ResolvePlaceholderString(
|
message := utils.ResolvePlaceholderString(
|
||||||
gui.Tr.SetUpstreamMessage,
|
gui.c.Tr.SetUpstreamMessage,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"checkedOut": checkedOutBranch.Name,
|
"checkedOut": checkedOutBranch.Name,
|
||||||
"selected": selectedBranch.FullName(),
|
"selected": selectedBranch.FullName(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.SetUpstreamTitle,
|
Title: gui.c.Tr.SetUpstreamTitle,
|
||||||
Prompt: message,
|
Prompt: message,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
gui.logAction(gui.Tr.Actions.SetBranchUpstream)
|
gui.c.LogAction(gui.c.Tr.Actions.SetBranchUpstream)
|
||||||
if err := gui.Git.Branch.SetUpstream(selectedBranch.RemoteName, selectedBranch.Name, checkedOutBranch.Name); err != nil {
|
if err := gui.git.Branch.SetUpstream(selectedBranch.RemoteName, selectedBranch.Name, checkedOutBranch.Name); err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
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 nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.createResetMenu(selectedBranch.FullName())
|
return gui.refHelper.CreateGitResetMenu(selectedBranch.FullName())
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"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/style"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// list panel functions
|
// list panel functions
|
||||||
@ -42,9 +40,9 @@ func (gui *Gui) remotesRenderToMain() error {
|
|||||||
func (gui *Gui) refreshRemotes() error {
|
func (gui *Gui) refreshRemotes() error {
|
||||||
prevSelectedRemote := gui.getSelectedRemote()
|
prevSelectedRemote := gui.getSelectedRemote()
|
||||||
|
|
||||||
remotes, err := gui.Git.Loaders.Remotes.GetRemotes()
|
remotes, err := gui.git.Loaders.Remotes.GetRemotes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.State.Remotes = remotes
|
gui.State.Remotes = remotes
|
||||||
@ -59,133 +57,5 @@ func (gui *Gui) refreshRemotes() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.postRefreshUpdate(gui.mustContextForContextKey(ContextKey(gui.Views.Branches.Context)))
|
return gui.c.PostRefreshUpdate(gui.mustContextForContextKey(types.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}})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -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()
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ func (gui *Gui) handleSearch() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, int) 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 {
|
return func(y int, index int, total int) error {
|
||||||
if total == 0 {
|
if total == 0 {
|
||||||
|
@ -28,16 +28,16 @@ func (gui *Gui) refreshStagingPanel(forceSecondaryFocused bool, selectedLineIdx
|
|||||||
}
|
}
|
||||||
|
|
||||||
if secondaryFocused {
|
if secondaryFocused {
|
||||||
gui.Views.Main.Title = gui.Tr.StagedChanges
|
gui.Views.Main.Title = gui.c.Tr.StagedChanges
|
||||||
gui.Views.Secondary.Title = gui.Tr.UnstagedChanges
|
gui.Views.Secondary.Title = gui.c.Tr.UnstagedChanges
|
||||||
} else {
|
} else {
|
||||||
gui.Views.Main.Title = gui.Tr.UnstagedChanges
|
gui.Views.Main.Title = gui.c.Tr.UnstagedChanges
|
||||||
gui.Views.Secondary.Title = gui.Tr.StagedChanges
|
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
|
// 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)
|
diff := gui.git.WorkingTree.WorktreeFileDiff(file, true, secondaryFocused, false)
|
||||||
secondaryDiff := 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
|
// 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
|
// 4-5 lines in which case we'll swap panels
|
||||||
@ -97,7 +97,7 @@ func (gui *Gui) handleTogglePanel() error {
|
|||||||
func (gui *Gui) handleStagingEscape() error {
|
func (gui *Gui) handleStagingEscape() error {
|
||||||
gui.escapeLineByLinePanel()
|
gui.escapeLineByLinePanel()
|
||||||
|
|
||||||
return gui.pushContext(gui.State.Contexts.Files)
|
return gui.c.PushContext(gui.State.Contexts.Files)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleToggleStagedSelection() error {
|
func (gui *Gui) handleToggleStagedSelection() error {
|
||||||
@ -113,10 +113,10 @@ func (gui *Gui) handleResetSelection() error {
|
|||||||
return gui.applySelection(true, state)
|
return gui.applySelection(true, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !gui.UserConfig.Gui.SkipUnstageLineWarning {
|
if !gui.c.UserConfig.Gui.SkipUnstageLineWarning {
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.UnstageLinesTitle,
|
Title: gui.c.Tr.UnstageLinesTitle,
|
||||||
Prompt: gui.Tr.UnstageLinesPrompt,
|
Prompt: gui.c.Tr.UnstageLinesPrompt,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
|
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
|
||||||
return gui.applySelection(true, state)
|
return gui.applySelection(true, state)
|
||||||
@ -148,17 +148,17 @@ func (gui *Gui) applySelection(reverse bool, state *LblPanelState) error {
|
|||||||
if !reverse || state.SecondaryFocused {
|
if !reverse || state.SecondaryFocused {
|
||||||
applyFlags = append(applyFlags, "cached")
|
applyFlags = append(applyFlags, "cached")
|
||||||
}
|
}
|
||||||
gui.logAction(gui.Tr.Actions.ApplyPatch)
|
gui.c.LogAction(gui.c.Tr.Actions.ApplyPatch)
|
||||||
err := gui.Git.WorkingTree.ApplyPatch(patch, applyFlags...)
|
err := gui.git.WorkingTree.ApplyPatch(patch, applyFlags...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.SelectingRange() {
|
if state.SelectingRange() {
|
||||||
state.SetLineSelectMode()
|
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
|
return err
|
||||||
}
|
}
|
||||||
if err := gui.refreshStagingPanel(false, -1); err != nil {
|
if err := gui.refreshStagingPanel(false, -1); err != nil {
|
||||||
|
@ -2,6 +2,7 @@ package gui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"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/popup"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
)
|
)
|
||||||
@ -21,9 +22,9 @@ func (gui *Gui) stashRenderToMain() error {
|
|||||||
var task updateTask
|
var task updateTask
|
||||||
stashEntry := gui.getSelectedStashEntry()
|
stashEntry := gui.getSelectedStashEntry()
|
||||||
if stashEntry == nil {
|
if stashEntry == nil {
|
||||||
task = NewRenderStringTask(gui.Tr.NoStashEntries)
|
task = NewRenderStringTask(gui.c.Tr.NoStashEntries)
|
||||||
} else {
|
} else {
|
||||||
task = NewRunPtyTask(gui.Git.Stash.ShowStashEntryCmdObj(stashEntry.Index).GetCmd())
|
task = NewRunPtyTask(gui.git.Stash.ShowStashEntryCmdObj(stashEntry.Index).GetCmd())
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.refreshMainViews(refreshMainOpts{
|
return gui.refreshMainViews(refreshMainOpts{
|
||||||
@ -35,7 +36,7 @@ func (gui *Gui) stashRenderToMain() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) refreshStashEntries() 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())
|
GetStashEntries(gui.State.Modes.Filtering.GetPath())
|
||||||
|
|
||||||
return gui.postRefreshUpdate(gui.State.Contexts.Stash)
|
return gui.postRefreshUpdate(gui.State.Contexts.Stash)
|
||||||
@ -49,14 +50,14 @@ func (gui *Gui) handleStashApply() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
skipStashWarning := gui.UserConfig.Gui.SkipStashWarning
|
skipStashWarning := gui.c.UserConfig.Gui.SkipStashWarning
|
||||||
|
|
||||||
apply := func() error {
|
apply := func() error {
|
||||||
gui.logAction(gui.Tr.Actions.Stash)
|
gui.c.LogAction(gui.c.Tr.Actions.Stash)
|
||||||
err := gui.Git.Stash.Apply(stashEntry.Index)
|
err := gui.git.Stash.Apply(stashEntry.Index)
|
||||||
_ = gui.postStashRefresh()
|
_ = gui.postStashRefresh()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -65,9 +66,9 @@ func (gui *Gui) handleStashApply() error {
|
|||||||
return apply()
|
return apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.StashApply,
|
Title: gui.c.Tr.StashApply,
|
||||||
Prompt: gui.Tr.SureApplyStashEntry,
|
Prompt: gui.c.Tr.SureApplyStashEntry,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
return apply()
|
return apply()
|
||||||
},
|
},
|
||||||
@ -80,14 +81,14 @@ func (gui *Gui) handleStashPop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
skipStashWarning := gui.UserConfig.Gui.SkipStashWarning
|
skipStashWarning := gui.c.UserConfig.Gui.SkipStashWarning
|
||||||
|
|
||||||
pop := func() error {
|
pop := func() error {
|
||||||
gui.logAction(gui.Tr.Actions.Stash)
|
gui.c.LogAction(gui.c.Tr.Actions.Stash)
|
||||||
err := gui.Git.Stash.Pop(stashEntry.Index)
|
err := gui.git.Stash.Pop(stashEntry.Index)
|
||||||
_ = gui.postStashRefresh()
|
_ = gui.postStashRefresh()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -96,9 +97,9 @@ func (gui *Gui) handleStashPop() error {
|
|||||||
return pop()
|
return pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.StashPop,
|
Title: gui.c.Tr.StashPop,
|
||||||
Prompt: gui.Tr.SurePopStashEntry,
|
Prompt: gui.c.Tr.SurePopStashEntry,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
return pop()
|
return pop()
|
||||||
},
|
},
|
||||||
@ -111,15 +112,15 @@ func (gui *Gui) handleStashDrop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.StashDrop,
|
Title: gui.c.Tr.StashDrop,
|
||||||
Prompt: gui.Tr.SureDropStashEntry,
|
Prompt: gui.c.Tr.SureDropStashEntry,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
gui.logAction(gui.Tr.Actions.Stash)
|
gui.c.LogAction(gui.c.Tr.Actions.Stash)
|
||||||
err := gui.Git.Stash.Drop(stashEntry.Index)
|
err := gui.git.Stash.Drop(stashEntry.Index)
|
||||||
_ = gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{STASH}})
|
_ = gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -127,25 +128,7 @@ func (gui *Gui) handleStashDrop() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) postStashRefresh() error {
|
func (gui *Gui) postStashRefresh() error {
|
||||||
return gui.refreshSidePanels(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}})
|
return gui.c.Refresh(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
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleViewStashFiles() error {
|
func (gui *Gui) handleViewStashFiles() error {
|
||||||
@ -154,5 +137,10 @@ func (gui *Gui) handleViewStashFiles() error {
|
|||||||
return nil
|
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()
|
gui.Mutexes.RefreshingStatusMutex.Lock()
|
||||||
defer gui.Mutexes.RefreshingStatusMutex.Unlock()
|
defer gui.Mutexes.RefreshingStatusMutex.Unlock()
|
||||||
|
|
||||||
currentBranch := gui.currentBranch()
|
currentBranch := gui.getCheckedOutBranch()
|
||||||
if currentBranch == nil {
|
if currentBranch == nil {
|
||||||
// need to wait for branches to refresh
|
// need to wait for branches to refresh
|
||||||
return
|
return
|
||||||
@ -29,7 +29,7 @@ func (gui *Gui) refreshStatus() {
|
|||||||
status += presentation.ColoredBranchStatus(currentBranch) + " "
|
status += presentation.ColoredBranchStatus(currentBranch) + " "
|
||||||
}
|
}
|
||||||
|
|
||||||
workingTreeState := gui.Git.Status.WorkingTreeState()
|
workingTreeState := gui.git.Status.WorkingTreeState()
|
||||||
if workingTreeState != enums.REBASE_MODE_NONE {
|
if workingTreeState != enums.REBASE_MODE_NONE {
|
||||||
status += style.FgYellow.Sprintf("(%s) ", formatWorkingTreeState(workingTreeState))
|
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 {
|
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)
|
gui.Updater.CheckForNewUpdate(gui.onUserUpdateCheckFinish, true)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -62,20 +62,20 @@ func (gui *Gui) handleStatusClick() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
currentBranch := gui.currentBranch()
|
currentBranch := gui.getCheckedOutBranch()
|
||||||
if currentBranch == nil {
|
if currentBranch == nil {
|
||||||
// need to wait for branches to refresh
|
// need to wait for branches to refresh
|
||||||
return nil
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cx, _ := gui.Views.Status.Cursor()
|
cx, _ := gui.Views.Status.Cursor()
|
||||||
upstreamStatus := presentation.BranchStatus(currentBranch)
|
upstreamStatus := presentation.BranchStatus(currentBranch)
|
||||||
repoName := utils.GetCurrentRepoName()
|
repoName := utils.GetCurrentRepoName()
|
||||||
workingTreeState := gui.Git.Status.WorkingTreeState()
|
workingTreeState := gui.git.Status.WorkingTreeState()
|
||||||
switch workingTreeState {
|
switch workingTreeState {
|
||||||
case enums.REBASE_MODE_REBASING, enums.REBASE_MODE_MERGING:
|
case enums.REBASE_MODE_REBASING, enums.REBASE_MODE_MERGING:
|
||||||
workingTreeStatus := fmt.Sprintf("(%s)", formatWorkingTreeState(workingTreeState))
|
workingTreeStatus := fmt.Sprintf("(%s)", formatWorkingTreeState(workingTreeState))
|
||||||
@ -135,7 +135,7 @@ func (gui *Gui) askForConfigFile(action func(file string) error) error {
|
|||||||
confPaths := gui.Config.GetUserConfigPaths()
|
confPaths := gui.Config.GetUserConfigPaths()
|
||||||
switch len(confPaths) {
|
switch len(confPaths) {
|
||||||
case 0:
|
case 0:
|
||||||
return errors.New(gui.Tr.NoConfigFileFoundErr)
|
return errors.New(gui.c.Tr.NoConfigFileFoundErr)
|
||||||
case 1:
|
case 1:
|
||||||
return action(confPaths[0])
|
return action(confPaths[0])
|
||||||
default:
|
default:
|
||||||
@ -149,8 +149,8 @@ func (gui *Gui) askForConfigFile(action func(file string) error) error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return gui.PopupHandler.Menu(popup.CreateMenuOptions{
|
return gui.c.Menu(popup.CreateMenuOptions{
|
||||||
Title: gui.Tr.SelectConfigFile,
|
Title: gui.c.Tr.SelectConfigFile,
|
||||||
Items: menuItems,
|
Items: menuItems,
|
||||||
HideCancel: true,
|
HideCancel: true,
|
||||||
})
|
})
|
||||||
@ -158,11 +158,11 @@ func (gui *Gui) askForConfigFile(action func(file string) error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleOpenConfig() error {
|
func (gui *Gui) handleOpenConfig() error {
|
||||||
return gui.askForConfigFile(gui.openFile)
|
return gui.askForConfigFile(gui.fileHelper.OpenFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleEditConfig() error {
|
func (gui *Gui) handleEditConfig() error {
|
||||||
return gui.askForConfigFile(gui.editFile)
|
return gui.askForConfigFile(gui.fileHelper.EditFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func lazygitTitle() string {
|
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",
|
"\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{New().SetFg(rgbYellow), BgRed},
|
||||||
TextStyle{
|
TextStyle{
|
||||||
fg: &rgbYellow,
|
fg: &rgbYellow,
|
||||||
@ -147,6 +147,19 @@ func TestMerge(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"\x1b[38;2;255;255;0;48;2;197;30;20mfoo\x1b[0m",
|
"\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 {
|
for _, s := range scenarios {
|
||||||
|
@ -3,7 +3,9 @@ package gui
|
|||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"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/popup"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// list panel functions
|
// list panel functions
|
||||||
@ -24,7 +26,7 @@ func (gui *Gui) subCommitsRenderToMain() error {
|
|||||||
if commit == nil {
|
if commit == nil {
|
||||||
task = NewRenderStringTask("No commits")
|
task = NewRenderStringTask("No commits")
|
||||||
} else {
|
} 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())
|
task = NewRunPtyTask(cmdObj.GetCmd())
|
||||||
}
|
}
|
||||||
@ -43,19 +45,19 @@ func (gui *Gui) handleCheckoutSubCommit() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := gui.PopupHandler.Ask(popup.AskOpts{
|
err := gui.c.Ask(popup.AskOpts{
|
||||||
Title: gui.Tr.LcCheckoutCommit,
|
Title: gui.c.Tr.LcCheckoutCommit,
|
||||||
Prompt: gui.Tr.SureCheckoutThisCommit,
|
Prompt: gui.c.Tr.SureCheckoutThisCommit,
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
gui.logAction(gui.Tr.Actions.CheckoutCommit)
|
gui.c.LogAction(gui.c.Tr.Actions.CheckoutCommit)
|
||||||
return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{})
|
return gui.refHelper.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.State.Panels.SubCommits.SelectedLineIdx = 0
|
gui.State.Contexts.SubCommits.GetPanelState().SetSelectedLineIdx(0)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -63,7 +65,7 @@ func (gui *Gui) handleCheckoutSubCommit() error {
|
|||||||
func (gui *Gui) handleCreateSubCommitResetMenu() error {
|
func (gui *Gui) handleCreateSubCommitResetMenu() error {
|
||||||
commit := gui.getSelectedSubCommit()
|
commit := gui.getSelectedSubCommit()
|
||||||
|
|
||||||
return gui.createResetMenu(commit.Sha)
|
return gui.refHelper.CreateGitResetMenu(commit.Sha)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleViewSubCommitFiles() error {
|
func (gui *Gui) handleViewSubCommitFiles() error {
|
||||||
@ -72,12 +74,17 @@ func (gui *Gui) handleViewSubCommitFiles() error {
|
|||||||
return nil
|
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 {
|
func (gui *Gui) switchToSubCommitsContext(refName string) error {
|
||||||
// need to populate my sub commits
|
// need to populate my sub commits
|
||||||
commits, err := gui.Git.Loaders.Commits.GetCommits(
|
commits, err := gui.git.Loaders.Commits.GetCommits(
|
||||||
loaders.GetCommitsOptions{
|
loaders.GetCommitsOptions{
|
||||||
Limit: gui.State.Panels.Commits.LimitCommits,
|
Limit: gui.State.Panels.Commits.LimitCommits,
|
||||||
FilterPath: gui.State.Modes.Filtering.GetPath(),
|
FilterPath: gui.State.Modes.Filtering.GetPath(),
|
||||||
@ -91,10 +98,10 @@ func (gui *Gui) switchToSubCommitsContext(refName string) error {
|
|||||||
|
|
||||||
gui.State.SubCommits = commits
|
gui.State.SubCommits = commits
|
||||||
gui.State.Panels.SubCommits.refName = refName
|
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())
|
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 {
|
func (gui *Gui) handleSwitchToSubCommits() error {
|
||||||
|
@ -30,11 +30,11 @@ func (gui *Gui) submodulesRenderToMain() error {
|
|||||||
style.FgCyan.Sprint(submodule.Url),
|
style.FgCyan.Sprint(submodule.Url),
|
||||||
)
|
)
|
||||||
|
|
||||||
file := gui.fileForSubmodule(submodule)
|
file := gui.workingTreeHelper.FileForSubmodule(submodule)
|
||||||
if file == nil {
|
if file == nil {
|
||||||
task = NewRenderStringTask(prefix)
|
task = NewRenderStringTask(prefix)
|
||||||
} else {
|
} 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)
|
task = NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,7 +48,7 @@ func (gui *Gui) submodulesRenderToMain() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) refreshStateSubmoduleConfigs() error {
|
func (gui *Gui) refreshStateSubmoduleConfigs() error {
|
||||||
configs, err := gui.Git.Submodule.GetConfigs()
|
configs, err := gui.git.Submodule.GetConfigs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/controllers"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"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
|
// finding suggestions in this file, so that it's easy to see if a function already
|
||||||
// exists for fetching a particular model.
|
// exists for fetching a particular model.
|
||||||
|
|
||||||
func (gui *Gui) getRemoteNames() []string {
|
type SuggestionsHelper struct {
|
||||||
result := make([]string, len(gui.State.Remotes))
|
c *controllers.ControllerCommon
|
||||||
for i, remote := range gui.State.Remotes {
|
|
||||||
|
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
|
result[i] = remote.Name
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@ -40,22 +62,22 @@ func matchesToSuggestions(matches []string) []*types.Suggestion {
|
|||||||
return suggestions
|
return suggestions
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getRemoteSuggestionsFunc() func(string) []*types.Suggestion {
|
func (self *SuggestionsHelper) GetRemoteSuggestionsFunc() func(string) []*types.Suggestion {
|
||||||
remoteNames := gui.getRemoteNames()
|
remoteNames := self.getRemoteNames()
|
||||||
|
|
||||||
return fuzzySearchFunc(remoteNames)
|
return fuzzySearchFunc(remoteNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getBranchNames() []string {
|
func (self *SuggestionsHelper) getBranchNames() []string {
|
||||||
result := make([]string, len(gui.State.Branches))
|
result := make([]string, len(self.State.Branches))
|
||||||
for i, branch := range gui.State.Branches {
|
for i, branch := range self.State.Branches {
|
||||||
result[i] = branch.Name
|
result[i] = branch.Name
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getBranchNameSuggestionsFunc() func(string) []*types.Suggestion {
|
func (self *SuggestionsHelper) GetBranchNameSuggestionsFunc() func(string) []*types.Suggestion {
|
||||||
branchNames := gui.getBranchNames()
|
branchNames := self.getBranchNames()
|
||||||
|
|
||||||
return func(input string) []*types.Suggestion {
|
return func(input string) []*types.Suggestion {
|
||||||
var matchingBranchNames []string
|
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
|
// 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
|
// self.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. 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.
|
// 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
|
// 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
|
// if nothing has been typed because there'll be too much to display efficiently
|
||||||
func (gui *Gui) getFilePathSuggestionsFunc() func(string) []*types.Suggestion {
|
func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*types.Suggestion {
|
||||||
_ = gui.PopupHandler.WithWaitingStatus(gui.Tr.LcLoadingFileSuggestions, func() error {
|
_ = self.c.WithWaitingStatus(self.c.Tr.LcLoadingFileSuggestions, func() error {
|
||||||
trie := patricia.NewTrie()
|
trie := patricia.NewTrie()
|
||||||
// load every non-gitignored file in the repo
|
// load every non-gitignored file in the repo
|
||||||
ignore, err := gitignore.FromGit()
|
ignore, err := gitignore.FromGit()
|
||||||
@ -101,22 +123,16 @@ func (gui *Gui) getFilePathSuggestionsFunc() func(string) []*types.Suggestion {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
// cache the trie for future use
|
// cache the trie for future use
|
||||||
gui.State.FilesTrie = trie
|
self.State.FilesTrie = trie
|
||||||
|
|
||||||
// refresh the selections view
|
self.refreshSuggestionsFn()
|
||||||
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) }
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
return func(input string) []*types.Suggestion {
|
return func(input string) []*types.Suggestion {
|
||||||
matchingNames := []string{}
|
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))
|
matchingNames = append(matchingNames, item.(string))
|
||||||
return nil
|
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{}
|
result := []string{}
|
||||||
for _, remote := range gui.State.Remotes {
|
for _, remote := range self.State.Remotes {
|
||||||
for _, branch := range remote.Branches {
|
for _, branch := range remote.Branches {
|
||||||
result = append(result, fmt.Sprintf("%s%s%s", remote.Name, separator, branch.Name))
|
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
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getRemoteBranchesSuggestionsFunc(separator string) func(string) []*types.Suggestion {
|
func (self *SuggestionsHelper) GetRemoteBranchesSuggestionsFunc(separator string) func(string) []*types.Suggestion {
|
||||||
return fuzzySearchFunc(gui.getRemoteBranchNames(separator))
|
return fuzzySearchFunc(self.getRemoteBranchNames(separator))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getTagNames() []string {
|
func (self *SuggestionsHelper) getTagNames() []string {
|
||||||
result := make([]string, len(gui.State.Tags))
|
result := make([]string, len(self.State.Tags))
|
||||||
for i, tag := range gui.State.Tags {
|
for i, tag := range self.State.Tags {
|
||||||
result[i] = tag.Name
|
result[i] = tag.Name
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getRefsSuggestionsFunc() func(string) []*types.Suggestion {
|
func (self *SuggestionsHelper) GetRefsSuggestionsFunc() func(string) []*types.Suggestion {
|
||||||
remoteBranchNames := gui.getRemoteBranchNames("/")
|
remoteBranchNames := self.getRemoteBranchNames("/")
|
||||||
localBranchNames := gui.getBranchNames()
|
localBranchNames := self.getBranchNames()
|
||||||
tagNames := gui.getTagNames()
|
tagNames := self.getTagNames()
|
||||||
additionalRefNames := []string{"HEAD", "FETCH_HEAD", "MERGE_HEAD", "ORIG_HEAD"}
|
additionalRefNames := []string{"HEAD", "FETCH_HEAD", "MERGE_HEAD", "ORIG_HEAD"}
|
||||||
|
|
||||||
refNames := append(append(append(remoteBranchNames, localBranchNames...), tagNames...), additionalRefNames...)
|
refNames := append(append(append(remoteBranchNames, localBranchNames...), tagNames...), additionalRefNames...)
|
||||||
@ -169,9 +185,9 @@ func (gui *Gui) getRefsSuggestionsFunc() func(string) []*types.Suggestion {
|
|||||||
return fuzzySearchFunc(refNames)
|
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
|
// 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)
|
return fuzzySearchFunc(history)
|
||||||
}
|
}
|
@ -2,36 +2,28 @@ package gui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"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 {
|
func (self *Gui) getSelectedTag() *models.Tag {
|
||||||
selectedLine := gui.State.Panels.Tags.SelectedLineIdx
|
selectedLine := self.State.Panels.Tags.SelectedLineIdx
|
||||||
if selectedLine == -1 || len(gui.State.Tags) == 0 {
|
if selectedLine == -1 || len(self.State.Tags) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.State.Tags[selectedLine]
|
return self.State.Tags[selectedLine]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCreateTag() error {
|
func (self *Gui) tagsRenderToMain() 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 {
|
|
||||||
var task updateTask
|
var task updateTask
|
||||||
tag := gui.getSelectedTag()
|
tag := self.getSelectedTag()
|
||||||
if tag == nil {
|
if tag == nil {
|
||||||
task = NewRenderStringTask("No tags")
|
task = NewRenderStringTask("No tags")
|
||||||
} else {
|
} else {
|
||||||
cmdObj := gui.Git.Branch.GetGraphCmdObj(tag.Name)
|
cmdObj := self.git.Branch.GetGraphCmdObj(tag.Name)
|
||||||
task = NewRunCommandTask(cmdObj.GetCmd())
|
task = NewRunCommandTask(cmdObj.GetCmd())
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.refreshMainViews(refreshMainOpts{
|
return self.refreshMainViews(refreshMainOpts{
|
||||||
main: &viewUpdateOpts{
|
main: &viewUpdateOpts{
|
||||||
title: "Tag",
|
title: "Tag",
|
||||||
task: task,
|
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.
|
// 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 {
|
func (self *Gui) refreshTags() error {
|
||||||
tags, err := gui.Git.Loaders.Tags.GetTags()
|
tags, err := self.git.Loaders.Tags.GetTags()
|
||||||
if err != nil {
|
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)
|
return self.postRefreshUpdate(self.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)
|
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
func (gui *Gui) newCmdTask(view *gocui.View, cmd *exec.Cmd, prefix string) error {
|
func (gui *Gui) newCmdTask(view *gocui.View, cmd *exec.Cmd, prefix string) error {
|
||||||
cmdStr := strings.Join(cmd.Args, " ")
|
cmdStr := strings.Join(cmd.Args, " ")
|
||||||
gui.Log.WithField(
|
gui.c.Log.WithField(
|
||||||
"command",
|
"command",
|
||||||
cmdStr,
|
cmdStr,
|
||||||
).Debug("RunCommand")
|
).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) {
|
start := func() (*exec.Cmd, io.Reader) {
|
||||||
r, err := cmd.StdoutPipe()
|
r, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gui.Log.Warn(err)
|
gui.c.Log.Warn(err)
|
||||||
}
|
}
|
||||||
cmd.Stderr = cmd.Stdout
|
cmd.Stderr = cmd.Stdout
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
gui.Log.Warn(err)
|
gui.c.Log.Warn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd, r
|
return cmd, r
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := manager.NewTask(manager.NewCmdTask(start, prefix, height+oy+10, nil), cmdStr); err != nil {
|
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
|
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
|
Tag string // e.g. 'navigation'. Used for grouping things in the cheatsheet
|
||||||
OpensMenu bool
|
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 (
|
const (
|
||||||
COMMITS RefreshableView = iota
|
COMMITS RefreshableView = iota
|
||||||
|
REBASE_COMMITS
|
||||||
BRANCHES
|
BRANCHES
|
||||||
FILES
|
FILES
|
||||||
STASH
|
STASH
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (gui *Gui) showUpdatePrompt(newVersion string) error {
|
func (gui *Gui) showUpdatePrompt(newVersion string) error {
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: "New version available!",
|
Title: "New version available!",
|
||||||
Prompt: fmt.Sprintf("Download version %s? (enter/esc)", newVersion),
|
Prompt: fmt.Sprintf("Download version %s? (enter/esc)", newVersion),
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
@ -20,10 +20,10 @@ func (gui *Gui) showUpdatePrompt(newVersion string) error {
|
|||||||
|
|
||||||
func (gui *Gui) onUserUpdateCheckFinish(newVersion string, err error) error {
|
func (gui *Gui) onUserUpdateCheckFinish(newVersion string, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.Error(err)
|
return gui.c.Error(err)
|
||||||
}
|
}
|
||||||
if newVersion == "" {
|
if newVersion == "" {
|
||||||
return gui.PopupHandler.ErrorMsg("New version not found")
|
return gui.c.ErrorMsg("New version not found")
|
||||||
}
|
}
|
||||||
return gui.showUpdatePrompt(newVersion)
|
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 {
|
func (gui *Gui) onBackgroundUpdateCheckFinish(newVersion string, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// ignoring the error for now so that I'm not annoying users
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
if newVersion == "" {
|
if newVersion == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if gui.UserConfig.Update.Method == "background" {
|
if gui.c.UserConfig.Update.Method == "background" {
|
||||||
gui.startUpdating(newVersion)
|
gui.startUpdating(newVersion)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ func (gui *Gui) onUpdateFinish(statusId int, err error) error {
|
|||||||
gui.OnUIThread(func() error {
|
gui.OnUIThread(func() error {
|
||||||
_ = gui.renderString(gui.Views.AppStatus, "")
|
_ = gui.renderString(gui.Views.AppStatus, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.PopupHandler.ErrorMsg("Update failed: " + err.Error())
|
return gui.c.ErrorMsg("Update failed: " + err.Error())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -65,7 +65,7 @@ func (gui *Gui) onUpdateFinish(statusId int, err error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) createUpdateQuitConfirmation() error {
|
func (gui *Gui) createUpdateQuitConfirmation() error {
|
||||||
return gui.PopupHandler.Ask(popup.AskOpts{
|
return gui.c.Ask(popup.AskOpts{
|
||||||
Title: "Currently Updating",
|
Title: "Currently Updating",
|
||||||
Prompt: "An update is in progress. Are you sure you want to quit?",
|
Prompt: "An update is in progress. Are you sure you want to quit?",
|
||||||
HandleConfirm: func() error {
|
HandleConfirm: func() error {
|
||||||
|
@ -59,14 +59,14 @@ func arrToMap(arr []types.RefreshableView) map[types.RefreshableView]bool {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) refreshSidePanels(options types.RefreshOptions) error {
|
func (gui *Gui) Refresh(options types.RefreshOptions) error {
|
||||||
if options.Scope == nil {
|
if options.Scope == nil {
|
||||||
gui.Log.Infof(
|
gui.c.Log.Infof(
|
||||||
"refreshing all scopes in %s mode",
|
"refreshing all scopes in %s mode",
|
||||||
getModeName(options.Mode),
|
getModeName(options.Mode),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
gui.Log.Infof(
|
gui.c.Log.Infof(
|
||||||
"refreshing the following scopes in %s mode: %s",
|
"refreshing the following scopes in %s mode: %s",
|
||||||
getModeName(options.Mode),
|
getModeName(options.Mode),
|
||||||
strings.Join(getScopeNames(options.Scope), ","),
|
strings.Join(getScopeNames(options.Scope), ","),
|
||||||
@ -78,69 +78,55 @@ func (gui *Gui) refreshSidePanels(options types.RefreshOptions) error {
|
|||||||
f := func() {
|
f := func() {
|
||||||
var scopeMap map[types.RefreshableView]bool
|
var scopeMap map[types.RefreshableView]bool
|
||||||
if len(options.Scope) == 0 {
|
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 {
|
} else {
|
||||||
scopeMap = arrToMap(options.Scope)
|
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)
|
wg.Add(1)
|
||||||
func() {
|
func() {
|
||||||
if options.Mode == types.ASYNC {
|
if options.Mode == types.ASYNC {
|
||||||
go utils.Safe(func() { gui.refreshCommits() })
|
go utils.Safe(f)
|
||||||
} else {
|
} else {
|
||||||
gui.refreshCommits()
|
f()
|
||||||
}
|
}
|
||||||
wg.Done()
|
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] {
|
if scopeMap[types.FILES] || scopeMap[types.SUBMODULES] {
|
||||||
wg.Add(1)
|
refresh(func() { _ = gui.refreshFilesAndSubmodules() })
|
||||||
func() {
|
|
||||||
if options.Mode == types.ASYNC {
|
|
||||||
go utils.Safe(func() { _ = gui.refreshFilesAndSubmodules() })
|
|
||||||
} else {
|
|
||||||
_ = gui.refreshFilesAndSubmodules()
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if scopeMap[types.STASH] {
|
if scopeMap[types.STASH] {
|
||||||
wg.Add(1)
|
refresh(func() { _ = gui.refreshStashEntries() })
|
||||||
func() {
|
|
||||||
if options.Mode == types.ASYNC {
|
|
||||||
go utils.Safe(func() { _ = gui.refreshStashEntries() })
|
|
||||||
} else {
|
|
||||||
_ = gui.refreshStashEntries()
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if scopeMap[types.TAGS] {
|
if scopeMap[types.TAGS] {
|
||||||
wg.Add(1)
|
refresh(func() { _ = gui.refreshTags() })
|
||||||
func() {
|
|
||||||
if options.Mode == types.ASYNC {
|
|
||||||
go utils.Safe(func() { _ = gui.refreshTags() })
|
|
||||||
} else {
|
|
||||||
_ = gui.refreshTags()
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if scopeMap[types.REMOTES] {
|
if scopeMap[types.REMOTES] {
|
||||||
wg.Add(1)
|
refresh(func() { _ = gui.refreshRemotes() })
|
||||||
func() {
|
|
||||||
if options.Mode == types.ASYNC {
|
|
||||||
go utils.Safe(func() { _ = gui.refreshRemotes() })
|
|
||||||
} else {
|
|
||||||
_ = gui.refreshRemotes()
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@ -234,7 +220,7 @@ func (gui *Gui) resizePopupPanel(v *gocui.View, content string) error {
|
|||||||
return err
|
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
|
// TODO: find out why we're doing this
|
||||||
line := panelState.GetSelectedLineIdx()
|
line := panelState.GetSelectedLineIdx()
|
||||||
|
|
||||||
@ -253,7 +239,7 @@ func (gui *Gui) changeSelectedLine(panelState IListPanelState, total int, change
|
|||||||
panelState.SetSelectedLineIdx(newLine)
|
panelState.SetSelectedLineIdx(newLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) refreshSelectedLine(panelState IListPanelState, total int) {
|
func (gui *Gui) refreshSelectedLine(panelState types.IListPanelState, total int) {
|
||||||
line := panelState.GetSelectedLineIdx()
|
line := panelState.GetSelectedLineIdx()
|
||||||
|
|
||||||
if line == -1 && total > 0 {
|
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 {
|
func (gui *Gui) globalOptionsMap() map[string]string {
|
||||||
keybindingConfig := gui.UserConfig.Keybinding
|
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||||
|
|
||||||
return map[string]string{
|
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", 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.Tr.LcNavigate,
|
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.Tr.LcCancel,
|
gui.getKeyDisplay(keybindingConfig.Universal.Return): gui.c.Tr.LcCancel,
|
||||||
gui.getKeyDisplay(keybindingConfig.Universal.Quit): gui.Tr.LcQuit,
|
gui.getKeyDisplay(keybindingConfig.Universal.Quit): gui.c.Tr.LcQuit,
|
||||||
gui.getKeyDisplay(keybindingConfig.Universal.OptionMenu): gui.Tr.LcMenu,
|
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.Tr.LcJump,
|
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.Tr.LcScrollLeftRight,
|
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 {
|
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 {
|
func (gui *Gui) handleNextTab() error {
|
||||||
|
@ -3,11 +3,11 @@ package gui
|
|||||||
func (gui *Gui) toggleWhitespaceInDiffView() error {
|
func (gui *Gui) toggleWhitespaceInDiffView() error {
|
||||||
gui.IgnoreWhitespaceInDiffView = !gui.IgnoreWhitespaceInDiffView
|
gui.IgnoreWhitespaceInDiffView = !gui.IgnoreWhitespaceInDiffView
|
||||||
|
|
||||||
toastMessage := gui.Tr.ShowingWhitespaceInDiffView
|
toastMessage := gui.c.Tr.ShowingWhitespaceInDiffView
|
||||||
if gui.IgnoreWhitespaceInDiffView {
|
if gui.IgnoreWhitespaceInDiffView {
|
||||||
toastMessage = gui.Tr.IgnoringWhitespaceInDiffView
|
toastMessage = gui.c.Tr.IgnoringWhitespaceInDiffView
|
||||||
}
|
}
|
||||||
gui.raiseToast(toastMessage)
|
gui.c.Toast(toastMessage)
|
||||||
|
|
||||||
return gui.refreshFilesAndSubmodules()
|
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