mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-12 08:36:25 -04:00
Merge branch 'master' into feat/detect-bare-repo
This commit is contained in:
commit
d072b0c75e
|
|
@ -18,7 +18,7 @@ A simple terminal UI for git commands, written in Go with the [gocui](https://gi
|
|||
## Sponsors
|
||||
|
||||
<p align="center">
|
||||
Maintainence of this project is made possible by all the <a href="https://github.com/jesseduffield/lazygit/graphs/contributors">contributors</a> and <a href="https://github.com/sponsors/jesseduffield">sponsors</a>. If you'd like to sponsor this project and have your avatar or company logo appear below <a href="https://github.com/sponsors/jesseduffield">click here</a>. 💙
|
||||
Maintenance of this project is made possible by all the <a href="https://github.com/jesseduffield/lazygit/graphs/contributors">contributors</a> and <a href="https://github.com/sponsors/jesseduffield">sponsors</a>. If you'd like to sponsor this project and have your avatar or company logo appear below <a href="https://github.com/sponsors/jesseduffield">click here</a>. 💙
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
|
|
|||
|
|
@ -30,13 +30,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
## List Panel Navigation
|
||||
|
||||
<pre>
|
||||
<kbd>H</kbd>: scroll left
|
||||
<kbd>L</kbd>: scroll right
|
||||
<kbd>,</kbd>: previous page
|
||||
<kbd>.</kbd>: next page
|
||||
<kbd><</kbd>: scroll to top
|
||||
<kbd>/</kbd>: start search
|
||||
<kbd>></kbd>: scroll to bottom
|
||||
<kbd>H</kbd>: scroll left
|
||||
<kbd>L</kbd>: scroll right
|
||||
<kbd>]</kbd>: next tab
|
||||
<kbd>[</kbd>: previous tab
|
||||
</pre>
|
||||
|
|
@ -140,24 +140,24 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
## Main Panel (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick all hunks
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>◄</kbd>: select previous conflict
|
||||
<kbd>►</kbd>: select next conflict
|
||||
<kbd>▲</kbd>: select previous hunk
|
||||
<kbd>▼</kbd>: select next hunk
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>z</kbd>: undo
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick all hunks
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
</pre>
|
||||
|
||||
## Main Panel (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: scroll down (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: scroll up (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: scroll down (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main Panel (Patch Building)
|
||||
|
|
|
|||
|
|
@ -30,13 +30,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
## 一覧パネルの操作
|
||||
|
||||
<pre>
|
||||
<kbd>H</kbd>: 左スクロール
|
||||
<kbd>L</kbd>: 右スクロール
|
||||
<kbd>,</kbd>: 前のページ
|
||||
<kbd>.</kbd>: 次のページ
|
||||
<kbd><</kbd>: 最上部までスクロール
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
<kbd>></kbd>: 最下部までスクロール
|
||||
<kbd>H</kbd>: 左スクロール
|
||||
<kbd>L</kbd>: 右スクロール
|
||||
<kbd>]</kbd>: 次のタブ
|
||||
<kbd>[</kbd>: 前のタブ
|
||||
</pre>
|
||||
|
|
@ -199,24 +199,24 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
## メインパネル (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ファイル一覧に戻る
|
||||
<kbd>M</kbd>: git mergetoolを開く
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick all hunks
|
||||
<kbd>e</kbd>: ファイルを編集
|
||||
<kbd>o</kbd>: ファイルを開く
|
||||
<kbd>◄</kbd>: 前のコンフリクトを選択
|
||||
<kbd>►</kbd>: 次のコンフリクトを選択
|
||||
<kbd>▲</kbd>: 前のhunkを選択
|
||||
<kbd>▼</kbd>: 次のhunkを選択
|
||||
<kbd>e</kbd>: ファイルを編集
|
||||
<kbd>o</kbd>: ファイルを開く
|
||||
<kbd>z</kbd>: アンドゥ
|
||||
<kbd>M</kbd>: git mergetoolを開く
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick all hunks
|
||||
<kbd>esc</kbd>: ファイル一覧に戻る
|
||||
</pre>
|
||||
|
||||
## メインパネル (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: 下にスクロール (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: 上にスクロール (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: 下にスクロール (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: 上にスクロール (fn+down)
|
||||
</pre>
|
||||
|
||||
## メインパネル (Patch Building)
|
||||
|
|
|
|||
|
|
@ -30,13 +30,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
## List Panel Navigation
|
||||
|
||||
<pre>
|
||||
<kbd>H</kbd>: 우 스크롤
|
||||
<kbd>L</kbd>: 좌 스크롤
|
||||
<kbd>,</kbd>: 이전 페이지
|
||||
<kbd>.</kbd>: 다음 페이지
|
||||
<kbd><</kbd>: 맨 위로 스크롤
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
<kbd>></kbd>: 맨 아래로 스크롤
|
||||
<kbd>H</kbd>: 우 스크롤
|
||||
<kbd>L</kbd>: 좌 스크롤
|
||||
<kbd>]</kbd>: 이전 탭
|
||||
<kbd>[</kbd>: 다음 탭
|
||||
</pre>
|
||||
|
|
@ -84,24 +84,24 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
## 메인 패널 (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: 파일 목록으로 돌아가기
|
||||
<kbd>M</kbd>: git mergetool를 열기
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick all hunks
|
||||
<kbd>e</kbd>: 파일 편집
|
||||
<kbd>o</kbd>: 파일 닫기
|
||||
<kbd>◄</kbd>: 이전 충돌을 선택
|
||||
<kbd>►</kbd>: 다음 충돌을 선택
|
||||
<kbd>▲</kbd>: 이전 hunk를 선택
|
||||
<kbd>▼</kbd>: 다음 hunk를 선택
|
||||
<kbd>e</kbd>: 파일 편집
|
||||
<kbd>o</kbd>: 파일 닫기
|
||||
<kbd>z</kbd>: 되돌리기
|
||||
<kbd>M</kbd>: git mergetool를 열기
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick all hunks
|
||||
<kbd>esc</kbd>: 파일 목록으로 돌아가기
|
||||
</pre>
|
||||
|
||||
## 메인 패널 (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: 아래로 스크롤 (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: 위로 스크롤 (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: 아래로 스크롤 (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: 위로 스크롤 (fn+down)
|
||||
</pre>
|
||||
|
||||
## 메인 패널 (Patch Building)
|
||||
|
|
|
|||
|
|
@ -30,13 +30,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
## Lijstpaneel Navigatie
|
||||
|
||||
<pre>
|
||||
<kbd>H</kbd>: scroll left
|
||||
<kbd>L</kbd>: scroll right
|
||||
<kbd>,</kbd>: vorige pagina
|
||||
<kbd>.</kbd>: volgende pagina
|
||||
<kbd><</kbd>: scroll naar boven
|
||||
<kbd>/</kbd>: start met zoeken
|
||||
<kbd>></kbd>: scroll naar beneden
|
||||
<kbd>H</kbd>: scroll left
|
||||
<kbd>L</kbd>: scroll right
|
||||
<kbd>]</kbd>: volgende tabblad
|
||||
<kbd>[</kbd>: vorige tabblad
|
||||
</pre>
|
||||
|
|
@ -140,24 +140,24 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
## Mergen
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd>space</kbd>: kies hunk
|
||||
<kbd>b</kbd>: kies bijde hunks
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>◄</kbd>: selecteer voorgaand conflict
|
||||
<kbd>►</kbd>: selecteer volgende conflict
|
||||
<kbd>▲</kbd>: selecteer bovenste hunk
|
||||
<kbd>▼</kbd>: selecteer onderste hunk
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>z</kbd>: ongedaan maken
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd>space</kbd>: kies hunk
|
||||
<kbd>b</kbd>: kies bijde hunks
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
</pre>
|
||||
|
||||
## Normaal
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: scroll omlaag (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: scroll omhoog (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: scroll omlaag (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: scroll omhoog (fn+down)
|
||||
</pre>
|
||||
|
||||
## Patch Bouwen
|
||||
|
|
|
|||
|
|
@ -30,13 +30,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
## List Panel Navigation
|
||||
|
||||
<pre>
|
||||
<kbd>H</kbd>: scroll left
|
||||
<kbd>L</kbd>: scroll right
|
||||
<kbd>,</kbd>: previous page
|
||||
<kbd>.</kbd>: next page
|
||||
<kbd><</kbd>: scroll to top
|
||||
<kbd>/</kbd>: start search
|
||||
<kbd>></kbd>: scroll to bottom
|
||||
<kbd>H</kbd>: scroll left
|
||||
<kbd>L</kbd>: scroll right
|
||||
<kbd>]</kbd>: next tab
|
||||
<kbd>[</kbd>: previous tab
|
||||
</pre>
|
||||
|
|
@ -211,17 +211,17 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
## Scalanie
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd>space</kbd>: wybierz kawałek
|
||||
<kbd>b</kbd>: wybierz wszystkie kawałki
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>◄</kbd>: poprzedni konflikt
|
||||
<kbd>►</kbd>: następny konflikt
|
||||
<kbd>▲</kbd>: wybierz poprzedni kawałek
|
||||
<kbd>▼</kbd>: wybierz następny kawałek
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>z</kbd>: cofnij
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd>space</kbd>: wybierz kawałek
|
||||
<kbd>b</kbd>: wybierz wszystkie kawałki
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
</pre>
|
||||
|
||||
## Schowek
|
||||
|
|
@ -286,6 +286,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
## Zwykłe
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: przewiń w dół (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: przewiń w górę (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: przewiń w dół (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: przewiń w górę (fn+down)
|
||||
</pre>
|
||||
|
|
|
|||
|
|
@ -30,13 +30,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
## 列表面板导航
|
||||
|
||||
<pre>
|
||||
<kbd>H</kbd>: 向左滚动
|
||||
<kbd>L</kbd>: 向右滚动
|
||||
<kbd>,</kbd>: 上一页
|
||||
<kbd>.</kbd>: 下一页
|
||||
<kbd><</kbd>: 滚动到顶部
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
<kbd>></kbd>: 滚动到底部
|
||||
<kbd>H</kbd>: 向左滚动
|
||||
<kbd>L</kbd>: 向右滚动
|
||||
<kbd>]</kbd>: 下一个标签
|
||||
<kbd>[</kbd>: 上一个标签
|
||||
</pre>
|
||||
|
|
@ -209,17 +209,17 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
## 正在合并
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: 返回文件面板
|
||||
<kbd>M</kbd>: 打开外部合并工具 (git mergetool)
|
||||
<kbd>space</kbd>: 选中区块
|
||||
<kbd>b</kbd>: 选中所有区块
|
||||
<kbd>e</kbd>: 编辑文件
|
||||
<kbd>o</kbd>: 打开文件
|
||||
<kbd>◄</kbd>: 选择上一个冲突
|
||||
<kbd>►</kbd>: 选择下一个冲突
|
||||
<kbd>▲</kbd>: 选择顶部块
|
||||
<kbd>▼</kbd>: 选择底部块
|
||||
<kbd>e</kbd>: 编辑文件
|
||||
<kbd>o</kbd>: 打开文件
|
||||
<kbd>z</kbd>: 撤销
|
||||
<kbd>M</kbd>: 打开外部合并工具 (git mergetool)
|
||||
<kbd>space</kbd>: 选中区块
|
||||
<kbd>b</kbd>: 选中所有区块
|
||||
<kbd>esc</kbd>: 返回文件面板
|
||||
</pre>
|
||||
|
||||
## 正在暂存
|
||||
|
|
@ -243,8 +243,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
|||
## 正常
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: 向下滚动 (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: 向上滚动 (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: 向下滚动 (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: 向上滚动 (fn+down)
|
||||
</pre>
|
||||
|
||||
## 状态
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -30,6 +30,7 @@ require (
|
|||
github.com/sahilm/fuzzy v0.1.0
|
||||
github.com/samber/lo v1.10.1
|
||||
github.com/sanity-io/litter v1.5.2
|
||||
github.com/sasha-s/go-deadlock v0.3.1
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
|
||||
github.com/stretchr/testify v1.7.0
|
||||
|
|
@ -58,6 +59,7 @@ require (
|
|||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/onsi/ginkgo v1.10.3 // indirect
|
||||
github.com/onsi/gomega v1.7.1 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
|
||||
github.com/rivo/uniseg v0.3.4 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.2.1 // indirect
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -125,6 +125,8 @@ github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
|
|||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
|
|
@ -140,6 +142,8 @@ github.com/samber/lo v1.10.1 h1:0D3h7i0U3hRAbaCeQ82DLe67n0A7Bbl0/cEoWqFGp+U=
|
|||
github.com/samber/lo v1.10.1/go.mod h1:2I7tgIv8Q1SG2xEIkRq0F2i2zgxVpnyPOP0d3Gj2r+A=
|
||||
github.com/sanity-io/litter v1.5.2 h1:AnC8s9BMORWH5a4atZ4D6FPVvKGzHcnc5/IVTa87myw=
|
||||
github.com/sanity-io/litter v1.5.2/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0=
|
||||
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
|
||||
github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
|
|||
"information": tr.InformationTitle,
|
||||
"main": tr.NormalTitle,
|
||||
"patchBuilding": tr.PatchBuildingTitle,
|
||||
"merging": tr.MergingTitle,
|
||||
"mergeConflicts": tr.MergingTitle,
|
||||
"staging": tr.StagingTitle,
|
||||
"menu": tr.MenuTitle,
|
||||
"search": tr.SearchTitle,
|
||||
|
|
@ -142,7 +142,7 @@ func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*b
|
|||
bindingsByHeader,
|
||||
func(header header, hBindings []*types.Binding) headerWithBindings {
|
||||
uniqBindings := lo.UniqBy(hBindings, func(binding *types.Binding) string {
|
||||
return binding.Description + keybindings.GetKeyDisplay(binding.Key)
|
||||
return binding.Description + keybindings.LabelFromKey(binding.Key)
|
||||
})
|
||||
|
||||
return headerWithBindings{
|
||||
|
|
@ -202,10 +202,10 @@ func formatBinding(binding *types.Binding) string {
|
|||
if binding.Alternative != "" {
|
||||
return fmt.Sprintf(
|
||||
" <kbd>%s</kbd>: %s (%s)\n",
|
||||
keybindings.GetKeyDisplay(binding.Key),
|
||||
keybindings.LabelFromKey(binding.Key),
|
||||
binding.Description,
|
||||
binding.Alternative,
|
||||
)
|
||||
}
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", keybindings.GetKeyDisplay(binding.Key), binding.Description)
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", keybindings.LabelFromKey(binding.Key), binding.Description)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
|
||||
gogit "github.com/jesseduffield/go-git/v5"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
|
|
@ -57,7 +57,7 @@ func NewGitCommand(
|
|||
cmn *common.Common,
|
||||
osCommand *oscommands.OSCommand,
|
||||
gitConfig git_config.IGitConfig,
|
||||
syncMutex *sync.Mutex,
|
||||
syncMutex *deadlock.Mutex,
|
||||
) (*GitCommand, error) {
|
||||
if err := navigateToRepoRootDirectory(os.Stat, os.Chdir); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -89,7 +89,7 @@ func NewGitCommandAux(
|
|||
gitConfig git_config.IGitConfig,
|
||||
dotGitDir string,
|
||||
repo *gogit.Repository,
|
||||
syncMutex *sync.Mutex,
|
||||
syncMutex *deadlock.Mutex,
|
||||
) *GitCommand {
|
||||
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package git_commands
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
gogit "github.com/jesseduffield/go-git/v5"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type GitCommon struct {
|
||||
|
|
@ -16,7 +15,7 @@ type GitCommon struct {
|
|||
repo *gogit.Repository
|
||||
config *ConfigCommands
|
||||
// mutex for doing things like push/pull/fetch
|
||||
syncMutex *sync.Mutex
|
||||
syncMutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
func NewGitCommon(
|
||||
|
|
@ -26,7 +25,7 @@ func NewGitCommon(
|
|||
dotGitDir string,
|
||||
repo *gogit.Repository,
|
||||
config *ConfigCommands,
|
||||
syncMutex *sync.Mutex,
|
||||
syncMutex *deadlock.Mutex,
|
||||
) *GitCommon {
|
||||
return &GitCommon{
|
||||
Common: cmn,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package commands
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -12,6 +11,7 @@ import (
|
|||
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
@ -220,7 +220,7 @@ func TestNewGitCommand(t *testing.T) {
|
|||
NewGitCommand(utils.NewDummyCommon(),
|
||||
oscommands.NewDummyOSCommand(),
|
||||
git_config.NewFakeGitConfig(nil),
|
||||
&sync.Mutex{},
|
||||
&deadlock.Mutex{},
|
||||
))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,11 @@ var bitbucketServiceDef = ServiceDefinition{
|
|||
pullRequestURLIntoDefaultBranch: "/pull-requests/new?source={{.From}}&t=1",
|
||||
pullRequestURLIntoTargetBranch: "/pull-requests/new?source={{.From}}&dest={{.To}}&t=1",
|
||||
commitURL: "/commits/{{.CommitSha}}",
|
||||
regexStrings: defaultUrlRegexStrings,
|
||||
repoURLTemplate: defaultRepoURLTemplate,
|
||||
regexStrings: []string{
|
||||
`^(?:https?|ssh)://.*/(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
|
||||
`^.*@.*:(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
|
||||
},
|
||||
repoURLTemplate: defaultRepoURLTemplate,
|
||||
}
|
||||
|
||||
var gitLabServiceDef = ServiceDefinition{
|
||||
|
|
|
|||
|
|
@ -115,6 +115,15 @@ func TestGetPullRequestURL(t *testing.T) {
|
|||
assert.Equal(t, "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature%2Fcommit-ui&merge_request[target_branch]=epic%2Fui", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on bitbucket with a custom SSH username",
|
||||
from: "feature/profile-page",
|
||||
remoteUrl: "john@bitbucket.org:johndoe/social_network.git",
|
||||
test: func(url string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature%2Fprofile-page&t=1", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on Azure DevOps (SSH)",
|
||||
from: "feature/new",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ package oscommands
|
|||
|
||||
import (
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
// A command object is a general way to represent a command to be run on the
|
||||
|
|
@ -51,8 +52,8 @@ type ICmdObj interface {
|
|||
PromptOnCredentialRequest() ICmdObj
|
||||
FailOnCredentialRequest() ICmdObj
|
||||
|
||||
WithMutex(mutex *sync.Mutex) ICmdObj
|
||||
Mutex() *sync.Mutex
|
||||
WithMutex(mutex *deadlock.Mutex) ICmdObj
|
||||
Mutex() *deadlock.Mutex
|
||||
|
||||
GetCredentialStrategy() CredentialStrategy
|
||||
}
|
||||
|
|
@ -76,7 +77,7 @@ type CmdObj struct {
|
|||
credentialStrategy CredentialStrategy
|
||||
|
||||
// can be set so that we don't run certain commands simultaneously
|
||||
mutex *sync.Mutex
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
type CredentialStrategy int
|
||||
|
|
@ -139,11 +140,11 @@ func (self *CmdObj) IgnoreEmptyError() ICmdObj {
|
|||
return self
|
||||
}
|
||||
|
||||
func (self *CmdObj) Mutex() *sync.Mutex {
|
||||
func (self *CmdObj) Mutex() *deadlock.Mutex {
|
||||
return self.mutex
|
||||
}
|
||||
|
||||
func (self *CmdObj) WithMutex(mutex *sync.Mutex) ICmdObj {
|
||||
func (self *CmdObj) WithMutex(mutex *deadlock.Mutex) ICmdObj {
|
||||
self.mutex = mutex
|
||||
|
||||
return self
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ import (
|
|||
"bytes"
|
||||
"io"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type Buffer struct {
|
||||
b bytes.Buffer
|
||||
m sync.Mutex
|
||||
m deadlock.Mutex
|
||||
}
|
||||
|
||||
func (b *Buffer) Read(p []byte) (n int, err error) {
|
||||
|
|
|
|||
|
|
@ -109,13 +109,34 @@ func (c *OSCommand) Quote(message string) string {
|
|||
// AppendLineToFile adds a new line in file
|
||||
func (c *OSCommand) AppendLineToFile(filename, line string) error {
|
||||
c.LogCommand(fmt.Sprintf("Appending '%s' to file '%s'", line, filename), false)
|
||||
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600)
|
||||
f, err := os.OpenFile(filename, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0o600)
|
||||
if err != nil {
|
||||
return utils.WrapError(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString("\n" + line)
|
||||
info, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
return utils.WrapError(err)
|
||||
}
|
||||
|
||||
if info.Size() > 0 {
|
||||
// read last char
|
||||
buf := make([]byte, 1)
|
||||
if _, err := f.ReadAt(buf, info.Size()-1); err != nil {
|
||||
return utils.WrapError(err)
|
||||
}
|
||||
|
||||
// if the last byte of the file is not a newline, add it
|
||||
if []byte("\n")[0] != buf[0] {
|
||||
_, err = f.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
_, err = f.WriteString(line + "\n")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return utils.WrapError(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package oscommands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -135,3 +137,61 @@ func TestOSCommandFileType(t *testing.T) {
|
|||
_ = os.RemoveAll(s.path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOSCommandAppendLineToFile(t *testing.T) {
|
||||
type scenario struct {
|
||||
path string
|
||||
setup func(string)
|
||||
test func(string)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
filepath.Join(os.TempDir(), "testFile"),
|
||||
func(path string) {
|
||||
if err := ioutil.WriteFile(path, []byte("hello"), 0o600); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
func(output string) {
|
||||
assert.EqualValues(t, "hello\nworld\n", output)
|
||||
},
|
||||
},
|
||||
{
|
||||
filepath.Join(os.TempDir(), "emptyTestFile"),
|
||||
func(path string) {
|
||||
if err := ioutil.WriteFile(path, []byte(""), 0o600); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
func(output string) {
|
||||
assert.EqualValues(t, "world\n", output)
|
||||
},
|
||||
},
|
||||
{
|
||||
filepath.Join(os.TempDir(), "testFileWithNewline"),
|
||||
func(path string) {
|
||||
if err := ioutil.WriteFile(path, []byte("hello\n"), 0o600); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
func(output string) {
|
||||
assert.EqualValues(t, "hello\nworld\n", output)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s.setup(s.path)
|
||||
osCommand := NewDummyOSCommand()
|
||||
if err := osCommand.AppendLineToFile(s.path, "world"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f, err := ioutil.ReadFile(s.path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.test(string(f))
|
||||
_ = os.RemoveAll(s.path)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package gui
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
// statusManager's job is to handle rendering of loading states and toast notifications
|
||||
|
|
@ -13,7 +13,7 @@ import (
|
|||
type statusManager struct {
|
||||
statuses []appStatus
|
||||
nextId int
|
||||
mutex sync.Mutex
|
||||
mutex deadlock.Mutex
|
||||
}
|
||||
|
||||
type appStatus struct {
|
||||
|
|
@ -94,7 +94,7 @@ func (gui *Gui) renderAppStatus() {
|
|||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
appStatus := gui.statusManager.getStatusString()
|
||||
gui.OnUIThread(func() error {
|
||||
gui.c.OnUIThread(func() error {
|
||||
return gui.renderString(gui.Views.AppStatus, appStatus)
|
||||
})
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ func (gui *Gui) withWaitingStatus(message string, f func() error) error {
|
|||
gui.renderAppStatus()
|
||||
|
||||
if err := f(); err != nil {
|
||||
gui.OnUIThread(func() error {
|
||||
gui.c.OnUIThread(func() error {
|
||||
return gui.c.Error(err)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,23 @@
|
|||
package gui
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
func (gui *Gui) branchesRenderToMain() error {
|
||||
var task updateTask
|
||||
var task types.UpdateTask
|
||||
branch := gui.State.Contexts.Branches.GetSelected()
|
||||
if branch == nil {
|
||||
task = NewRenderStringTask(gui.c.Tr.NoBranchesThisRepo)
|
||||
task = types.NewRenderStringTask(gui.c.Tr.NoBranchesThisRepo)
|
||||
} else {
|
||||
cmdObj := gui.git.Branch.GetGraphCmdObj(branch.FullRefName())
|
||||
|
||||
task = NewRunPtyTask(cmdObj.GetCmd())
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.normalMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
title: gui.c.Tr.LogTitle,
|
||||
task: task,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.c.Tr.LogTitle,
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/constants"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
|
@ -53,7 +54,7 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
|
|||
func (gui *Gui) printCommandLogHeader() {
|
||||
introStr := fmt.Sprintf(
|
||||
gui.c.Tr.CommandLogHeader,
|
||||
gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.ExtrasMenu),
|
||||
keybindings.Label(gui.c.UserConfig.Keybinding.Universal.ExtrasMenu),
|
||||
)
|
||||
fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr))
|
||||
|
||||
|
|
@ -71,7 +72,7 @@ func (gui *Gui) getRandomTip() string {
|
|||
config := gui.c.UserConfig.Keybinding
|
||||
|
||||
formattedKey := func(key string) string {
|
||||
return gui.getKeyDisplay(key)
|
||||
return keybindings.Label(key)
|
||||
}
|
||||
|
||||
tips := []string{
|
||||
|
|
|
|||
|
|
@ -16,20 +16,20 @@ func (gui *Gui) commitFilesRenderToMain() error {
|
|||
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
|
||||
cmdObj := gui.git.WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false)
|
||||
task := NewRunPtyTask(cmdObj.GetCmd())
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
pair := gui.normalMainContextPair()
|
||||
pair := gui.c.MainViewPairs().Normal
|
||||
if node.File != nil {
|
||||
pair = gui.patchBuildingMainContextPair()
|
||||
pair = gui.c.MainViewPairs().PatchBuilding
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: pair,
|
||||
main: &viewUpdateOpts{
|
||||
title: gui.Tr.Patch,
|
||||
task: task,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: pair,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.Tr.Patch,
|
||||
Task: task,
|
||||
},
|
||||
secondary: gui.secondaryPatchPanelUpdateOpts(),
|
||||
Secondary: gui.secondaryPatchPanelUpdateOpts(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
|
|
@ -12,9 +13,9 @@ func (gui *Gui) handleCommitMessageFocused() error {
|
|||
message := utils.ResolvePlaceholderString(
|
||||
gui.c.Tr.CommitMessageConfirm,
|
||||
map[string]string{
|
||||
"keyBindClose": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.Return),
|
||||
"keyBindConfirm": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.Confirm),
|
||||
"keyBindNewLine": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.AppendNewline),
|
||||
"keyBindClose": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Return),
|
||||
"keyBindConfirm": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Confirm),
|
||||
"keyBindNewLine": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.AppendNewline),
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package gui
|
|||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
|
|
@ -29,32 +30,32 @@ func (gui *Gui) onCommitFocus() error {
|
|||
}
|
||||
|
||||
func (gui *Gui) branchCommitsRenderToMain() error {
|
||||
var task updateTask
|
||||
var task types.UpdateTask
|
||||
commit := gui.State.Contexts.LocalCommits.GetSelected()
|
||||
if commit == nil {
|
||||
task = NewRenderStringTask(gui.c.Tr.NoCommitsThisBranch)
|
||||
task = types.NewRenderStringTask(gui.c.Tr.NoCommitsThisBranch)
|
||||
} else {
|
||||
cmdObj := gui.git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
|
||||
task = NewRunPtyTask(cmdObj.GetCmd())
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.normalMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
title: "Patch",
|
||||
task: task,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Patch",
|
||||
Task: task,
|
||||
},
|
||||
secondary: gui.secondaryPatchPanelUpdateOpts(),
|
||||
Secondary: gui.secondaryPatchPanelUpdateOpts(),
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) secondaryPatchPanelUpdateOpts() *viewUpdateOpts {
|
||||
func (gui *Gui) secondaryPatchPanelUpdateOpts() *types.ViewUpdateOpts {
|
||||
if gui.git.Patch.PatchManager.Active() {
|
||||
patch := gui.git.Patch.PatchManager.RenderAggregatedPatchColored(false)
|
||||
|
||||
return &viewUpdateOpts{
|
||||
task: NewRenderStringWithoutScrollTask(patch),
|
||||
title: gui.Tr.CustomPatch,
|
||||
return &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(patch),
|
||||
Title: gui.Tr.CustomPatch,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
|
|
@ -220,22 +221,22 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
|
|||
bindings := []*types.Binding{
|
||||
{
|
||||
ViewName: "confirmation",
|
||||
Key: gui.getKey(keybindingConfig.Universal.Confirm),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
|
||||
Handler: onConfirm,
|
||||
},
|
||||
{
|
||||
ViewName: "confirmation",
|
||||
Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1),
|
||||
Handler: onConfirm,
|
||||
},
|
||||
{
|
||||
ViewName: "confirmation",
|
||||
Key: gui.getKey(keybindingConfig.Universal.Return),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
|
||||
Handler: gui.wrappedConfirmationFunction(opts.HandleClose),
|
||||
},
|
||||
{
|
||||
ViewName: "confirmation",
|
||||
Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
|
||||
Handler: func() error {
|
||||
if len(gui.State.Suggestions) > 0 {
|
||||
return gui.replaceContext(gui.State.Contexts.Suggestions)
|
||||
|
|
@ -245,22 +246,22 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
|
|||
},
|
||||
{
|
||||
ViewName: "suggestions",
|
||||
Key: gui.getKey(keybindingConfig.Universal.Confirm),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
|
||||
Handler: onSuggestionConfirm,
|
||||
},
|
||||
{
|
||||
ViewName: "suggestions",
|
||||
Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1),
|
||||
Handler: onSuggestionConfirm,
|
||||
},
|
||||
{
|
||||
ViewName: "suggestions",
|
||||
Key: gui.getKey(keybindingConfig.Universal.Return),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
|
||||
Handler: gui.wrappedConfirmationFunction(opts.HandleClose),
|
||||
},
|
||||
{
|
||||
ViewName: "suggestions",
|
||||
Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
|
||||
Handler: func() error { return gui.replaceContext(gui.State.Contexts.Confirmation) },
|
||||
},
|
||||
}
|
||||
|
|
@ -276,12 +277,12 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
|
|||
|
||||
func (gui *Gui) clearConfirmationViewKeyBindings() {
|
||||
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshSuggestions() {
|
||||
|
|
@ -297,8 +298,8 @@ func (gui *Gui) handleAskFocused() error {
|
|||
message := utils.ResolvePlaceholderString(
|
||||
gui.c.Tr.CloseConfirm,
|
||||
map[string]string{
|
||||
"keyBindClose": gui.getKeyDisplay(keybindingConfig.Universal.Return),
|
||||
"keyBindConfirm": gui.getKeyDisplay(keybindingConfig.Universal.Confirm),
|
||||
"keyBindClose": keybindings.Label(keybindingConfig.Universal.Return),
|
||||
"keyBindConfirm": keybindings.Label(keybindingConfig.Universal.Confirm),
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ const (
|
|||
STAGING_SECONDARY_CONTEXT_KEY types.ContextKey = "stagingSecondary"
|
||||
PATCH_BUILDING_MAIN_CONTEXT_KEY types.ContextKey = "patchBuilding"
|
||||
PATCH_BUILDING_SECONDARY_CONTEXT_KEY types.ContextKey = "patchBuildingSecondary"
|
||||
MERGING_MAIN_CONTEXT_KEY types.ContextKey = "merging"
|
||||
MERGE_CONFLICTS_CONTEXT_KEY types.ContextKey = "mergeConflicts"
|
||||
|
||||
// these shouldn't really be needed for anything but I'm giving them unique keys nonetheless
|
||||
OPTIONS_CONTEXT_KEY types.ContextKey = "options"
|
||||
|
|
@ -60,7 +60,7 @@ var AllContextKeys = []types.ContextKey{
|
|||
STAGING_SECONDARY_CONTEXT_KEY,
|
||||
PATCH_BUILDING_MAIN_CONTEXT_KEY,
|
||||
PATCH_BUILDING_SECONDARY_CONTEXT_KEY,
|
||||
MERGING_MAIN_CONTEXT_KEY,
|
||||
MERGE_CONFLICTS_CONTEXT_KEY,
|
||||
|
||||
MENU_CONTEXT_KEY,
|
||||
CONFIRMATION_CONTEXT_KEY,
|
||||
|
|
@ -93,7 +93,7 @@ type ContextTree struct {
|
|||
StagingSecondary *PatchExplorerContext
|
||||
CustomPatchBuilder *PatchExplorerContext
|
||||
CustomPatchBuilderSecondary types.Context
|
||||
Merging types.Context
|
||||
MergeConflicts *MergeConflictsContext
|
||||
Confirmation types.Context
|
||||
CommitMessage types.Context
|
||||
CommandLog types.Context
|
||||
|
|
@ -127,7 +127,7 @@ func (self *ContextTree) Flatten() []types.Context {
|
|||
self.Confirmation,
|
||||
self.CommitMessage,
|
||||
|
||||
self.Merging,
|
||||
self.MergeConflicts,
|
||||
self.StagingSecondary,
|
||||
self.Staging,
|
||||
self.CustomPatchBuilderSecondary,
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ func (self *MenuViewModel) GetDisplayStrings(_startIdx int, _length int) [][]str
|
|||
return slices.Map(self.menuItems, func(item *types.MenuItem) []string {
|
||||
displayStrings := item.LabelColumns
|
||||
if showKeys {
|
||||
displayStrings = slices.Prepend(displayStrings, style.FgCyan.Sprint(keybindings.GetKeyDisplay(item.Key)))
|
||||
displayStrings = slices.Prepend(displayStrings, style.FgCyan.Sprint(keybindings.LabelFromKey(item.Key)))
|
||||
}
|
||||
return displayStrings
|
||||
})
|
||||
|
|
|
|||
117
pkg/gui/context/merge_conflicts_context.go
Normal file
117
pkg/gui/context/merge_conflicts_context.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type MergeConflictsContext struct {
|
||||
types.Context
|
||||
viewModel *ConflictsViewModel
|
||||
c *types.HelperCommon
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
type ConflictsViewModel struct {
|
||||
state *mergeconflicts.State
|
||||
|
||||
// userVerticalScrolling tells us if the user has started scrolling through the file themselves
|
||||
// in which case we won't auto-scroll to a conflict.
|
||||
userVerticalScrolling bool
|
||||
}
|
||||
|
||||
func NewMergeConflictsContext(
|
||||
view *gocui.View,
|
||||
|
||||
opts ContextCallbackOpts,
|
||||
|
||||
c *types.HelperCommon,
|
||||
getOptionsMap func() map[string]string,
|
||||
) *MergeConflictsContext {
|
||||
viewModel := &ConflictsViewModel{
|
||||
state: mergeconflicts.NewState(),
|
||||
userVerticalScrolling: false,
|
||||
}
|
||||
|
||||
return &MergeConflictsContext{
|
||||
viewModel: viewModel,
|
||||
mutex: &deadlock.Mutex{},
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: view,
|
||||
WindowName: "main",
|
||||
Key: MERGE_CONFLICTS_CONTEXT_KEY,
|
||||
OnGetOptionsMap: getOptionsMap,
|
||||
Focusable: true,
|
||||
}),
|
||||
opts,
|
||||
),
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) GetState() *mergeconflicts.State {
|
||||
return self.viewModel.state
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) SetState(state *mergeconflicts.State) {
|
||||
self.viewModel.state = state
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) GetMutex() *deadlock.Mutex {
|
||||
return self.mutex
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) SetUserScrolling(isScrolling bool) {
|
||||
self.viewModel.userVerticalScrolling = isScrolling
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) IsUserScrolling() bool {
|
||||
return self.viewModel.userVerticalScrolling
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) RenderAndFocus(isFocused bool) error {
|
||||
self.setContent(isFocused)
|
||||
self.focusSelection()
|
||||
|
||||
self.c.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) Render(isFocused bool) error {
|
||||
self.setContent(isFocused)
|
||||
|
||||
self.c.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) GetContentToRender(isFocused bool) string {
|
||||
if self.GetState() == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return mergeconflicts.ColoredConflictFile(self.GetState(), isFocused)
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) setContent(isFocused bool) {
|
||||
self.GetView().SetContent(self.GetContentToRender(isFocused))
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) focusSelection() {
|
||||
if !self.IsUserScrolling() {
|
||||
_ = self.GetView().SetOrigin(self.GetView().OriginX(), self.GetOriginY())
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) GetOriginY() int {
|
||||
view := self.GetView()
|
||||
conflictMiddle := self.GetState().GetConflictMiddle()
|
||||
return int(math.Max(0, float64(conflictMiddle-(view.Height()/2))))
|
||||
}
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
deadlock "github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type PatchExplorerContext struct {
|
||||
|
|
@ -15,7 +14,7 @@ type PatchExplorerContext struct {
|
|||
viewTrait *ViewTrait
|
||||
getIncludedLineIndices func() []int
|
||||
c *types.HelperCommon
|
||||
mutex *sync.Mutex
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
var _ types.IPatchExplorerContext = (*PatchExplorerContext)(nil)
|
||||
|
|
@ -35,7 +34,7 @@ func NewPatchExplorerContext(
|
|||
state: nil,
|
||||
viewTrait: NewViewTrait(view),
|
||||
c: c,
|
||||
mutex: &sync.Mutex{},
|
||||
mutex: &deadlock.Mutex{},
|
||||
getIncludedLineIndices: getIncludedLineIndices,
|
||||
SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
|
|
@ -67,19 +66,16 @@ func (self *PatchExplorerContext) GetIncludedLineIndices() []int {
|
|||
}
|
||||
|
||||
func (self *PatchExplorerContext) RenderAndFocus(isFocused bool) error {
|
||||
self.GetView().SetContent(self.GetContentToRender(isFocused))
|
||||
|
||||
if err := self.focusSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
self.setContent(isFocused)
|
||||
|
||||
self.focusSelection()
|
||||
self.c.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) Render(isFocused bool) error {
|
||||
self.GetView().SetContent(self.GetContentToRender(isFocused))
|
||||
self.setContent(isFocused)
|
||||
|
||||
self.c.Render()
|
||||
|
||||
|
|
@ -87,16 +83,17 @@ func (self *PatchExplorerContext) Render(isFocused bool) error {
|
|||
}
|
||||
|
||||
func (self *PatchExplorerContext) Focus() error {
|
||||
if err := self.focusSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.focusSelection()
|
||||
self.c.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) focusSelection() error {
|
||||
func (self *PatchExplorerContext) setContent(isFocused bool) {
|
||||
self.GetView().SetContent(self.GetContentToRender(isFocused))
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) focusSelection() {
|
||||
view := self.GetView()
|
||||
state := self.GetState()
|
||||
_, viewHeight := view.Size()
|
||||
|
|
@ -107,11 +104,8 @@ func (self *PatchExplorerContext) focusSelection() error {
|
|||
|
||||
newOrigin := state.CalculateOrigin(origin, bufferHeight)
|
||||
|
||||
if err := view.SetOriginY(newOrigin); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return view.SetCursor(0, selectedLineIdx-newOrigin)
|
||||
_ = view.SetOriginY(newOrigin)
|
||||
_ = view.SetCursor(0, selectedLineIdx-newOrigin)
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) GetContentToRender(isFocused bool) string {
|
||||
|
|
@ -129,6 +123,6 @@ func (self *PatchExplorerContext) NavigateTo(isFocused bool, selectedLineIdx int
|
|||
return self.RenderAndFocus(isFocused)
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) GetMutex() *sync.Mutex {
|
||||
func (self *PatchExplorerContext) GetMutex() *deadlock.Mutex {
|
||||
return self.mutex
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,11 +29,12 @@ func NewSuggestionsContext(
|
|||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
WindowName: "suggestions",
|
||||
Key: SUGGESTIONS_CONTEXT_KEY,
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
Focusable: true,
|
||||
View: view,
|
||||
WindowName: "suggestions",
|
||||
Key: SUGGESTIONS_CONTEXT_KEY,
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ func (gui *Gui) contextTree() *context.ContextTree {
|
|||
Global: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.GLOBAL_CONTEXT,
|
||||
View: nil,
|
||||
View: nil, // TODO: see if this breaks anything
|
||||
WindowName: "",
|
||||
Key: context.GLOBAL_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
|
|
@ -158,27 +158,27 @@ func (gui *Gui) contextTree() *context.ContextTree {
|
|||
}),
|
||||
context.ContextCallbackOpts{},
|
||||
),
|
||||
Merging: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: gui.Views.Merging,
|
||||
WindowName: "main",
|
||||
Key: context.MERGING_MAIN_CONTEXT_KEY,
|
||||
OnGetOptionsMap: gui.getMergingOptions,
|
||||
Focusable: true,
|
||||
}),
|
||||
MergeConflicts: context.NewMergeConflictsContext(
|
||||
gui.Views.MergeConflicts,
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: OnFocusWrapper(func() error {
|
||||
gui.Views.Merging.Wrap = false
|
||||
gui.Views.MergeConflicts.Wrap = false
|
||||
|
||||
return gui.renderConflictsWithLock(true)
|
||||
return gui.refreshMergePanel(true)
|
||||
}),
|
||||
OnFocusLost: func(types.OnFocusLostOpts) error {
|
||||
gui.Views.Merging.Wrap = true
|
||||
OnFocusLost: func(opts types.OnFocusLostOpts) error {
|
||||
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
|
||||
gui.State.Contexts.MergeConflicts.GetState().ResetConflictSelection()
|
||||
gui.Views.MergeConflicts.Wrap = true
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
gui.c,
|
||||
func() map[string]string {
|
||||
// wrapping in a function because contexts are initialized before helpers
|
||||
return gui.helpers.MergeConflicts.GetMergingOptions()
|
||||
},
|
||||
),
|
||||
Confirmation: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
|
|
@ -222,12 +222,11 @@ func (gui *Gui) contextTree() *context.ContextTree {
|
|||
),
|
||||
CommandLog: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.EXTRAS_CONTEXT,
|
||||
View: gui.Views.Extras,
|
||||
WindowName: "extras",
|
||||
Key: context.COMMAND_LOG_CONTEXT_KEY,
|
||||
OnGetOptionsMap: gui.getMergingOptions,
|
||||
Focusable: true,
|
||||
Kind: types.EXTRAS_CONTEXT,
|
||||
View: gui.Views.Extras,
|
||||
WindowName: "extras",
|
||||
Key: context.COMMAND_LOG_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnFocusLost: func(opts types.OnFocusLostOpts) error {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ func (gui *Gui) resetControllers() {
|
|||
model,
|
||||
)
|
||||
|
||||
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, gui.State.Contexts, gui.git, gui.takeOverMergeConflictScrolling, refsHelper)
|
||||
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, gui.State.Contexts, gui.git, refsHelper)
|
||||
suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon, model, gui.refreshSuggestions)
|
||||
gui.helpers = &helpers.Helpers{
|
||||
Refs: refsHelper,
|
||||
|
|
@ -35,6 +35,7 @@ func (gui *Gui) resetControllers() {
|
|||
Tags: helpers.NewTagsHelper(helperCommon, gui.git),
|
||||
GPG: helpers.NewGpgHelper(helperCommon, gui.os, gui.git),
|
||||
MergeAndRebase: rebaseHelper,
|
||||
MergeConflicts: helpers.NewMergeConflictsHelper(helperCommon, gui.State.Contexts, gui.git),
|
||||
CherryPick: helpers.NewCherryPickHelper(
|
||||
helperCommon,
|
||||
gui.git,
|
||||
|
|
@ -51,7 +52,6 @@ func (gui *Gui) resetControllers() {
|
|||
gui.git,
|
||||
gui.State.Contexts,
|
||||
gui.helpers,
|
||||
gui.getKey,
|
||||
)
|
||||
|
||||
common := controllers.NewControllerCommon(
|
||||
|
|
@ -112,8 +112,8 @@ func (gui *Gui) resetControllers() {
|
|||
gui.enterSubmodule,
|
||||
setCommitMessage,
|
||||
getSavedCommitMessage,
|
||||
gui.switchToMerge,
|
||||
)
|
||||
mergeConflictsController := controllers.NewMergeConflictsController(common)
|
||||
remotesController := controllers.NewRemotesController(
|
||||
common,
|
||||
func(branches []*models.RemoteBranch) { gui.State.Model.RemoteBranches = branches },
|
||||
|
|
@ -187,6 +187,10 @@ func (gui *Gui) resetControllers() {
|
|||
verticalScrollControllerFactory.Create(gui.State.Contexts.CustomPatchBuilder),
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.MergeConflicts,
|
||||
mergeConflictsController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.Files,
|
||||
filesController,
|
||||
filesRemoveController,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ type FilesController struct {
|
|||
enterSubmodule func(submodule *models.SubmoduleConfig) error
|
||||
setCommitMessage func(message string)
|
||||
getSavedCommitMessage func() string
|
||||
switchToMergeFn func(path string) error
|
||||
}
|
||||
|
||||
var _ types.IController = &FilesController{}
|
||||
|
|
@ -32,14 +31,12 @@ func NewFilesController(
|
|||
enterSubmodule func(submodule *models.SubmoduleConfig) error,
|
||||
setCommitMessage func(message string),
|
||||
getSavedCommitMessage func() string,
|
||||
switchToMergeFn func(path string) error,
|
||||
) *FilesController {
|
||||
return &FilesController{
|
||||
controllerCommon: common,
|
||||
enterSubmodule: enterSubmodule,
|
||||
setCommitMessage: setCommitMessage,
|
||||
getSavedCommitMessage: getSavedCommitMessage,
|
||||
switchToMergeFn: switchToMergeFn,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +158,7 @@ func (self *FilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []*
|
|||
FocusedView: self.context().GetViewName(),
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
ViewName: "mergeConflicts",
|
||||
Key: gocui.MouseLeft,
|
||||
Handler: self.onClickMain,
|
||||
FocusedView: self.context().GetViewName(),
|
||||
|
|
@ -268,10 +265,6 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
|
|||
if node.IsFile() {
|
||||
file := node.File
|
||||
|
||||
if file.HasInlineMergeConflicts {
|
||||
return self.c.PushContext(self.contexts.Merging)
|
||||
}
|
||||
|
||||
if file.HasUnstagedChanges {
|
||||
self.c.LogAction(self.c.Tr.Actions.StageFile)
|
||||
|
||||
|
|
@ -328,6 +321,10 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
|
|||
}
|
||||
|
||||
func (self *FilesController) press(node *filetree.FileNode) error {
|
||||
if node.IsFile() && node.File.HasInlineMergeConflicts {
|
||||
return self.switchToMerge()
|
||||
}
|
||||
|
||||
if err := self.pressWithLock(node); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -750,7 +747,7 @@ func (self *FilesController) switchToMerge() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
return self.switchToMergeFn(file.Name)
|
||||
return self.helpers.MergeConflicts.SwitchToMerge(file.Name)
|
||||
}
|
||||
|
||||
func (self *FilesController) createStashMenu() error {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ type Helpers struct {
|
|||
WorkingTree *WorkingTreeHelper
|
||||
Tags *TagsHelper
|
||||
MergeAndRebase *MergeAndRebaseHelper
|
||||
MergeConflicts *MergeConflictsHelper
|
||||
CherryPick *CherryPickHelper
|
||||
Host *HostHelper
|
||||
PatchBuilding *PatchBuildingHelper
|
||||
|
|
@ -24,6 +25,7 @@ func NewStubHelpers() *Helpers {
|
|||
WorkingTree: &WorkingTreeHelper{},
|
||||
Tags: &TagsHelper{},
|
||||
MergeAndRebase: &MergeAndRebaseHelper{},
|
||||
MergeConflicts: &MergeConflictsHelper{},
|
||||
CherryPick: &CherryPickHelper{},
|
||||
Host: &HostHelper{},
|
||||
PatchBuilding: &PatchBuildingHelper{},
|
||||
|
|
|
|||
|
|
@ -14,26 +14,23 @@ import (
|
|||
)
|
||||
|
||||
type MergeAndRebaseHelper struct {
|
||||
c *types.HelperCommon
|
||||
contexts *context.ContextTree
|
||||
git *commands.GitCommand
|
||||
takeOverMergeConflictScrolling func()
|
||||
refsHelper *RefsHelper
|
||||
c *types.HelperCommon
|
||||
contexts *context.ContextTree
|
||||
git *commands.GitCommand
|
||||
refsHelper *RefsHelper
|
||||
}
|
||||
|
||||
func NewMergeAndRebaseHelper(
|
||||
c *types.HelperCommon,
|
||||
contexts *context.ContextTree,
|
||||
git *commands.GitCommand,
|
||||
takeOverMergeConflictScrolling func(),
|
||||
refsHelper *RefsHelper,
|
||||
) *MergeAndRebaseHelper {
|
||||
return &MergeAndRebaseHelper{
|
||||
c: c,
|
||||
contexts: contexts,
|
||||
git: git,
|
||||
takeOverMergeConflictScrolling: takeOverMergeConflictScrolling,
|
||||
refsHelper: refsHelper,
|
||||
c: c,
|
||||
contexts: contexts,
|
||||
git: git,
|
||||
refsHelper: refsHelper,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,8 +186,6 @@ func (self *MergeAndRebaseHelper) workingTreeStateNoun() string {
|
|||
|
||||
// PromptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress
|
||||
func (self *MergeAndRebaseHelper) PromptToContinueRebase() error {
|
||||
self.takeOverMergeConflictScrolling()
|
||||
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: "continue",
|
||||
Prompt: self.c.Tr.ConflictsResolved,
|
||||
|
|
|
|||
115
pkg/gui/controllers/helpers/merge_conflicts_helper.go
Normal file
115
pkg/gui/controllers/helpers/merge_conflicts_helper.go
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type MergeConflictsHelper struct {
|
||||
c *types.HelperCommon
|
||||
contexts *context.ContextTree
|
||||
git *commands.GitCommand
|
||||
}
|
||||
|
||||
func NewMergeConflictsHelper(
|
||||
c *types.HelperCommon,
|
||||
contexts *context.ContextTree,
|
||||
git *commands.GitCommand,
|
||||
) *MergeConflictsHelper {
|
||||
return &MergeConflictsHelper{
|
||||
c: c,
|
||||
contexts: contexts,
|
||||
git: git,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) GetMergingOptions() map[string]string {
|
||||
keybindingConfig := self.c.UserConfig.Keybinding
|
||||
|
||||
return map[string]string{
|
||||
fmt.Sprintf("%s %s", keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)): self.c.Tr.LcSelectHunk,
|
||||
fmt.Sprintf("%s %s", keybindings.Label(keybindingConfig.Universal.PrevBlock), keybindings.Label(keybindingConfig.Universal.NextBlock)): self.c.Tr.LcNavigateConflicts,
|
||||
keybindings.Label(keybindingConfig.Universal.Select): self.c.Tr.LcPickHunk,
|
||||
keybindings.Label(keybindingConfig.Main.PickBothHunks): self.c.Tr.LcPickAllHunks,
|
||||
keybindings.Label(keybindingConfig.Universal.Undo): self.c.Tr.LcUndo,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) SetMergeState(path string) (bool, error) {
|
||||
self.context().GetMutex().Lock()
|
||||
defer self.context().GetMutex().Unlock()
|
||||
|
||||
return self.setMergeStateWithoutLock(path)
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) setMergeStateWithoutLock(path string) (bool, error) {
|
||||
content, err := self.git.File.Cat(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if path != self.context().GetState().GetPath() {
|
||||
self.context().SetUserScrolling(false)
|
||||
}
|
||||
|
||||
self.context().GetState().SetContent(content, path)
|
||||
|
||||
return !self.context().GetState().NoConflicts(), nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) ResetMergeState() {
|
||||
self.context().GetMutex().Lock()
|
||||
defer self.context().GetMutex().Unlock()
|
||||
|
||||
self.resetMergeState()
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) resetMergeState() {
|
||||
self.context().SetUserScrolling(false)
|
||||
self.context().GetState().Reset()
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) EscapeMerge() error {
|
||||
self.resetMergeState()
|
||||
|
||||
// doing this in separate UI thread so that we're not still holding the lock by the time refresh the file
|
||||
self.c.OnUIThread(func() error {
|
||||
return self.c.PushContext(self.contexts.Files)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) SetConflictsAndRender(path string, isFocused bool) (bool, error) {
|
||||
hasConflicts, err := self.setMergeStateWithoutLock(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if hasConflicts {
|
||||
return true, self.context().Render(isFocused)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) SwitchToMerge(path string) error {
|
||||
if self.context().GetState().GetPath() != path {
|
||||
hasConflicts, err := self.SetMergeState(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !hasConflicts {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return self.c.PushContext(self.contexts.MergeConflicts)
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) context() *context.MergeConflictsContext {
|
||||
return self.contexts.MergeConflicts
|
||||
}
|
||||
317
pkg/gui/controllers/merge_conflicts_controller.go
Normal file
317
pkg/gui/controllers/merge_conflicts_controller.go
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type MergeConflictsController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &MergeConflictsController{}
|
||||
|
||||
func NewMergeConflictsController(
|
||||
common *controllerCommon,
|
||||
) *MergeConflictsController {
|
||||
return &MergeConflictsController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
bindings := []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Edit),
|
||||
Handler: self.HandleEditFile,
|
||||
Description: self.c.Tr.LcEditFile,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.OpenFile),
|
||||
Handler: self.HandleOpenFile,
|
||||
Description: self.c.Tr.LcOpenFile,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevBlock),
|
||||
Handler: self.withRenderAndFocus(self.PrevConflict),
|
||||
Description: self.c.Tr.PrevConflict,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.NextBlock),
|
||||
Handler: self.withRenderAndFocus(self.NextConflict),
|
||||
Description: self.c.Tr.NextConflict,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevItem),
|
||||
Handler: self.withRenderAndFocus(self.PrevConflictHunk),
|
||||
Description: self.c.Tr.SelectPrevHunk,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.NextItem),
|
||||
Handler: self.withRenderAndFocus(self.NextConflictHunk),
|
||||
Description: self.c.Tr.SelectNextHunk,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt),
|
||||
Handler: self.withRenderAndFocus(self.PrevConflict),
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.NextBlockAlt),
|
||||
Handler: self.withRenderAndFocus(self.NextConflict),
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevItemAlt),
|
||||
Handler: self.withRenderAndFocus(self.PrevConflictHunk),
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.NextItemAlt),
|
||||
Handler: self.withRenderAndFocus(self.NextConflictHunk),
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.ScrollLeft),
|
||||
Handler: self.withRenderAndFocus(self.HandleScrollLeft),
|
||||
Description: self.c.Tr.LcScrollLeft,
|
||||
Tag: "navigation",
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.ScrollRight),
|
||||
Handler: self.withRenderAndFocus(self.HandleScrollRight),
|
||||
Description: self.c.Tr.LcScrollRight,
|
||||
Tag: "navigation",
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Undo),
|
||||
Handler: self.withRenderAndFocus(self.HandleUndo),
|
||||
Description: self.c.Tr.LcUndo,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
|
||||
Handler: self.helpers.WorkingTree.OpenMergeTool,
|
||||
Description: self.c.Tr.LcOpenMergeTool,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Select),
|
||||
Handler: self.withRenderAndFocus(self.HandlePickHunk),
|
||||
Description: self.c.Tr.PickHunk,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Main.PickBothHunks),
|
||||
Handler: self.withRenderAndFocus(self.HandlePickAllHunks),
|
||||
Description: self.c.Tr.PickAllHunks,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: self.Escape,
|
||||
Description: self.c.Tr.ReturnToFilesPanel,
|
||||
},
|
||||
}
|
||||
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
|
||||
return []*gocui.ViewMouseBinding{
|
||||
{
|
||||
ViewName: self.context().GetViewName(),
|
||||
Key: gocui.MouseWheelUp,
|
||||
Handler: func(gocui.ViewMouseBindingOpts) error {
|
||||
return self.HandleScrollUp()
|
||||
},
|
||||
},
|
||||
{
|
||||
ViewName: self.context().GetViewName(),
|
||||
Key: gocui.MouseWheelDown,
|
||||
Handler: func(gocui.ViewMouseBindingOpts) error {
|
||||
return self.HandleScrollDown()
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) HandleScrollUp() error {
|
||||
self.context().SetUserScrolling(true)
|
||||
self.context().GetViewTrait().ScrollUp(self.c.UserConfig.Gui.ScrollHeight)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) HandleScrollDown() error {
|
||||
self.context().SetUserScrolling(true)
|
||||
self.context().GetViewTrait().ScrollDown(self.c.UserConfig.Gui.ScrollHeight)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) context() *context.MergeConflictsContext {
|
||||
return self.contexts.MergeConflicts
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) Escape() error {
|
||||
return self.c.PushContext(self.contexts.Files)
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) HandleEditFile() error {
|
||||
lineNumber := self.context().GetState().GetSelectedLine()
|
||||
return self.helpers.Files.EditFileAtLine(self.context().GetState().GetPath(), lineNumber)
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) HandleOpenFile() error {
|
||||
lineNumber := self.context().GetState().GetSelectedLine()
|
||||
return self.helpers.Files.OpenFileAtLine(self.context().GetState().GetPath(), lineNumber)
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) HandleScrollLeft() error {
|
||||
self.context().GetViewTrait().ScrollLeft()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) HandleScrollRight() error {
|
||||
self.context().GetViewTrait().ScrollRight()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) HandleUndo() error {
|
||||
state := self.context().GetState()
|
||||
|
||||
ok := state.Undo()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.c.LogAction("Restoring file to previous state")
|
||||
self.c.LogCommand("Undoing last conflict resolution", false)
|
||||
if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) PrevConflictHunk() error {
|
||||
self.context().SetUserScrolling(false)
|
||||
self.context().GetState().SelectPrevConflictHunk()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) NextConflictHunk() error {
|
||||
self.context().SetUserScrolling(false)
|
||||
self.context().GetState().SelectNextConflictHunk()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) NextConflict() error {
|
||||
self.context().SetUserScrolling(false)
|
||||
self.context().GetState().SelectNextConflict()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) PrevConflict() error {
|
||||
self.context().SetUserScrolling(false)
|
||||
self.context().GetState().SelectPrevConflict()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) HandlePickHunk() error {
|
||||
return self.pickSelection(self.context().GetState().Selection())
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) HandlePickAllHunks() error {
|
||||
return self.pickSelection(mergeconflicts.ALL)
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) pickSelection(selection mergeconflicts.Selection) error {
|
||||
ok, err := self.resolveConflict(selection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if self.context().GetState().AllConflictsResolved() {
|
||||
return self.onLastConflictResolved()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) resolveConflict(selection mergeconflicts.Selection) (bool, error) {
|
||||
self.context().SetUserScrolling(false)
|
||||
|
||||
state := self.context().GetState()
|
||||
|
||||
ok, content, err := state.ContentAfterConflictResolve(selection)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var logStr string
|
||||
switch selection {
|
||||
case mergeconflicts.TOP:
|
||||
logStr = "Picking top hunk"
|
||||
case mergeconflicts.MIDDLE:
|
||||
logStr = "Picking middle hunk"
|
||||
case mergeconflicts.BOTTOM:
|
||||
logStr = "Picking bottom hunk"
|
||||
case mergeconflicts.ALL:
|
||||
logStr = "Picking all hunks"
|
||||
}
|
||||
self.c.LogAction("Resolve merge conflict")
|
||||
self.c.LogCommand(logStr, false)
|
||||
state.PushContent(content)
|
||||
return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0o644)
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) onLastConflictResolved() error {
|
||||
// as part of refreshing files, we handle the situation where a file has had
|
||||
// its merge conflicts resolved.
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) isFocused() bool {
|
||||
return self.c.CurrentContext().GetKey() == self.context().GetKey()
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) withRenderAndFocus(f func() error) func() error {
|
||||
return self.withLock(func() error {
|
||||
if err := f(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.context().RenderAndFocus(self.isFocused())
|
||||
})
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) withLock(f func() error) func() error {
|
||||
return func() error {
|
||||
self.context().GetMutex().Lock()
|
||||
defer self.context().GetMutex().Unlock()
|
||||
|
||||
if self.context().GetState() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return f()
|
||||
}
|
||||
}
|
||||
|
|
@ -18,13 +18,13 @@ func (gui *Gui) renderDiff() error {
|
|||
cmdObj := gui.os.Cmd.New(
|
||||
fmt.Sprintf("git diff --submodule --no-ext-diff --color %s", gui.diffStr()),
|
||||
)
|
||||
task := NewRunPtyTask(cmdObj.GetCmd())
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.normalMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
title: "Diff",
|
||||
task: task,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Diff",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import (
|
|||
"unicode"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
)
|
||||
|
||||
func (gui *Gui) handleEditorKeypress(textArea *gocui.TextArea, key gocui.Key, ch rune, mod gocui.Modifier, allowMultiline bool) bool {
|
||||
newlineKey, ok := gui.getKey(gui.c.UserConfig.Keybinding.Universal.AppendNewline).(gocui.Key)
|
||||
newlineKey, ok := keybindings.GetKey(gui.c.UserConfig.Keybinding.Universal.AppendNewline).(gocui.Key)
|
||||
if !ok {
|
||||
newlineKey = gocui.KeyAltEnter
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@ import (
|
|||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) getSelectedFileNode() *filetree.FileNode {
|
||||
return gui.State.Contexts.Files.GetSelected()
|
||||
}
|
||||
|
|
@ -20,43 +19,35 @@ func (gui *Gui) getSelectedFile() *models.File {
|
|||
return node.File
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectedPath() string {
|
||||
node := gui.getSelectedFileNode()
|
||||
if node == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return node.GetPath()
|
||||
}
|
||||
|
||||
func (gui *Gui) filesRenderToMain() error {
|
||||
node := gui.getSelectedFileNode()
|
||||
|
||||
if node == nil {
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.normalMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
title: gui.c.Tr.DiffTitle,
|
||||
task: NewRenderStringTask(gui.c.Tr.NoChangedFiles),
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.c.Tr.DiffTitle,
|
||||
Task: types.NewRenderStringTask(gui.c.Tr.NoChangedFiles),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if node.File != nil && node.File.HasInlineMergeConflicts {
|
||||
ok, err := gui.setConflictsAndRenderWithLock(node.GetPath(), false)
|
||||
hasConflicts, err := gui.helpers.MergeConflicts.SetMergeState(node.GetPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
return nil
|
||||
|
||||
if hasConflicts {
|
||||
return gui.refreshMergePanel(false)
|
||||
}
|
||||
}
|
||||
|
||||
gui.resetMergeStateWithLock()
|
||||
gui.helpers.MergeConflicts.ResetMergeState()
|
||||
|
||||
pair := gui.normalMainContextPair()
|
||||
pair := gui.c.MainViewPairs().Normal
|
||||
if node.File != nil {
|
||||
pair = gui.stagingMainContextPair()
|
||||
pair = gui.c.MainViewPairs().Staging
|
||||
}
|
||||
|
||||
split := gui.c.UserConfig.Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges())
|
||||
|
|
@ -67,11 +58,11 @@ func (gui *Gui) filesRenderToMain() error {
|
|||
if mainShowsStaged {
|
||||
title = gui.c.Tr.StagedChanges
|
||||
}
|
||||
refreshOpts := refreshMainOpts{
|
||||
pair: pair,
|
||||
main: &viewUpdateOpts{
|
||||
task: NewRunPtyTask(cmdObj.GetCmd()),
|
||||
title: title,
|
||||
refreshOpts := types.RefreshMainOpts{
|
||||
Pair: pair,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: types.NewRunPtyTask(cmdObj.GetCmd()),
|
||||
Title: title,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -83,22 +74,15 @@ func (gui *Gui) filesRenderToMain() error {
|
|||
title = gui.c.Tr.UnstagedChanges
|
||||
}
|
||||
|
||||
refreshOpts.secondary = &viewUpdateOpts{
|
||||
title: title,
|
||||
task: NewRunPtyTask(cmdObj.GetCmd()),
|
||||
refreshOpts.Secondary = &types.ViewUpdateOpts{
|
||||
Title: title,
|
||||
Task: types.NewRunPtyTask(cmdObj.GetCmd()),
|
||||
}
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshOpts)
|
||||
return gui.c.RenderToMainViews(refreshOpts)
|
||||
}
|
||||
|
||||
func (gui *Gui) onFocusFile() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
return nil
|
||||
}
|
||||
|
||||
// test
|
||||
|
||||
func (gui *Gui) getSetTextareaTextFn(getView func() *gocui.View) func(string) {
|
||||
return func(text string) {
|
||||
// using a getView function so that we don't need to worry about when the view is created
|
||||
|
|
@ -108,5 +92,3 @@ func (gui *Gui) getSetTextareaTextFn(getView func() *gocui.View) func(string) {
|
|||
view.RenderTextArea()
|
||||
}
|
||||
}
|
||||
|
||||
// test
|
||||
|
|
|
|||
|
|
@ -75,10 +75,6 @@ func (gui *Gui) scrollDownView(view *gocui.View) {
|
|||
}
|
||||
|
||||
func (gui *Gui) scrollUpMain() error {
|
||||
if gui.renderingConflicts() {
|
||||
gui.State.Panels.Merging.UserVerticalScrolling = true
|
||||
}
|
||||
|
||||
var view *gocui.View
|
||||
if gui.c.CurrentContext().GetWindowName() == "secondary" {
|
||||
view = gui.secondaryView()
|
||||
|
|
@ -86,16 +82,20 @@ func (gui *Gui) scrollUpMain() error {
|
|||
view = gui.mainView()
|
||||
}
|
||||
|
||||
if view.Name() == "mergeConflicts" {
|
||||
// although we have this same logic in the controller, this method can be invoked
|
||||
// via the global scroll up/down keybindings, as opposed to just the mouse wheel keybinding.
|
||||
// It would be nice to have a concept of a global keybinding that runs on the top context in a
|
||||
// window but that might be overkill for this one use case.
|
||||
gui.State.Contexts.MergeConflicts.SetUserScrolling(true)
|
||||
}
|
||||
|
||||
gui.scrollUpView(view)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollDownMain() error {
|
||||
if gui.renderingConflicts() {
|
||||
gui.State.Panels.Merging.UserVerticalScrolling = true
|
||||
}
|
||||
|
||||
var view *gocui.View
|
||||
if gui.c.CurrentContext().GetWindowName() == "secondary" {
|
||||
view = gui.secondaryView()
|
||||
|
|
@ -103,6 +103,10 @@ func (gui *Gui) scrollDownMain() error {
|
|||
view = gui.mainView()
|
||||
}
|
||||
|
||||
if view.Name() == "mergeConflicts" {
|
||||
gui.State.Contexts.MergeConflicts.SetUserScrolling(true)
|
||||
}
|
||||
|
||||
gui.scrollDownView(view)
|
||||
|
||||
return nil
|
||||
|
|
@ -120,27 +124,6 @@ func (gui *Gui) secondaryView() *gocui.View {
|
|||
return view
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollLeftMain() error {
|
||||
gui.scrollLeft(gui.mainView())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollRightMain() error {
|
||||
gui.scrollRight(gui.mainView())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollLeft(view *gocui.View) {
|
||||
newOriginX := utils.Max(view.OriginX()-view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR, 0)
|
||||
_ = view.SetOriginX(newOriginX)
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollRight(view *gocui.View) {
|
||||
_ = view.SetOriginX(view.OriginX() + view.InnerWidth()/HORIZONTAL_SCROLL_FACTOR)
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollUpSecondary() error {
|
||||
gui.scrollUpView(gui.secondaryView())
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/filtering"
|
||||
|
|
@ -35,6 +35,7 @@ import (
|
|||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/updates"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
"gopkg.in/ozeidan/fuzzy-patricia.v3/patricia"
|
||||
)
|
||||
|
||||
|
|
@ -169,7 +170,6 @@ type GuiRepoState struct {
|
|||
Suggestions []*types.Suggestion
|
||||
|
||||
Updating bool
|
||||
Panels *panelStates
|
||||
SplitMainPanel bool
|
||||
LimitCommits bool
|
||||
|
||||
|
|
@ -199,20 +199,6 @@ type GuiRepoState struct {
|
|||
CurrentPopupOpts *types.CreatePopupPanelOpts
|
||||
}
|
||||
|
||||
type MergingPanelState struct {
|
||||
*mergeconflicts.State
|
||||
|
||||
// UserVerticalScrolling tells us if the user has started scrolling through the file themselves
|
||||
// in which case we won't auto-scroll to a conflict.
|
||||
UserVerticalScrolling bool
|
||||
}
|
||||
|
||||
// as we move things to the new context approach we're going to eventually
|
||||
// remove this struct altogether and store this state on the contexts.
|
||||
type panelStates struct {
|
||||
Merging *MergingPanelState
|
||||
}
|
||||
|
||||
type searchingState struct {
|
||||
view *gocui.View
|
||||
isSearching bool
|
||||
|
|
@ -299,13 +285,6 @@ func (gui *Gui) resetState(startArgs types.StartArgs, reuseState bool) {
|
|||
BisectInfo: git_commands.NewNullBisectInfo(),
|
||||
FilesTrie: patricia.NewTrie(),
|
||||
},
|
||||
|
||||
Panels: &panelStates{
|
||||
Merging: &MergingPanelState{
|
||||
State: mergeconflicts.NewState(),
|
||||
UserVerticalScrolling: false,
|
||||
},
|
||||
},
|
||||
Modes: &types.Modes{
|
||||
Filtering: filtering.New(startArgs.FilterPath),
|
||||
CherryPicking: cherrypicking.New(),
|
||||
|
|
@ -380,13 +359,13 @@ func NewGui(
|
|||
// sake of backwards compatibility. We're making use of short circuiting here
|
||||
ShowExtrasWindow: cmn.UserConfig.Gui.ShowCommandLog && !config.GetAppState().HideCommandLog,
|
||||
Mutexes: types.Mutexes{
|
||||
RefreshingFilesMutex: &sync.Mutex{},
|
||||
RefreshingStatusMutex: &sync.Mutex{},
|
||||
SyncMutex: &sync.Mutex{},
|
||||
LocalCommitsMutex: &sync.Mutex{},
|
||||
SubprocessMutex: &sync.Mutex{},
|
||||
PopupMutex: &sync.Mutex{},
|
||||
PtyMutex: &sync.Mutex{},
|
||||
RefreshingFilesMutex: &deadlock.Mutex{},
|
||||
RefreshingStatusMutex: &deadlock.Mutex{},
|
||||
SyncMutex: &deadlock.Mutex{},
|
||||
LocalCommitsMutex: &deadlock.Mutex{},
|
||||
SubprocessMutex: &deadlock.Mutex{},
|
||||
PopupMutex: &deadlock.Mutex{},
|
||||
PtyMutex: &deadlock.Mutex{},
|
||||
},
|
||||
InitialDir: initialDir,
|
||||
}
|
||||
|
|
@ -438,7 +417,7 @@ var RuneReplacements = map[rune]string{
|
|||
graph.CommitSymbol: "o",
|
||||
}
|
||||
|
||||
func (gui *Gui) initGocui() (*gocui.Gui, error) {
|
||||
func (gui *Gui) initGocui(headless bool) (*gocui.Gui, error) {
|
||||
recordEvents := recordingEvents()
|
||||
playMode := gocui.NORMAL
|
||||
if recordEvents {
|
||||
|
|
@ -447,7 +426,7 @@ func (gui *Gui) initGocui() (*gocui.Gui, error) {
|
|||
playMode = gocui.REPLAYING
|
||||
}
|
||||
|
||||
g, err := gocui.NewGui(gocui.OutputTrue, OverlappingEdges, playMode, headless(), RuneReplacements)
|
||||
g, err := gocui.NewGui(gocui.OutputTrue, OverlappingEdges, playMode, headless, RuneReplacements)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -496,7 +475,7 @@ func (gui *Gui) viewTabMap() map[string][]context.TabView {
|
|||
|
||||
// Run: setup the gui with keybindings and start the mainloop
|
||||
func (gui *Gui) Run(startArgs types.StartArgs) error {
|
||||
g, err := gui.initGocui()
|
||||
g, err := gui.initGocui(headless())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -504,6 +483,13 @@ func (gui *Gui) Run(startArgs types.StartArgs) error {
|
|||
gui.g = g
|
||||
defer gui.g.Close()
|
||||
|
||||
// if the deadlock package wants to report a deadlock, we first need to
|
||||
// close the gui so that we can actually read what it prints.
|
||||
deadlock.Opts.LogBuf = utils.NewOnceWriter(os.Stderr, func() {
|
||||
gui.g.Close()
|
||||
})
|
||||
deadlock.Opts.Disable = !gui.Debug
|
||||
|
||||
if replaying() {
|
||||
gui.g.RecordingConfig = gocui.RecordingConfig{
|
||||
Speed: getRecordingSpeed(),
|
||||
|
|
@ -527,9 +513,9 @@ func (gui *Gui) Run(startArgs types.StartArgs) error {
|
|||
return nil
|
||||
}
|
||||
userConfig := gui.UserConfig
|
||||
gui.g.SearchEscapeKey = gui.getKey(userConfig.Keybinding.Universal.Return)
|
||||
gui.g.NextSearchMatchKey = gui.getKey(userConfig.Keybinding.Universal.NextMatch)
|
||||
gui.g.PrevSearchMatchKey = gui.getKey(userConfig.Keybinding.Universal.PrevMatch)
|
||||
gui.g.SearchEscapeKey = keybindings.GetKey(userConfig.Keybinding.Universal.Return)
|
||||
gui.g.NextSearchMatchKey = keybindings.GetKey(userConfig.Keybinding.Universal.NextMatch)
|
||||
gui.g.PrevSearchMatchKey = keybindings.GetKey(userConfig.Keybinding.Universal.PrevMatch)
|
||||
|
||||
gui.g.ShowListFooter = userConfig.Gui.ShowListFooter
|
||||
|
||||
|
|
@ -794,7 +780,7 @@ func (gui *Gui) setColorScheme() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) OnUIThread(f func() error) {
|
||||
func (gui *Gui) onUIThread(f func() error) {
|
||||
gui.g.Update(func(*gocui.Gui) error {
|
||||
return f()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -87,5 +87,18 @@ func (self *guiCommon) OpenSearch() {
|
|||
}
|
||||
|
||||
func (self *guiCommon) OnUIThread(f func() error) {
|
||||
self.gui.OnUIThread(f)
|
||||
self.gui.onUIThread(f)
|
||||
}
|
||||
|
||||
func (self *guiCommon) RenderToMainViews(opts types.RefreshMainOpts) error {
|
||||
return self.gui.refreshMainViews(opts)
|
||||
}
|
||||
|
||||
func (self *guiCommon) MainViewPairs() types.MainViewPairs {
|
||||
return types.MainViewPairs{
|
||||
Normal: self.gui.normalMainContextPair(),
|
||||
Staging: self.gui.stagingMainContextPair(),
|
||||
PatchBuilding: self.gui.patchBuildingMainContextPair(),
|
||||
MergeConflicts: self.gui.mergingMainContextPair(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,37 +2,13 @@ package gui
|
|||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/constants"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
func (gui *Gui) getKeyDisplay(name string) string {
|
||||
key := gui.getKey(name)
|
||||
return keybindings.GetKeyDisplay(key)
|
||||
}
|
||||
|
||||
func (gui *Gui) getKey(key string) types.Key {
|
||||
runeCount := utf8.RuneCountInString(key)
|
||||
if runeCount > 1 {
|
||||
binding := keybindings.Keymap[strings.ToLower(key)]
|
||||
if binding == nil {
|
||||
log.Fatalf("Unrecognized key %s for keybinding. For permitted values see %s", strings.ToLower(key), constants.Links.Docs.CustomKeybindings)
|
||||
} else {
|
||||
return binding
|
||||
}
|
||||
} else if runeCount == 1 {
|
||||
return []rune(key)[0]
|
||||
}
|
||||
log.Fatal("Key empty for keybinding: " + strings.ToLower(key))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) noPopupPanel(f func() error) func() error {
|
||||
return func() error {
|
||||
if gui.popupPanelFocused() {
|
||||
|
|
@ -68,7 +44,7 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
|
|||
}
|
||||
|
||||
opts := types.KeybindingsOpts{
|
||||
GetKey: self.getKey,
|
||||
GetKey: keybindings.GetKey,
|
||||
Config: config,
|
||||
Guards: guards,
|
||||
}
|
||||
|
|
@ -325,110 +301,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
|
|||
Modifier: gocui.ModNone,
|
||||
Handler: self.scrollUpSecondary,
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Universal.ScrollLeft),
|
||||
Handler: self.scrollLeftMain,
|
||||
Description: self.c.Tr.LcScrollLeft,
|
||||
Tag: "navigation",
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Universal.ScrollRight),
|
||||
Handler: self.scrollRightMain,
|
||||
Description: self.c.Tr.LcScrollRight,
|
||||
Tag: "navigation",
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: self.handleEscapeMerge,
|
||||
Description: self.c.Tr.ReturnToFilesPanel,
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
|
||||
Handler: self.helpers.WorkingTree.OpenMergeTool,
|
||||
Description: self.c.Tr.LcOpenMergeTool,
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Universal.Select),
|
||||
Handler: self.handlePickHunk,
|
||||
Description: self.c.Tr.PickHunk,
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Main.PickBothHunks),
|
||||
Handler: self.handlePickAllHunks,
|
||||
Description: self.c.Tr.PickAllHunks,
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevBlock),
|
||||
Handler: self.handleSelectPrevConflict,
|
||||
Description: self.c.Tr.PrevConflict,
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Universal.NextBlock),
|
||||
Handler: self.handleSelectNextConflict,
|
||||
Description: self.c.Tr.NextConflict,
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevItem),
|
||||
Handler: self.handleSelectPrevConflictHunk,
|
||||
Description: self.c.Tr.SelectPrevHunk,
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Universal.NextItem),
|
||||
Handler: self.handleSelectNextConflictHunk,
|
||||
Description: self.c.Tr.SelectNextHunk,
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.handleSelectPrevConflict,
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Universal.NextBlockAlt),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.handleSelectNextConflict,
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevItemAlt),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.handleSelectPrevConflictHunk,
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Universal.NextItemAlt),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.handleSelectNextConflictHunk,
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Universal.Edit),
|
||||
Handler: self.handleMergeConflictEditFileAtLine,
|
||||
Description: self.c.Tr.LcEditFile,
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Universal.OpenFile),
|
||||
Handler: self.handleMergeConflictOpenFileAtLine,
|
||||
Description: self.c.Tr.LcOpenFile,
|
||||
},
|
||||
{
|
||||
ViewName: "merging",
|
||||
Key: opts.GetKey(opts.Config.Universal.Undo),
|
||||
Handler: self.handleMergeConflictUndo,
|
||||
Description: self.c.Tr.LcUndo,
|
||||
},
|
||||
{
|
||||
ViewName: "status",
|
||||
Key: gocui.MouseLeft,
|
||||
|
|
|
|||
|
|
@ -2,12 +2,16 @@ package keybindings
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/constants"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
var KeyMapReversed = map[gocui.Key]string{
|
||||
var keyMapReversed = map[gocui.Key]string{
|
||||
gocui.KeyF1: "f1",
|
||||
gocui.KeyF2: "f2",
|
||||
gocui.KeyF3: "f3",
|
||||
|
|
@ -66,11 +70,11 @@ var KeyMapReversed = map[gocui.Key]string{
|
|||
gocui.KeyCtrl5: "ctrl+5", // ctrl+]
|
||||
gocui.KeyCtrl6: "ctrl+6",
|
||||
gocui.KeyCtrl8: "ctrl+8",
|
||||
gocui.MouseWheelUp: "mouse wheel up",
|
||||
gocui.MouseWheelDown: "mouse wheel down",
|
||||
gocui.MouseWheelUp: "mouse wheel ▲",
|
||||
gocui.MouseWheelDown: "mouse wheel ▼",
|
||||
}
|
||||
|
||||
var Keymap = map[string]types.Key{
|
||||
var keyMap = map[string]types.Key{
|
||||
"<c-a>": gocui.KeyCtrlA,
|
||||
"<c-b>": gocui.KeyCtrlB,
|
||||
"<c-c>": gocui.KeyCtrlC,
|
||||
|
|
@ -142,14 +146,18 @@ var Keymap = map[string]types.Key{
|
|||
"<right>": gocui.KeyArrowRight,
|
||||
}
|
||||
|
||||
func GetKeyDisplay(key types.Key) string {
|
||||
func Label(name string) string {
|
||||
return LabelFromKey(GetKey(name))
|
||||
}
|
||||
|
||||
func LabelFromKey(key types.Key) string {
|
||||
keyInt := 0
|
||||
|
||||
switch key := key.(type) {
|
||||
case rune:
|
||||
keyInt = int(key)
|
||||
case gocui.Key:
|
||||
value, ok := KeyMapReversed[key]
|
||||
value, ok := keyMapReversed[key]
|
||||
if ok {
|
||||
return value
|
||||
}
|
||||
|
|
@ -158,3 +166,19 @@ func GetKeyDisplay(key types.Key) string {
|
|||
|
||||
return fmt.Sprintf("%c", keyInt)
|
||||
}
|
||||
|
||||
func GetKey(key string) types.Key {
|
||||
runeCount := utf8.RuneCountInString(key)
|
||||
if runeCount > 1 {
|
||||
binding := keyMap[strings.ToLower(key)]
|
||||
if binding == nil {
|
||||
log.Fatalf("Unrecognized key %s for keybinding. For permitted values see %s", strings.ToLower(key), constants.Links.Docs.CustomKeybindings)
|
||||
} else {
|
||||
return binding
|
||||
}
|
||||
} else if runeCount == 1 {
|
||||
return []rune(key)[0]
|
||||
}
|
||||
log.Fatal("Key empty for keybinding: " + strings.ToLower(key))
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ func (gui *Gui) filesListContext() *context.WorkingTreeContext {
|
|||
return []string{line}
|
||||
})
|
||||
},
|
||||
OnFocusWrapper(gui.onFocusFile),
|
||||
nil,
|
||||
gui.withDiffModeCheck(gui.filesRenderToMain),
|
||||
nil,
|
||||
gui.c,
|
||||
|
|
@ -270,7 +270,10 @@ func (gui *Gui) suggestionsListContext() *context.SuggestionsContext {
|
|||
},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
func(types.OnFocusLostOpts) error {
|
||||
gui.deactivateConfirmationPrompt()
|
||||
return nil
|
||||
},
|
||||
gui.c,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,114 +1,48 @@
|
|||
package gui
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type viewUpdateOpts struct {
|
||||
title string
|
||||
|
||||
task updateTask
|
||||
}
|
||||
|
||||
type refreshMainOpts struct {
|
||||
pair MainContextPair
|
||||
main *viewUpdateOpts
|
||||
secondary *viewUpdateOpts
|
||||
}
|
||||
|
||||
type updateTask interface {
|
||||
IsUpdateTask()
|
||||
}
|
||||
|
||||
type renderStringTask struct {
|
||||
str string
|
||||
}
|
||||
|
||||
func (t *renderStringTask) IsUpdateTask() {}
|
||||
|
||||
func NewRenderStringTask(str string) *renderStringTask {
|
||||
return &renderStringTask{str: str}
|
||||
}
|
||||
|
||||
type renderStringWithoutScrollTask struct {
|
||||
str string
|
||||
}
|
||||
|
||||
func (t *renderStringWithoutScrollTask) IsUpdateTask() {}
|
||||
|
||||
func NewRenderStringWithoutScrollTask(str string) *renderStringWithoutScrollTask {
|
||||
return &renderStringWithoutScrollTask{str: str}
|
||||
}
|
||||
|
||||
type runCommandTask struct {
|
||||
cmd *exec.Cmd
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (t *runCommandTask) IsUpdateTask() {}
|
||||
|
||||
func NewRunCommandTask(cmd *exec.Cmd) *runCommandTask {
|
||||
return &runCommandTask{cmd: cmd}
|
||||
}
|
||||
|
||||
func NewRunCommandTaskWithPrefix(cmd *exec.Cmd, prefix string) *runCommandTask {
|
||||
return &runCommandTask{cmd: cmd, prefix: prefix}
|
||||
}
|
||||
|
||||
type runPtyTask struct {
|
||||
cmd *exec.Cmd
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (t *runPtyTask) IsUpdateTask() {}
|
||||
|
||||
func NewRunPtyTask(cmd *exec.Cmd) *runPtyTask {
|
||||
return &runPtyTask{cmd: cmd}
|
||||
}
|
||||
|
||||
// currently unused
|
||||
// func (gui *Gui) createRunPtyTaskWithPrefix(cmd *exec.Cmd, prefix string) *runPtyTask {
|
||||
// return &runPtyTask{cmd: cmd, prefix: prefix}
|
||||
// }
|
||||
|
||||
func (gui *Gui) runTaskForView(view *gocui.View, task updateTask) error {
|
||||
func (gui *Gui) runTaskForView(view *gocui.View, task types.UpdateTask) error {
|
||||
switch v := task.(type) {
|
||||
case *renderStringTask:
|
||||
return gui.newStringTask(view, v.str)
|
||||
case *types.RenderStringTask:
|
||||
return gui.newStringTask(view, v.Str)
|
||||
|
||||
case *renderStringWithoutScrollTask:
|
||||
return gui.newStringTaskWithoutScroll(view, v.str)
|
||||
case *types.RenderStringWithoutScrollTask:
|
||||
return gui.newStringTaskWithoutScroll(view, v.Str)
|
||||
|
||||
case *runCommandTask:
|
||||
return gui.newCmdTask(view, v.cmd, v.prefix)
|
||||
case *types.RenderStringWithScrollTask:
|
||||
return gui.newStringTaskWithScroll(view, v.Str, v.OriginX, v.OriginY)
|
||||
|
||||
case *runPtyTask:
|
||||
return gui.newPtyTask(view, v.cmd, v.prefix)
|
||||
case *types.RunCommandTask:
|
||||
return gui.newCmdTask(view, v.Cmd, v.Prefix)
|
||||
|
||||
case *types.RunPtyTask:
|
||||
return gui.newPtyTask(view, v.Cmd, v.Prefix)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) moveMainContextPairToTop(pair MainContextPair) {
|
||||
gui.setWindowContext(pair.main)
|
||||
gui.moveToTopOfWindow(pair.main)
|
||||
if pair.secondary != nil {
|
||||
gui.setWindowContext(pair.secondary)
|
||||
gui.moveToTopOfWindow(pair.secondary)
|
||||
func (gui *Gui) moveMainContextPairToTop(pair types.MainContextPair) {
|
||||
gui.setWindowContext(pair.Main)
|
||||
gui.moveToTopOfWindow(pair.Main)
|
||||
if pair.Secondary != nil {
|
||||
gui.setWindowContext(pair.Secondary)
|
||||
gui.moveToTopOfWindow(pair.Secondary)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshMainView(opts *viewUpdateOpts, context types.Context) error {
|
||||
func (gui *Gui) RefreshMainView(opts *types.ViewUpdateOpts, context types.Context) error {
|
||||
view := context.GetView()
|
||||
|
||||
if opts.title != "" {
|
||||
view.Title = opts.title
|
||||
if opts.Title != "" {
|
||||
view.Title = opts.Title
|
||||
}
|
||||
|
||||
if err := gui.runTaskForView(view, opts.task); err != nil {
|
||||
if err := gui.runTaskForView(view, opts.Task); err != nil {
|
||||
gui.c.Log.Error(err)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -116,41 +50,36 @@ func (gui *Gui) refreshMainView(opts *viewUpdateOpts, context types.Context) err
|
|||
return nil
|
||||
}
|
||||
|
||||
type MainContextPair struct {
|
||||
main types.Context
|
||||
secondary types.Context
|
||||
func (gui *Gui) normalMainContextPair() types.MainContextPair {
|
||||
return types.NewMainContextPair(
|
||||
gui.State.Contexts.Normal,
|
||||
gui.State.Contexts.NormalSecondary,
|
||||
)
|
||||
}
|
||||
|
||||
func (gui *Gui) normalMainContextPair() MainContextPair {
|
||||
return MainContextPair{
|
||||
main: gui.State.Contexts.Normal,
|
||||
secondary: gui.State.Contexts.NormalSecondary,
|
||||
}
|
||||
func (gui *Gui) stagingMainContextPair() types.MainContextPair {
|
||||
return types.NewMainContextPair(
|
||||
gui.State.Contexts.Staging,
|
||||
gui.State.Contexts.StagingSecondary,
|
||||
)
|
||||
}
|
||||
|
||||
func (gui *Gui) stagingMainContextPair() MainContextPair {
|
||||
return MainContextPair{
|
||||
main: gui.State.Contexts.Staging,
|
||||
secondary: gui.State.Contexts.StagingSecondary,
|
||||
}
|
||||
func (gui *Gui) patchBuildingMainContextPair() types.MainContextPair {
|
||||
return types.NewMainContextPair(
|
||||
gui.State.Contexts.CustomPatchBuilder,
|
||||
gui.State.Contexts.CustomPatchBuilderSecondary,
|
||||
)
|
||||
}
|
||||
|
||||
func (gui *Gui) patchBuildingMainContextPair() MainContextPair {
|
||||
return MainContextPair{
|
||||
main: gui.State.Contexts.CustomPatchBuilder,
|
||||
secondary: gui.State.Contexts.CustomPatchBuilderSecondary,
|
||||
}
|
||||
func (gui *Gui) mergingMainContextPair() types.MainContextPair {
|
||||
return types.NewMainContextPair(
|
||||
gui.State.Contexts.MergeConflicts,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
func (gui *Gui) mergingMainContextPair() MainContextPair {
|
||||
return MainContextPair{
|
||||
main: gui.State.Contexts.Merging,
|
||||
secondary: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) allMainContextPairs() []MainContextPair {
|
||||
return []MainContextPair{
|
||||
func (gui *Gui) allMainContextPairs() []types.MainContextPair {
|
||||
return []types.MainContextPair{
|
||||
gui.normalMainContextPair(),
|
||||
gui.stagingMainContextPair(),
|
||||
gui.patchBuildingMainContextPair(),
|
||||
|
|
@ -158,34 +87,34 @@ func (gui *Gui) allMainContextPairs() []MainContextPair {
|
|||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshMainViews(opts refreshMainOpts) error {
|
||||
func (gui *Gui) refreshMainViews(opts types.RefreshMainOpts) error {
|
||||
// need to reset scroll positions of all other main views
|
||||
for _, pair := range gui.allMainContextPairs() {
|
||||
if pair.main != opts.pair.main {
|
||||
_ = pair.main.GetView().SetOrigin(0, 0)
|
||||
if pair.Main != opts.Pair.Main {
|
||||
_ = pair.Main.GetView().SetOrigin(0, 0)
|
||||
}
|
||||
if pair.secondary != nil && pair.secondary != opts.pair.secondary {
|
||||
_ = pair.secondary.GetView().SetOrigin(0, 0)
|
||||
if pair.Secondary != nil && pair.Secondary != opts.Pair.Secondary {
|
||||
_ = pair.Secondary.GetView().SetOrigin(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.main != nil {
|
||||
if err := gui.refreshMainView(opts.main, opts.pair.main); err != nil {
|
||||
if opts.Main != nil {
|
||||
if err := gui.RefreshMainView(opts.Main, opts.Pair.Main); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.secondary != nil {
|
||||
if err := gui.refreshMainView(opts.secondary, opts.pair.secondary); err != nil {
|
||||
if opts.Secondary != nil {
|
||||
if err := gui.RefreshMainView(opts.Secondary, opts.Pair.Secondary); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if opts.pair.secondary != nil {
|
||||
opts.pair.secondary.GetView().Clear()
|
||||
} else if opts.Pair.Secondary != nil {
|
||||
opts.Pair.Secondary.GetView().Clear()
|
||||
}
|
||||
|
||||
gui.moveMainContextPairToTop(opts.pair)
|
||||
gui.moveMainContextPairToTop(opts.Pair)
|
||||
|
||||
gui.splitMainPanel(opts.secondary != nil)
|
||||
gui.splitMainPanel(opts.Secondary != nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package gui
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
|
|
@ -13,9 +14,9 @@ func (gui *Gui) getMenuOptions() map[string]string {
|
|||
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||
|
||||
return map[string]string{
|
||||
gui.getKeyDisplay(keybindingConfig.Universal.Return): gui.c.Tr.LcClose,
|
||||
fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
|
||||
gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.c.Tr.LcExecute,
|
||||
keybindings.Label(keybindingConfig.Universal.Return): gui.c.Tr.LcClose,
|
||||
fmt.Sprintf("%s %s", keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
|
||||
keybindings.Label(keybindingConfig.Universal.Select): gui.c.Tr.LcExecute,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,319 +0,0 @@
|
|||
// though this panel is called the merge panel, it's really going to use the main panel. This may change in the future
|
||||
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
func (gui *Gui) handleSelectPrevConflictHunk() error {
|
||||
return gui.withMergeConflictLock(func() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
gui.State.Panels.Merging.SelectPrevConflictHunk()
|
||||
return gui.renderConflictsWithFocus()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSelectNextConflictHunk() error {
|
||||
return gui.withMergeConflictLock(func() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
gui.State.Panels.Merging.SelectNextConflictHunk()
|
||||
return gui.renderConflictsWithFocus()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSelectNextConflict() error {
|
||||
return gui.withMergeConflictLock(func() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
gui.State.Panels.Merging.SelectNextConflict()
|
||||
return gui.renderConflictsWithFocus()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSelectPrevConflict() error {
|
||||
return gui.withMergeConflictLock(func() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
gui.State.Panels.Merging.SelectPrevConflict()
|
||||
return gui.renderConflictsWithFocus()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMergeConflictUndo() error {
|
||||
state := gui.State.Panels.Merging
|
||||
|
||||
ok := state.Undo()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.c.LogAction("Restoring file to previous state")
|
||||
gui.LogCommand("Undoing last conflict resolution", false)
|
||||
if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.renderConflictsWithFocus()
|
||||
}
|
||||
|
||||
func (gui *Gui) handlePickHunk() error {
|
||||
return gui.withMergeConflictLock(func() error {
|
||||
ok, err := gui.resolveConflict(gui.State.Panels.Merging.Selection())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if gui.State.Panels.Merging.AllConflictsResolved() {
|
||||
return gui.onLastConflictResolved()
|
||||
}
|
||||
|
||||
return gui.renderConflictsWithFocus()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handlePickAllHunks() error {
|
||||
return gui.withMergeConflictLock(func() error {
|
||||
ok, err := gui.resolveConflict(mergeconflicts.ALL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if gui.State.Panels.Merging.AllConflictsResolved() {
|
||||
return gui.onLastConflictResolved()
|
||||
}
|
||||
|
||||
return gui.renderConflictsWithFocus()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error) {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
|
||||
state := gui.State.Panels.Merging
|
||||
|
||||
ok, content, err := state.ContentAfterConflictResolve(selection)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var logStr string
|
||||
switch selection {
|
||||
case mergeconflicts.TOP:
|
||||
logStr = "Picking top hunk"
|
||||
case mergeconflicts.MIDDLE:
|
||||
logStr = "Picking middle hunk"
|
||||
case mergeconflicts.BOTTOM:
|
||||
logStr = "Picking bottom hunk"
|
||||
case mergeconflicts.ALL:
|
||||
logStr = "Picking all hunks"
|
||||
}
|
||||
gui.c.LogAction("Resolve merge conflict")
|
||||
gui.LogCommand(logStr, false)
|
||||
state.PushContent(content)
|
||||
return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0o644)
|
||||
}
|
||||
|
||||
// precondition: we actually have conflicts to render
|
||||
func (gui *Gui) renderConflicts(hasFocus bool) error {
|
||||
state := gui.State.Panels.Merging.State
|
||||
content := mergeconflicts.ColoredConflictFile(state, hasFocus)
|
||||
|
||||
if !gui.State.Panels.Merging.UserVerticalScrolling {
|
||||
// TODO: find a way to not have to do this OnUIThread thing. Why doesn't it work
|
||||
// without it given that we're calling the 'no scroll' variant below?
|
||||
gui.OnUIThread(func() error {
|
||||
gui.State.Panels.Merging.Lock()
|
||||
defer gui.State.Panels.Merging.Unlock()
|
||||
|
||||
if !state.Active() {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.centerYPos(gui.Views.Merging, state.GetConflictMiddle())
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.mergingMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
task: NewRenderStringWithoutScrollTask(content),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) renderConflictsWithFocus() error {
|
||||
return gui.renderConflicts(true)
|
||||
}
|
||||
|
||||
func (gui *Gui) renderConflictsWithLock(hasFocus bool) error {
|
||||
return gui.withMergeConflictLock(func() error {
|
||||
return gui.renderConflicts(hasFocus)
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) centerYPos(view *gocui.View, y int) {
|
||||
ox, _ := view.Origin()
|
||||
_, height := view.Size()
|
||||
newOriginY := int(math.Max(0, float64(y-(height/2))))
|
||||
_ = view.SetOrigin(ox, newOriginY)
|
||||
}
|
||||
|
||||
func (gui *Gui) getMergingOptions() map[string]string {
|
||||
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||
|
||||
return map[string]string{
|
||||
fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcSelectHunk,
|
||||
fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevBlock), gui.getKeyDisplay(keybindingConfig.Universal.NextBlock)): gui.c.Tr.LcNavigateConflicts,
|
||||
gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.c.Tr.LcPickHunk,
|
||||
gui.getKeyDisplay(keybindingConfig.Main.PickBothHunks): gui.c.Tr.LcPickAllHunks,
|
||||
gui.getKeyDisplay(keybindingConfig.Universal.Undo): gui.c.Tr.LcUndo,
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleEscapeMerge() error {
|
||||
if err := gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.escapeMerge()
|
||||
}
|
||||
|
||||
func (gui *Gui) onLastConflictResolved() error {
|
||||
// as part of refreshing files, we handle the situation where a file has had
|
||||
// its merge conflicts resolved.
|
||||
return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
|
||||
}
|
||||
|
||||
func (gui *Gui) resetMergeState() {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
gui.State.Panels.Merging.Reset()
|
||||
}
|
||||
|
||||
func (gui *Gui) setMergeState(path string) (bool, error) {
|
||||
content, err := gui.git.File.Cat(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
gui.State.Panels.Merging.SetContent(content, path)
|
||||
|
||||
return !gui.State.Panels.Merging.NoConflicts(), nil
|
||||
}
|
||||
|
||||
func (gui *Gui) setMergeStateWithLock(path string) (bool, error) {
|
||||
gui.State.Panels.Merging.Lock()
|
||||
defer gui.State.Panels.Merging.Unlock()
|
||||
|
||||
return gui.setMergeState(path)
|
||||
}
|
||||
|
||||
func (gui *Gui) resetMergeStateWithLock() {
|
||||
gui.State.Panels.Merging.Lock()
|
||||
defer gui.State.Panels.Merging.Unlock()
|
||||
|
||||
gui.resetMergeState()
|
||||
}
|
||||
|
||||
func (gui *Gui) escapeMerge() error {
|
||||
gui.resetMergeState()
|
||||
|
||||
// doing this in separate UI thread so that we're not still holding the lock by the time refresh the file
|
||||
gui.OnUIThread(func() error {
|
||||
return gui.c.PushContext(gui.State.Contexts.Files)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderingConflicts() bool {
|
||||
currentView := gui.g.CurrentView()
|
||||
if currentView != gui.Views.Merging && currentView != gui.Views.Files {
|
||||
return false
|
||||
}
|
||||
|
||||
return gui.State.Panels.Merging.Active()
|
||||
}
|
||||
|
||||
func (gui *Gui) withMergeConflictLock(f func() error) error {
|
||||
gui.State.Panels.Merging.Lock()
|
||||
defer gui.State.Panels.Merging.Unlock()
|
||||
|
||||
return f()
|
||||
}
|
||||
|
||||
func (gui *Gui) takeOverMergeConflictScrolling() {
|
||||
gui.State.Panels.Merging.UserVerticalScrolling = false
|
||||
}
|
||||
|
||||
func (gui *Gui) setConflictsAndRender(path string, hasFocus bool) (bool, error) {
|
||||
hasConflicts, err := gui.setMergeState(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if hasConflicts {
|
||||
return true, gui.renderConflicts(hasFocus)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (gui *Gui) setConflictsAndRenderWithLock(path string, hasFocus bool) (bool, error) {
|
||||
gui.State.Panels.Merging.Lock()
|
||||
defer gui.State.Panels.Merging.Unlock()
|
||||
|
||||
return gui.setConflictsAndRender(path, hasFocus)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMergeConflictEditFileAtLine() error {
|
||||
file := gui.getSelectedFile()
|
||||
if file == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
lineNumber := gui.State.Panels.Merging.GetSelectedLine()
|
||||
return gui.helpers.Files.EditFileAtLine(file.GetPath(), lineNumber)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMergeConflictOpenFileAtLine() error {
|
||||
file := gui.getSelectedFile()
|
||||
if file == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
lineNumber := gui.State.Panels.Merging.GetSelectedLine()
|
||||
return gui.helpers.Files.OpenFileAtLine(file.GetPath(), lineNumber)
|
||||
}
|
||||
|
|
@ -1,15 +1,11 @@
|
|||
package mergeconflicts
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// State represents the selection state of the merge conflict context.
|
||||
type State struct {
|
||||
sync.Mutex
|
||||
|
||||
// path of the file with the conflicts
|
||||
path string
|
||||
|
||||
|
|
@ -28,7 +24,6 @@ type State struct {
|
|||
|
||||
func NewState() *State {
|
||||
return &State{
|
||||
Mutex: sync.Mutex{},
|
||||
conflictIndex: 0,
|
||||
selectionIndex: 0,
|
||||
conflicts: []*mergeConflict{},
|
||||
|
|
@ -151,6 +146,12 @@ func (s *State) Reset() {
|
|||
s.path = ""
|
||||
}
|
||||
|
||||
// we're not resetting selectedIndex here because the user typically would want
|
||||
// to pick either all top hunks or all bottom hunks so we retain that selection
|
||||
func (s *State) ResetConflictSelection() {
|
||||
s.conflictIndex = 0
|
||||
}
|
||||
|
||||
func (s *State) Active() bool {
|
||||
return s.path != ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func (gui *Gui) getBindings(context types.Context) []*types.Binding {
|
|||
bindings = append(customBindings, bindings...)
|
||||
|
||||
for _, binding := range bindings {
|
||||
if keybindings.GetKeyDisplay(binding.Key) != "" && binding.Description != "" {
|
||||
if keybindings.LabelFromKey(binding.Key) != "" && binding.Description != "" {
|
||||
if binding.ViewName == "" {
|
||||
bindingsGlobal = append(bindingsGlobal, binding)
|
||||
} else if binding.Tag == "navigation" {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package popup
|
|||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
|
|
@ -10,12 +9,13 @@ import (
|
|||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type PopupHandler struct {
|
||||
*common.Common
|
||||
index int
|
||||
sync.Mutex
|
||||
deadlock.Mutex
|
||||
createPopupPanelFn func(types.CreatePopupPanelOpts) error
|
||||
onErrorFn func() error
|
||||
popContextFn func() error
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package presentation
|
|||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
|
|
@ -14,6 +13,7 @@ import (
|
|||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/kyokomi/emoji/v2"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type pipeSetCacheKey struct {
|
||||
|
|
@ -23,7 +23,7 @@ type pipeSetCacheKey struct {
|
|||
|
||||
var (
|
||||
pipeSetCache = make(map[pipeSetCacheKey][][]*graph.Pipe)
|
||||
mutex sync.Mutex
|
||||
mutex deadlock.Mutex
|
||||
)
|
||||
|
||||
type bisectBounds struct {
|
||||
|
|
|
|||
|
|
@ -107,13 +107,13 @@ func (gui *Gui) handleCreateRecentReposMenu() error {
|
|||
|
||||
func (gui *Gui) handleShowAllBranchLogs() error {
|
||||
cmdObj := gui.git.Branch.AllBranchesLogCmdObj()
|
||||
task := NewRunPtyTask(cmdObj.GetCmd())
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.normalMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
title: gui.c.Tr.LogTitle,
|
||||
task: task,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.c.Tr.LogTitle,
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,23 @@
|
|||
package gui
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
func (gui *Gui) reflogCommitsRenderToMain() error {
|
||||
commit := gui.State.Contexts.ReflogCommits.GetSelected()
|
||||
var task updateTask
|
||||
var task types.UpdateTask
|
||||
if commit == nil {
|
||||
task = NewRenderStringTask("No reflog history")
|
||||
task = types.NewRenderStringTask("No reflog history")
|
||||
} else {
|
||||
cmdObj := gui.git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
|
||||
|
||||
task = NewRunPtyTask(cmdObj.GetCmd())
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.normalMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
title: "Reflog Entry",
|
||||
task: task,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Reflog Entry",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,17 +22,18 @@ import (
|
|||
|
||||
func getScopeNames(scopes []types.RefreshableView) []string {
|
||||
scopeNameMap := map[types.RefreshableView]string{
|
||||
types.COMMITS: "commits",
|
||||
types.BRANCHES: "branches",
|
||||
types.FILES: "files",
|
||||
types.SUBMODULES: "submodules",
|
||||
types.STASH: "stash",
|
||||
types.REFLOG: "reflog",
|
||||
types.TAGS: "tags",
|
||||
types.REMOTES: "remotes",
|
||||
types.STATUS: "status",
|
||||
types.BISECT_INFO: "bisect",
|
||||
types.STAGING: "staging",
|
||||
types.COMMITS: "commits",
|
||||
types.BRANCHES: "branches",
|
||||
types.FILES: "files",
|
||||
types.SUBMODULES: "submodules",
|
||||
types.STASH: "stash",
|
||||
types.REFLOG: "reflog",
|
||||
types.TAGS: "tags",
|
||||
types.REMOTES: "remotes",
|
||||
types.STATUS: "status",
|
||||
types.BISECT_INFO: "bisect",
|
||||
types.STAGING: "staging",
|
||||
types.MERGE_CONFLICTS: "mergeConflicts",
|
||||
}
|
||||
|
||||
return slices.Map(scopes, func(scope types.RefreshableView) string {
|
||||
|
|
@ -138,6 +139,10 @@ func (gui *Gui) Refresh(options types.RefreshOptions) error {
|
|||
refresh(func() { _ = gui.refreshPatchBuildingPanel(types.OnFocusOpts{}) })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.MERGE_CONFLICTS) || scopeSet.Includes(types.FILES) {
|
||||
refresh(func() { _ = gui.refreshMergeState() })
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
gui.refreshStatus()
|
||||
|
|
@ -148,7 +153,7 @@ func (gui *Gui) Refresh(options types.RefreshOptions) error {
|
|||
}
|
||||
|
||||
if options.Mode == types.BLOCK_UI {
|
||||
gui.OnUIThread(func() error {
|
||||
gui.c.OnUIThread(func() error {
|
||||
f()
|
||||
return nil
|
||||
})
|
||||
|
|
@ -323,21 +328,15 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
|
|||
gui.Mutexes.RefreshingFilesMutex.Unlock()
|
||||
}()
|
||||
|
||||
prevSelectedPath := gui.getSelectedPath()
|
||||
|
||||
if err := gui.refreshStateSubmoduleConfigs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.refreshMergeState(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.refreshStateFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.OnUIThread(func() error {
|
||||
gui.c.OnUIThread(func() error {
|
||||
if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Submodules); err != nil {
|
||||
gui.c.Log.Error(err)
|
||||
}
|
||||
|
|
@ -346,14 +345,6 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
|
|||
gui.c.Log.Error(err)
|
||||
}
|
||||
|
||||
if gui.currentContext().GetKey() == context.FILES_CONTEXT_KEY {
|
||||
currentSelectedPath := gui.getSelectedPath()
|
||||
alreadySelected := prevSelectedPath != "" && currentSelectedPath == prevSelectedPath
|
||||
if !alreadySelected {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
|
|
@ -361,20 +352,20 @@ func (gui *Gui) refreshFilesAndSubmodules() error {
|
|||
}
|
||||
|
||||
func (gui *Gui) refreshMergeState() error {
|
||||
gui.State.Panels.Merging.Lock()
|
||||
defer gui.State.Panels.Merging.Unlock()
|
||||
gui.State.Contexts.MergeConflicts.GetMutex().Lock()
|
||||
defer gui.State.Contexts.MergeConflicts.GetMutex().Unlock()
|
||||
|
||||
if gui.currentContext().GetKey() != context.MERGING_MAIN_CONTEXT_KEY {
|
||||
if gui.currentContext().GetKey() != context.MERGE_CONFLICTS_CONTEXT_KEY {
|
||||
return nil
|
||||
}
|
||||
|
||||
hasConflicts, err := gui.setConflictsAndRender(gui.State.Panels.Merging.GetPath(), true)
|
||||
hasConflicts, err := gui.helpers.MergeConflicts.SetConflictsAndRender(gui.State.Contexts.MergeConflicts.GetState().GetPath(), true)
|
||||
if err != nil {
|
||||
return gui.c.Error(err)
|
||||
}
|
||||
|
||||
if !hasConflicts {
|
||||
return gui.escapeMerge()
|
||||
return gui.helpers.MergeConflicts.EscapeMerge()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -426,7 +417,7 @@ func (gui *Gui) refreshStateFiles() error {
|
|||
}
|
||||
|
||||
if gui.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE && conflictFileCount == 0 && prevConflictFileCount > 0 {
|
||||
gui.OnUIThread(func() error { return gui.helpers.MergeAndRebase.PromptToContinueRebase() })
|
||||
gui.c.OnUIThread(func() error { return gui.helpers.MergeAndRebase.PromptToContinueRebase() })
|
||||
}
|
||||
|
||||
fileTreeViewModel.RWMutex.Lock()
|
||||
|
|
@ -627,15 +618,15 @@ func (gui *Gui) refreshStagingPanel(focusOpts types.OnFocusOpts) error {
|
|||
return gui.c.PushContext(mainContext, focusOpts)
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.stagingMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
task: NewRenderStringWithoutScrollTask(mainContent),
|
||||
title: gui.Tr.UnstagedChanges,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Staging,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(mainContent),
|
||||
Title: gui.Tr.UnstagedChanges,
|
||||
},
|
||||
secondary: &viewUpdateOpts{
|
||||
task: NewRenderStringWithoutScrollTask(secondaryContent),
|
||||
title: gui.Tr.StagedChanges,
|
||||
Secondary: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(secondaryContent),
|
||||
Title: gui.Tr.StagedChanges,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -689,15 +680,34 @@ func (gui *Gui) refreshPatchBuildingPanel(opts types.OnFocusOpts) error {
|
|||
|
||||
mainContent := context.GetContentToRender(true)
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.patchBuildingMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
task: NewRenderStringWithoutScrollTask(mainContent),
|
||||
title: gui.Tr.Patch,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().PatchBuilding,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(mainContent),
|
||||
Title: gui.Tr.Patch,
|
||||
},
|
||||
secondary: &viewUpdateOpts{
|
||||
task: NewRenderStringWithoutScrollTask(secondaryDiff),
|
||||
title: gui.Tr.CustomPatch,
|
||||
Secondary: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(secondaryDiff),
|
||||
Title: gui.Tr.CustomPatch,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshMergePanel(isFocused bool) error {
|
||||
content := gui.State.Contexts.MergeConflicts.GetContentToRender(isFocused)
|
||||
|
||||
var task types.UpdateTask
|
||||
if gui.State.Contexts.MergeConflicts.IsUserScrolling() {
|
||||
task = types.NewRenderStringWithoutScrollTask(content)
|
||||
} else {
|
||||
originY := gui.State.Contexts.MergeConflicts.GetOriginY()
|
||||
task = types.NewRenderStringWithScrollTask(content, 0, originY)
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().MergeConflicts,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
package gui
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
func (gui *Gui) remoteBranchesRenderToMain() error {
|
||||
var task updateTask
|
||||
var task types.UpdateTask
|
||||
remoteBranch := gui.State.Contexts.RemoteBranches.GetSelected()
|
||||
if remoteBranch == nil {
|
||||
task = NewRenderStringTask("No branches for this remote")
|
||||
task = types.NewRenderStringTask("No branches for this remote")
|
||||
} else {
|
||||
cmdObj := gui.git.Branch.GetGraphCmdObj(remoteBranch.FullRefName())
|
||||
task = NewRunCommandTask(cmdObj.GetCmd())
|
||||
task = types.NewRunCommandTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.normalMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
title: "Remote Branch",
|
||||
task: task,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Remote Branch",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,24 +5,25 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) remotesRenderToMain() error {
|
||||
var task updateTask
|
||||
var task types.UpdateTask
|
||||
remote := gui.State.Contexts.Remotes.GetSelected()
|
||||
if remote == nil {
|
||||
task = NewRenderStringTask("No remotes")
|
||||
task = types.NewRenderStringTask("No remotes")
|
||||
} else {
|
||||
task = NewRenderStringTask(fmt.Sprintf("%s\nUrls:\n%s", style.FgGreen.Sprint(remote.Name), strings.Join(remote.Urls, "\n")))
|
||||
task = types.NewRenderStringTask(fmt.Sprintf("%s\nUrls:\n%s", style.FgGreen.Sprint(remote.Name), strings.Join(remote.Urls, "\n")))
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.normalMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
title: "Remote",
|
||||
task: task,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Remote",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package gui
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
||||
|
|
@ -52,7 +53,7 @@ func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, in
|
|||
fmt.Sprintf(
|
||||
"no matches for '%s' %s",
|
||||
gui.State.Searching.searchString,
|
||||
theme.OptionsFgColor.Sprintf("%s: exit search mode", gui.getKeyDisplay(keybindingConfig.Universal.Return)),
|
||||
theme.OptionsFgColor.Sprintf("%s: exit search mode", keybindings.Label(keybindingConfig.Universal.Return)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -65,9 +66,9 @@ func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, in
|
|||
total,
|
||||
theme.OptionsFgColor.Sprintf(
|
||||
"%s: next match, %s: previous match, %s: exit search mode",
|
||||
gui.getKeyDisplay(keybindingConfig.Universal.NextMatch),
|
||||
gui.getKeyDisplay(keybindingConfig.Universal.PrevMatch),
|
||||
gui.getKeyDisplay(keybindingConfig.Universal.Return),
|
||||
keybindings.Label(keybindingConfig.Universal.NextMatch),
|
||||
keybindings.Label(keybindingConfig.Universal.PrevMatch),
|
||||
keybindings.Label(keybindingConfig.Universal.Return),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,11 +23,10 @@ func NewClient(
|
|||
git *commands.GitCommand,
|
||||
contexts *context.ContextTree,
|
||||
helpers *helpers.Helpers,
|
||||
getKey func(string) types.Key,
|
||||
) *Client {
|
||||
sessionStateLoader := NewSessionStateLoader(contexts, helpers)
|
||||
handlerCreator := NewHandlerCreator(c, os, git, sessionStateLoader)
|
||||
keybindingCreator := NewKeybindingCreator(contexts, getKey)
|
||||
keybindingCreator := NewKeybindingCreator(contexts)
|
||||
customCommands := c.UserConfig.CustomCommands
|
||||
|
||||
return &Client{
|
||||
|
|
|
|||
|
|
@ -8,19 +8,18 @@ import (
|
|||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
// KeybindingCreator takes a custom command along with its handler and returns a corresponding keybinding
|
||||
type KeybindingCreator struct {
|
||||
contexts *context.ContextTree
|
||||
getKey func(string) types.Key
|
||||
}
|
||||
|
||||
func NewKeybindingCreator(contexts *context.ContextTree, getKey func(string) types.Key) *KeybindingCreator {
|
||||
func NewKeybindingCreator(contexts *context.ContextTree) *KeybindingCreator {
|
||||
return &KeybindingCreator{
|
||||
contexts: contexts,
|
||||
getKey: getKey,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +40,7 @@ func (self *KeybindingCreator) call(customCommand config.CustomCommand, handler
|
|||
|
||||
return &types.Binding{
|
||||
ViewName: viewName,
|
||||
Key: self.getKey(customCommand.Key),
|
||||
Key: keybindings.GetKey(customCommand.Key),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: handler,
|
||||
Description: description,
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
package gui
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
func (gui *Gui) stashRenderToMain() error {
|
||||
var task updateTask
|
||||
var task types.UpdateTask
|
||||
stashEntry := gui.State.Contexts.Stash.GetSelected()
|
||||
if stashEntry == nil {
|
||||
task = NewRenderStringTask(gui.c.Tr.NoStashEntries)
|
||||
task = types.NewRenderStringTask(gui.c.Tr.NoStashEntries)
|
||||
} else {
|
||||
task = NewRunPtyTask(gui.git.Stash.ShowStashEntryCmdObj(stashEntry.Index).GetCmd())
|
||||
task = types.NewRunPtyTask(gui.git.Stash.ShowStashEntryCmdObj(stashEntry.Index).GetCmd())
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.normalMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
title: "Stash",
|
||||
task: task,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Stash",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,11 +87,11 @@ func (gui *Gui) statusRenderToMain() error {
|
|||
style.FgMagenta.Sprintf("Become a sponsor: %s", constants.Links.Donate), // caffeine ain't free
|
||||
}, "\n\n")
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.normalMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
title: gui.c.Tr.StatusTitle,
|
||||
task: NewRenderStringTask(dashboardString),
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.c.Tr.StatusTitle,
|
||||
Task: types.NewRenderStringTask(dashboardString),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,25 @@
|
|||
package gui
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) subCommitsRenderToMain() error {
|
||||
commit := gui.State.Contexts.SubCommits.GetSelected()
|
||||
var task updateTask
|
||||
var task types.UpdateTask
|
||||
if commit == nil {
|
||||
task = NewRenderStringTask("No commits")
|
||||
task = types.NewRenderStringTask("No commits")
|
||||
} else {
|
||||
cmdObj := gui.git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
|
||||
|
||||
task = NewRunPtyTask(cmdObj.GetCmd())
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.normalMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
title: "Commit",
|
||||
task: task,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Commit",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,14 @@ import (
|
|||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
func (gui *Gui) submodulesRenderToMain() error {
|
||||
var task updateTask
|
||||
var task types.UpdateTask
|
||||
submodule := gui.State.Contexts.Submodules.GetSelected()
|
||||
if submodule == nil {
|
||||
task = NewRenderStringTask("No submodules")
|
||||
task = types.NewRenderStringTask("No submodules")
|
||||
} else {
|
||||
prefix := fmt.Sprintf(
|
||||
"Name: %s\nPath: %s\nUrl: %s\n\n",
|
||||
|
|
@ -23,18 +24,18 @@ func (gui *Gui) submodulesRenderToMain() error {
|
|||
|
||||
file := gui.helpers.WorkingTree.FileForSubmodule(submodule)
|
||||
if file == nil {
|
||||
task = NewRenderStringTask(prefix)
|
||||
task = types.NewRenderStringTask(prefix)
|
||||
} else {
|
||||
cmdObj := gui.git.WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, gui.IgnoreWhitespaceInDiffView)
|
||||
task = NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix)
|
||||
task = types.NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix)
|
||||
}
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.normalMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
title: "Submodule",
|
||||
task: task,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Submodule",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
package gui
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
func (gui *Gui) tagsRenderToMain() error {
|
||||
var task updateTask
|
||||
var task types.UpdateTask
|
||||
tag := gui.State.Contexts.Tags.GetSelected()
|
||||
if tag == nil {
|
||||
task = NewRenderStringTask("No tags")
|
||||
task = types.NewRenderStringTask("No tags")
|
||||
} else {
|
||||
cmdObj := gui.git.Branch.GetGraphCmdObj(tag.FullRefName())
|
||||
task = NewRunCommandTask(cmdObj.GetCmd())
|
||||
task = types.NewRunCommandTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
pair: gui.normalMainContextPair(),
|
||||
main: &viewUpdateOpts{
|
||||
title: "Tag",
|
||||
task: task,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Tag",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,22 @@ func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) newStringTaskWithScroll(view *gocui.View, str string, originX int, originY int) error {
|
||||
manager := gui.getManager(view)
|
||||
|
||||
f := func(stop chan struct{}) error {
|
||||
gui.setViewContent(view, str)
|
||||
_ = view.SetOrigin(originX, originY)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := manager.NewTask(f, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) newStringTaskWithKey(view *gocui.View, str string, key string) error {
|
||||
manager := gui.getManager(view)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
"gopkg.in/ozeidan/fuzzy-patricia.v3/patricia"
|
||||
)
|
||||
|
||||
|
|
@ -29,6 +28,12 @@ type IGuiCommon interface {
|
|||
PostRefreshUpdate(Context) error
|
||||
// this just re-renders the screen
|
||||
Render()
|
||||
// allows rendering to main views (i.e. the ones to the right of the side panel)
|
||||
// in such a way that avoids concurrency issues when there are slow commands
|
||||
// to display the output of
|
||||
RenderToMainViews(opts RefreshMainOpts) error
|
||||
// used purely for the sake of RenderToMainViews to provide the pair of main views we want to render to
|
||||
MainViewPairs() MainViewPairs
|
||||
|
||||
// returns true if command completed successfully
|
||||
RunSubprocess(cmdObj oscommands.ICmdObj) (bool, error)
|
||||
|
|
@ -160,11 +165,11 @@ type Model struct {
|
|||
// 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 Mutexes struct {
|
||||
RefreshingFilesMutex *sync.Mutex
|
||||
RefreshingStatusMutex *sync.Mutex
|
||||
SyncMutex *sync.Mutex
|
||||
LocalCommitsMutex *sync.Mutex
|
||||
SubprocessMutex *sync.Mutex
|
||||
PopupMutex *sync.Mutex
|
||||
PtyMutex *sync.Mutex
|
||||
RefreshingFilesMutex *deadlock.Mutex
|
||||
RefreshingStatusMutex *deadlock.Mutex
|
||||
SyncMutex *deadlock.Mutex
|
||||
LocalCommitsMutex *deadlock.Mutex
|
||||
SubprocessMutex *deadlock.Mutex
|
||||
PopupMutex *deadlock.Mutex
|
||||
PtyMutex *deadlock.Mutex
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type ContextKind int
|
||||
|
|
@ -106,7 +105,7 @@ type IPatchExplorerContext interface {
|
|||
Focus() error
|
||||
GetContentToRender(isFocused bool) string
|
||||
NavigateTo(isFocused bool, selectedLineIdx int) error
|
||||
GetMutex() *sync.Mutex
|
||||
GetMutex() *deadlock.Mutex
|
||||
}
|
||||
|
||||
type IViewTrait interface {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const (
|
|||
SUBMODULES
|
||||
STAGING
|
||||
PATCH_BUILDING
|
||||
MERGE_CONFLICTS
|
||||
COMMIT_FILES
|
||||
// not actually a view. Will refactor this later
|
||||
BISECT_INFO
|
||||
|
|
|
|||
95
pkg/gui/types/rendering.go
Normal file
95
pkg/gui/types/rendering.go
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
type MainContextPair struct {
|
||||
Main Context
|
||||
Secondary Context
|
||||
}
|
||||
|
||||
func NewMainContextPair(main Context, secondary Context) MainContextPair {
|
||||
return MainContextPair{Main: main, Secondary: secondary}
|
||||
}
|
||||
|
||||
type MainViewPairs struct {
|
||||
Normal MainContextPair
|
||||
MergeConflicts MainContextPair
|
||||
Staging MainContextPair
|
||||
PatchBuilding MainContextPair
|
||||
}
|
||||
|
||||
type ViewUpdateOpts struct {
|
||||
Title string
|
||||
|
||||
Task UpdateTask
|
||||
}
|
||||
|
||||
type RefreshMainOpts struct {
|
||||
Pair MainContextPair
|
||||
Main *ViewUpdateOpts
|
||||
Secondary *ViewUpdateOpts
|
||||
}
|
||||
|
||||
type UpdateTask interface {
|
||||
IsUpdateTask()
|
||||
}
|
||||
|
||||
type RenderStringTask struct {
|
||||
Str string
|
||||
}
|
||||
|
||||
func (t *RenderStringTask) IsUpdateTask() {}
|
||||
|
||||
func NewRenderStringTask(str string) *RenderStringTask {
|
||||
return &RenderStringTask{Str: str}
|
||||
}
|
||||
|
||||
type RenderStringWithoutScrollTask struct {
|
||||
Str string
|
||||
}
|
||||
|
||||
func (t *RenderStringWithoutScrollTask) IsUpdateTask() {}
|
||||
|
||||
func NewRenderStringWithoutScrollTask(str string) *RenderStringWithoutScrollTask {
|
||||
return &RenderStringWithoutScrollTask{Str: str}
|
||||
}
|
||||
|
||||
type RenderStringWithScrollTask struct {
|
||||
Str string
|
||||
OriginX int
|
||||
OriginY int
|
||||
}
|
||||
|
||||
func (t *RenderStringWithScrollTask) IsUpdateTask() {}
|
||||
|
||||
func NewRenderStringWithScrollTask(str string, originX int, originY int) *RenderStringWithScrollTask {
|
||||
return &RenderStringWithScrollTask{Str: str, OriginX: originX, OriginY: originY}
|
||||
}
|
||||
|
||||
type RunCommandTask struct {
|
||||
Cmd *exec.Cmd
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func (t *RunCommandTask) IsUpdateTask() {}
|
||||
|
||||
func NewRunCommandTask(cmd *exec.Cmd) *RunCommandTask {
|
||||
return &RunCommandTask{Cmd: cmd}
|
||||
}
|
||||
|
||||
func NewRunCommandTaskWithPrefix(cmd *exec.Cmd, prefix string) *RunCommandTask {
|
||||
return &RunCommandTask{Cmd: cmd, Prefix: prefix}
|
||||
}
|
||||
|
||||
type RunPtyTask struct {
|
||||
Cmd *exec.Cmd
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func (t *RunPtyTask) IsUpdateTask() {}
|
||||
|
||||
func NewRunPtyTask(cmd *exec.Cmd) *RunPtyTask {
|
||||
return &RunPtyTask{Cmd: cmd}
|
||||
}
|
||||
|
|
@ -58,7 +58,7 @@ func (gui *Gui) startUpdating(newVersion string) {
|
|||
func (gui *Gui) onUpdateFinish(statusId int, err error) error {
|
||||
gui.State.Updating = false
|
||||
gui.statusManager.removeStatus(statusId)
|
||||
gui.OnUIThread(func() error {
|
||||
gui.c.OnUIThread(func() error {
|
||||
_ = gui.renderString(gui.Views.AppStatus, "")
|
||||
if err != nil {
|
||||
errMessage := utils.ResolvePlaceholderString(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/spkg/bom"
|
||||
|
|
@ -99,13 +100,13 @@ func (gui *Gui) globalOptionsMap() map[string]string {
|
|||
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||
|
||||
return map[string]string{
|
||||
fmt.Sprintf("%s/%s", gui.getKeyDisplay(keybindingConfig.Universal.ScrollUpMain), gui.getKeyDisplay(keybindingConfig.Universal.ScrollDownMain)): gui.c.Tr.LcScroll,
|
||||
fmt.Sprintf("%s %s %s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevBlock), gui.getKeyDisplay(keybindingConfig.Universal.NextBlock), gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
|
||||
gui.getKeyDisplay(keybindingConfig.Universal.Return): gui.c.Tr.LcCancel,
|
||||
gui.getKeyDisplay(keybindingConfig.Universal.Quit): gui.c.Tr.LcQuit,
|
||||
gui.getKeyDisplay(keybindingConfig.Universal.OptionMenu): gui.c.Tr.LcMenu,
|
||||
fmt.Sprintf("%s-%s", gui.getKeyDisplay(keybindingConfig.Universal.JumpToBlock[0]), gui.getKeyDisplay(keybindingConfig.Universal.JumpToBlock[len(keybindingConfig.Universal.JumpToBlock)-1])): gui.c.Tr.LcJump,
|
||||
fmt.Sprintf("%s/%s", gui.getKeyDisplay(keybindingConfig.Universal.ScrollLeft), gui.getKeyDisplay(keybindingConfig.Universal.ScrollRight)): gui.c.Tr.LcScrollLeftRight,
|
||||
fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollUpMain), keybindings.Label(keybindingConfig.Universal.ScrollDownMain)): gui.c.Tr.LcScroll,
|
||||
fmt.Sprintf("%s %s %s %s", keybindings.Label(keybindingConfig.Universal.PrevBlock), keybindings.Label(keybindingConfig.Universal.NextBlock), keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
|
||||
keybindings.Label(keybindingConfig.Universal.Return): gui.c.Tr.LcCancel,
|
||||
keybindings.Label(keybindingConfig.Universal.Quit): gui.c.Tr.LcQuit,
|
||||
keybindings.Label(keybindingConfig.Universal.OptionMenu): gui.c.Tr.LcMenu,
|
||||
fmt.Sprintf("%s-%s", keybindings.Label(keybindingConfig.Universal.JumpToBlock[0]), keybindings.Label(keybindingConfig.Universal.JumpToBlock[len(keybindingConfig.Universal.JumpToBlock)-1])): gui.c.Tr.LcJump,
|
||||
fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollLeft), keybindings.Label(keybindingConfig.Universal.ScrollRight)): gui.c.Tr.LcScrollLeftRight,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -192,5 +193,5 @@ func getTabbedView(gui *Gui) *gocui.View {
|
|||
}
|
||||
|
||||
func (gui *Gui) render() {
|
||||
gui.OnUIThread(func() error { return nil })
|
||||
gui.c.OnUIThread(func() error { return nil })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ type Views struct {
|
|||
StagingSecondary *gocui.View
|
||||
PatchBuilding *gocui.View
|
||||
PatchBuildingSecondary *gocui.View
|
||||
Merging *gocui.View
|
||||
MergeConflicts *gocui.View
|
||||
|
||||
Options *gocui.View
|
||||
Confirmation *gocui.View
|
||||
|
|
@ -74,7 +74,7 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping {
|
|||
{viewPtr: &gui.Views.StagingSecondary, name: "stagingSecondary"},
|
||||
{viewPtr: &gui.Views.PatchBuilding, name: "patchBuilding"},
|
||||
{viewPtr: &gui.Views.PatchBuildingSecondary, name: "patchBuildingSecondary"},
|
||||
{viewPtr: &gui.Views.Merging, name: "merging"},
|
||||
{viewPtr: &gui.Views.MergeConflicts, name: "mergeConflicts"},
|
||||
{viewPtr: &gui.Views.Secondary, name: "secondary"},
|
||||
{viewPtr: &gui.Views.Main, name: "main"},
|
||||
|
||||
|
|
@ -151,7 +151,7 @@ func (gui *Gui) createAllViews() error {
|
|||
gui.Views.Files.Title = gui.c.Tr.FilesTitle
|
||||
gui.Views.Files.FgColor = theme.GocuiDefaultTextColor
|
||||
|
||||
for _, view := range []*gocui.View{gui.Views.Main, gui.Views.Secondary, gui.Views.Staging, gui.Views.StagingSecondary, gui.Views.PatchBuilding, gui.Views.PatchBuildingSecondary, gui.Views.Merging} {
|
||||
for _, view := range []*gocui.View{gui.Views.Main, gui.Views.Secondary, gui.Views.Staging, gui.Views.StagingSecondary, gui.Views.PatchBuilding, gui.Views.PatchBuildingSecondary, gui.Views.MergeConflicts} {
|
||||
view.Title = gui.c.Tr.DiffTitle
|
||||
view.Wrap = true
|
||||
view.FgColor = theme.GocuiDefaultTextColor
|
||||
|
|
@ -175,9 +175,9 @@ func (gui *Gui) createAllViews() error {
|
|||
gui.Views.PatchBuildingSecondary.Highlight = true
|
||||
gui.Views.PatchBuildingSecondary.Wrap = true
|
||||
|
||||
gui.Views.Merging.Title = gui.c.Tr.MergeConflictsTitle
|
||||
gui.Views.Merging.Highlight = true
|
||||
gui.Views.Merging.Wrap = true
|
||||
gui.Views.MergeConflicts.Title = gui.c.Tr.MergeConflictsTitle
|
||||
gui.Views.MergeConflicts.Highlight = true
|
||||
gui.Views.MergeConflicts.Wrap = false
|
||||
|
||||
gui.Views.Limit.Title = gui.c.Tr.NotEnoughSpace
|
||||
gui.Views.Limit.Wrap = true
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ func RunTests(
|
|||
|
||||
// validates that the actual and expected dirs have the same repo names (doesn't actually check the contents of the repos)
|
||||
func validateSameRepos(expectedDir string, actualDir string) error {
|
||||
// iterate through each repo in the expected dir and comparet to the corresponding repo in the actual dir
|
||||
// iterate through each repo in the expected dir and compare to the corresponding repo in the actual dir
|
||||
expectedFiles, err := ioutil.ReadDir(expectedDir)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -441,6 +441,13 @@ func generateSnapshots(actualDir string, expectedDir string) (string, string, er
|
|||
return "", "", err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := os.RemoveAll(expectedDirCopyDir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := restoreSpecialPaths(expectedDirCopyDir); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
|
@ -450,11 +457,6 @@ func generateSnapshots(actualDir string, expectedDir string) (string, string, er
|
|||
return "", "", err
|
||||
}
|
||||
|
||||
err = os.RemoveAll(expectedDirCopyDir)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return actual, expected, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
package tasks
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
// the purpose of an AsyncHandler is to ensure that if we have multiple long-running
|
||||
|
|
@ -17,13 +16,13 @@ import (
|
|||
type AsyncHandler struct {
|
||||
currentId int
|
||||
lastId int
|
||||
mutex sync.Mutex
|
||||
mutex deadlock.Mutex
|
||||
onReject func()
|
||||
}
|
||||
|
||||
func NewAsyncHandler() *AsyncHandler {
|
||||
return &AsyncHandler{
|
||||
mutex: sync.Mutex{},
|
||||
mutex: deadlock.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
|
@ -34,8 +35,8 @@ type ViewBufferManager struct {
|
|||
// this is what we write the output of the task to. It's typically a view
|
||||
writer io.Writer
|
||||
|
||||
waitingMutex sync.Mutex
|
||||
taskIDMutex sync.Mutex
|
||||
waitingMutex deadlock.Mutex
|
||||
taskIDMutex deadlock.Mutex
|
||||
Log *logrus.Entry
|
||||
newTaskID int
|
||||
readLines chan int
|
||||
|
|
@ -126,7 +127,7 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
|||
}
|
||||
})
|
||||
|
||||
loadingMutex := sync.Mutex{}
|
||||
loadingMutex := deadlock.Mutex{}
|
||||
|
||||
// not sure if it's the right move to redefine this or not
|
||||
self.readLines = make(chan int, 1024)
|
||||
|
|
|
|||
31
pkg/utils/once_writer.go
Normal file
31
pkg/utils/once_writer.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// This wraps a writer and ensures that before we actually write anything we call a given function first
|
||||
|
||||
type OnceWriter struct {
|
||||
writer io.Writer
|
||||
once sync.Once
|
||||
f func()
|
||||
}
|
||||
|
||||
var _ io.Writer = &OnceWriter{}
|
||||
|
||||
func NewOnceWriter(writer io.Writer, f func()) *OnceWriter {
|
||||
return &OnceWriter{
|
||||
writer: writer,
|
||||
f: f,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *OnceWriter) Write(p []byte) (n int, err error) {
|
||||
self.once.Do(func() {
|
||||
self.f()
|
||||
})
|
||||
|
||||
return self.writer.Write(p)
|
||||
}
|
||||
19
pkg/utils/once_writer_test.go
Normal file
19
pkg/utils/once_writer_test.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOnceWriter(t *testing.T) {
|
||||
innerWriter := bytes.NewBuffer(nil)
|
||||
counter := 0
|
||||
onceWriter := NewOnceWriter(innerWriter, func() {
|
||||
counter += 1
|
||||
})
|
||||
_, _ = onceWriter.Write([]byte("hello"))
|
||||
_, _ = onceWriter.Write([]byte("hello"))
|
||||
if counter != 1 {
|
||||
t.Errorf("expected counter to be 1, got %d", counter)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,6 @@
|
|||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
[user]
|
||||
email = CI@example.com
|
||||
name = CI
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@
|
|||
# exclude patterns (uncomment them if you want to use them):
|
||||
# *.[oa]
|
||||
# *~
|
||||
.DS_Store
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
0000000000000000000000000000000000000000 9dd04ee245b7d6f1f80aa2b428111cbac4a4e37d CI <CI@example.com> 1657012500 +1000 commit (initial): Initial commit
|
||||
0000000000000000000000000000000000000000 04535177acab8a81c84b0b1b44ee3aea76b0e36e CI <CI@example.com> 1659528492 +0200 commit (initial): Initial commit
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
0000000000000000000000000000000000000000 9dd04ee245b7d6f1f80aa2b428111cbac4a4e37d CI <CI@example.com> 1657012500 +1000 commit (initial): Initial commit
|
||||
0000000000000000000000000000000000000000 04535177acab8a81c84b0b1b44ee3aea76b0e36e CI <CI@example.com> 1659528492 +0200 commit (initial): Initial commit
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -1 +1 @@
|
|||
9dd04ee245b7d6f1f80aa2b428111cbac4a4e37d
|
||||
04535177acab8a81c84b0b1b44ee3aea76b0e36e
|
||||
|
|
|
|||
|
|
@ -1,2 +1 @@
|
|||
|
||||
myfile1
|
||||
myfile1
|
||||
|
|
|
|||
4
vendor/github.com/petermattis/goid/.gitignore
generated
vendored
Normal file
4
vendor/github.com/petermattis/goid/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
*~
|
||||
*.test
|
||||
.*.swp
|
||||
.DS_Store
|
||||
202
vendor/github.com/petermattis/goid/LICENSE
generated
vendored
Normal file
202
vendor/github.com/petermattis/goid/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
5
vendor/github.com/petermattis/goid/README.md
generated
vendored
Normal file
5
vendor/github.com/petermattis/goid/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# goid [](https://travis-ci.org/petermattis/goid)
|
||||
|
||||
Programatically retrieve the current goroutine's ID. See [the CI
|
||||
configuration](.travis.yml) for supported Go versions. In addition,
|
||||
gccgo 7.2.1 (Go 1.8.3) is supported.
|
||||
35
vendor/github.com/petermattis/goid/goid.go
generated
vendored
Normal file
35
vendor/github.com/petermattis/goid/goid.go
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2016 Peter Mattis.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
// implied. See the License for the specific language governing
|
||||
// permissions and limitations under the License. See the AUTHORS file
|
||||
// for names of contributors.
|
||||
|
||||
package goid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func ExtractGID(s []byte) int64 {
|
||||
s = s[len("goroutine "):]
|
||||
s = s[:bytes.IndexByte(s, ' ')]
|
||||
gid, _ := strconv.ParseInt(string(s), 10, 64)
|
||||
return gid
|
||||
}
|
||||
|
||||
// Parse the goid from runtime.Stack() output. Slow, but it works.
|
||||
func getSlow() int64 {
|
||||
var buf [64]byte
|
||||
return ExtractGID(buf[:runtime.Stack(buf[:], false)])
|
||||
}
|
||||
25
vendor/github.com/petermattis/goid/goid_gccgo.go
generated
vendored
Normal file
25
vendor/github.com/petermattis/goid/goid_gccgo.go
generated
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2018 Peter Mattis.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
// implied. See the License for the specific language governing
|
||||
// permissions and limitations under the License. See the AUTHORS file
|
||||
// for names of contributors.
|
||||
|
||||
// +build gccgo
|
||||
|
||||
package goid
|
||||
|
||||
//extern runtime.getg
|
||||
func getg() *g
|
||||
|
||||
func Get() int64 {
|
||||
return getg().goid
|
||||
}
|
||||
23
vendor/github.com/petermattis/goid/goid_go1.3.c
generated
vendored
Normal file
23
vendor/github.com/petermattis/goid/goid_go1.3.c
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2015 Peter Mattis.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
// implied. See the License for the specific language governing
|
||||
// permissions and limitations under the License. See the AUTHORS file
|
||||
// for names of contributors.
|
||||
|
||||
// +build !go1.4
|
||||
|
||||
#include <runtime.h>
|
||||
|
||||
void ·Get(int64 ret) {
|
||||
ret = g->goid;
|
||||
USED(&ret);
|
||||
}
|
||||
21
vendor/github.com/petermattis/goid/goid_go1.3.go
generated
vendored
Normal file
21
vendor/github.com/petermattis/goid/goid_go1.3.go
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2015 Peter Mattis.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
// implied. See the License for the specific language governing
|
||||
// permissions and limitations under the License. See the AUTHORS file
|
||||
// for names of contributors.
|
||||
|
||||
// +build !go1.4
|
||||
|
||||
package goid
|
||||
|
||||
// Get returns the id of the current goroutine.
|
||||
func Get() int64
|
||||
34
vendor/github.com/petermattis/goid/goid_go1.4.go
generated
vendored
Normal file
34
vendor/github.com/petermattis/goid/goid_go1.4.go
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2015 Peter Mattis.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
// implied. See the License for the specific language governing
|
||||
// permissions and limitations under the License. See the AUTHORS file
|
||||
// for names of contributors.
|
||||
|
||||
// +build go1.4,!go1.5
|
||||
|
||||
package goid
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var pointerSize = unsafe.Sizeof(uintptr(0))
|
||||
|
||||
// Backdoor access to runtime·getg().
|
||||
func getg() uintptr // in goid_go1.4.s
|
||||
|
||||
// Get returns the id of the current goroutine.
|
||||
func Get() int64 {
|
||||
// The goid is the 16th field in the G struct where each field is a
|
||||
// pointer, uintptr or padded to that size. See runtime.h from the
|
||||
// Go sources. I'm not aware of a cleaner way to determine the
|
||||
// offset.
|
||||
return *(*int64)(unsafe.Pointer(getg() + 16*pointerSize))
|
||||
}
|
||||
18
vendor/github.com/petermattis/goid/goid_go1.4.s
generated
vendored
Normal file
18
vendor/github.com/petermattis/goid/goid_go1.4.s
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Assembly to get into package runtime without using exported symbols.
|
||||
// See https://github.com/golang/go/blob/release-branch.go1.4/misc/cgo/test/backdoor/thunk.s
|
||||
|
||||
// +build amd64 amd64p32 arm 386
|
||||
// +build go1.4,!go1.5
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
#ifdef GOARCH_arm
|
||||
#define JMP B
|
||||
#endif
|
||||
|
||||
TEXT ·getg(SB),NOSPLIT,$0-0
|
||||
JMP runtime·getg(SB)
|
||||
21
vendor/github.com/petermattis/goid/goid_go1.5_amd64.go
generated
vendored
Normal file
21
vendor/github.com/petermattis/goid/goid_go1.5_amd64.go
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2016 Peter Mattis.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
// implied. See the License for the specific language governing
|
||||
// permissions and limitations under the License. See the AUTHORS file
|
||||
// for names of contributors.
|
||||
|
||||
// +build amd64 amd64p32
|
||||
// +build gc,go1.5
|
||||
|
||||
package goid
|
||||
|
||||
func Get() int64
|
||||
29
vendor/github.com/petermattis/goid/goid_go1.5_amd64.s
generated
vendored
Normal file
29
vendor/github.com/petermattis/goid/goid_go1.5_amd64.s
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2016 Peter Mattis.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
// implied. See the License for the specific language governing
|
||||
// permissions and limitations under the License. See the AUTHORS file
|
||||
// for names of contributors.
|
||||
|
||||
// Assembly to mimic runtime.getg.
|
||||
|
||||
// +build amd64 amd64p32
|
||||
// +build gc,go1.5
|
||||
|
||||
#include "go_asm.h"
|
||||
#include "textflag.h"
|
||||
|
||||
// func Get() int64
|
||||
TEXT ·Get(SB),NOSPLIT,$0-8
|
||||
MOVQ (TLS), R14
|
||||
MOVQ g_goid(R14), R13
|
||||
MOVQ R13, ret+0(FP)
|
||||
RET
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue