1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-11-28 09:08:41 +02:00

refactor to only have one context per view

This commit is contained in:
Jesse Duffield 2022-06-13 11:01:26 +10:00
parent 6dfef08efc
commit 524bf83a4a
372 changed files with 28866 additions and 6902 deletions

View File

@ -274,7 +274,7 @@ os:
Lazygit will log an error if none of these options are set.
You can specify a line number you are currently at when in the line-by-line mode.
You can specify the current line number when you're in the patch explorer.
```yaml
os:

View File

@ -30,13 +30,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## List Panel Navigation
<pre>
<kbd>.</kbd>: next page
<kbd>,</kbd>: previous page
<kbd><</kbd>: scroll to top
<kbd>></kbd>: scroll to bottom
<kbd>/</kbd>: start search
<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>]</kbd>: next tab
<kbd>[</kbd>: previous tab
</pre>
@ -163,36 +163,33 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Main Panel (Patch Building)
<pre>
<kbd>esc</kbd>: exit line-by-line mode
<kbd>o</kbd>: open file
<kbd></kbd>: select previous line
<kbd></kbd>: select next line
<kbd></kbd>: select previous hunk
<kbd></kbd>: select next hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>space</kbd>: add/remove line(s) to patch
<kbd>v</kbd>: toggle drag select
<kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: open file
<kbd>e</kbd>: edit file
<kbd>space</kbd>: add/remove line(s) to patch
<kbd>esc</kbd>: exit custom patch builder
</pre>
## Main Panel (Staging)
<pre>
<kbd>esc</kbd>: return to files panel
<kbd>space</kbd>: toggle line staged / unstaged
<kbd>d</kbd>: delete change (git reset)
<kbd>tab</kbd>: switch to other panel
<kbd>o</kbd>: open file
<kbd></kbd>: select previous line
<kbd></kbd>: select next line
<kbd></kbd>: select previous hunk
<kbd></kbd>: select next hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>e</kbd>: edit file
<kbd>v</kbd>: toggle drag select
<kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: open file
<kbd>e</kbd>: edit file
<kbd>esc</kbd>: return to files panel
<kbd>tab</kbd>: switch to other panel (staged/unstaged changes)
<kbd>space</kbd>: toggle line staged / unstaged
<kbd>d</kbd>: delete change (git reset)
<kbd>E</kbd>: edit hunk
</pre>

View File

@ -30,13 +30,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 一覧パネルの操作
<pre>
<kbd>.</kbd>: 次のページ
<kbd>,</kbd>: 前のページ
<kbd><</kbd>: 最上部までスクロール
<kbd>></kbd>: 最下部までスクロール
<kbd>/</kbd>: 検索を開始
<kbd>H</kbd>: 左スクロール
<kbd>L</kbd>: 右スクロール
<kbd>,</kbd>: 前のページ
<kbd>.</kbd>: 次のページ
<kbd><</kbd>: 最上部までスクロール
<kbd>/</kbd>: 検索を開始
<kbd>></kbd>: 最下部までスクロール
<kbd>]</kbd>: 次のタブ
<kbd>[</kbd>: 前のタブ
</pre>
@ -222,36 +222,33 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## メインパネル (Patch Building)
<pre>
<kbd>esc</kbd>: line-by-lineモードを終了
<kbd>o</kbd>: ファイルを開く
<kbd></kbd>: 前の行を選択
<kbd></kbd>: 次の行を選択
<kbd></kbd>: 前のhunkを選択
<kbd></kbd>: 次のhunkを選択
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>space</kbd>: 行をパッチに追加/削除
<kbd>v</kbd>: 範囲選択を切り替え
<kbd>V</kbd>: 範囲選択を切り替え
<kbd>a</kbd>: hunk選択を切り替え
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>o</kbd>: ファイルを開く
<kbd>e</kbd>: ファイルを編集
<kbd>space</kbd>: 行をパッチに追加/削除
<kbd>esc</kbd>: exit custom patch builder
</pre>
## メインパネル (Staging)
<pre>
<kbd>esc</kbd>: ファイル一覧に戻る
<kbd>space</kbd>: 選択行をステージ/アンステージ
<kbd>d</kbd>: 変更を削除 (git reset)
<kbd>tab</kbd>: パネルを切り替え
<kbd>o</kbd>: ファイルを開く
<kbd></kbd>: 前の行を選択
<kbd></kbd>: 次の行を選択
<kbd></kbd>: 前のhunkを選択
<kbd></kbd>: 次のhunkを選択
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>e</kbd>: ファイルを編集
<kbd>v</kbd>: 範囲選択を切り替え
<kbd>V</kbd>: 範囲選択を切り替え
<kbd>a</kbd>: hunk選択を切り替え
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>o</kbd>: ファイルを開く
<kbd>e</kbd>: ファイルを編集
<kbd>esc</kbd>: ファイル一覧に戻る
<kbd>tab</kbd>: パネルを切り替え
<kbd>space</kbd>: 選択行をステージ/アンステージ
<kbd>d</kbd>: 変更を削除 (git reset)
<kbd>E</kbd>: edit hunk
</pre>

View File

@ -30,13 +30,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## List Panel Navigation
<pre>
<kbd>.</kbd>: 다음 페이지
<kbd>,</kbd>: 이전 페이지
<kbd><</kbd>: 맨 위로 스크롤
<kbd>></kbd>: 맨 아래로 스크롤
<kbd>/</kbd>: 검색 시작
<kbd>H</kbd>: 우 스크롤
<kbd>L</kbd>: 좌 스크롤
<kbd>,</kbd>: 이전 페이지
<kbd>.</kbd>: 다음 페이지
<kbd><</kbd>: 맨 위로 스크롤
<kbd>/</kbd>: 검색 시작
<kbd>></kbd>: 맨 아래로 스크롤
<kbd>]</kbd>: 이전 탭
<kbd>[</kbd>: 다음 탭
</pre>
@ -107,36 +107,33 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 메인 패널 (Patch Building)
<pre>
<kbd>esc</kbd>: line-by-line 모드 종료
<kbd>o</kbd>: 파일 닫기
<kbd></kbd>: 이전 줄 선택
<kbd></kbd>: 다음 줄 선택
<kbd></kbd>: 이전 hunk를 선택
<kbd></kbd>: 다음 hunk를 선택
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>space</kbd>: line(s)을 패치에 추가/삭제
<kbd>v</kbd>: 드래그 선택 전환
<kbd>V</kbd>: 드래그 선택 전환
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>o</kbd>: 파일 닫기
<kbd>e</kbd>: 파일 편집
<kbd>space</kbd>: line(s)을 패치에 추가/삭제
<kbd>esc</kbd>: exit custom patch builder
</pre>
## 메인 패널 (Staging)
<pre>
<kbd>esc</kbd>: 파일 목록으로 돌아가기
<kbd>space</kbd>: 선택한 행을 staged / unstaged
<kbd>d</kbd>: 변경을 삭제 (git reset)
<kbd>tab</kbd>: 패널 전환
<kbd>o</kbd>: 파일 닫기
<kbd></kbd>: 이전 줄 선택
<kbd></kbd>: 다음 줄 선택
<kbd></kbd>: 이전 hunk를 선택
<kbd></kbd>: 다음 hunk를 선택
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>e</kbd>: 파일 편집
<kbd>v</kbd>: 드래그 선택 전환
<kbd>V</kbd>: 드래그 선택 전환
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>o</kbd>: 파일 닫기
<kbd>e</kbd>: 파일 편집
<kbd>esc</kbd>: 파일 목록으로 돌아가기
<kbd>tab</kbd>: 패널 전환
<kbd>space</kbd>: 선택한 행을 staged / unstaged
<kbd>d</kbd>: 변경을 삭제 (git reset)
<kbd>E</kbd>: edit hunk
</pre>

View File

@ -30,13 +30,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Lijstpaneel Navigatie
<pre>
<kbd>.</kbd>: volgende pagina
<kbd>,</kbd>: vorige pagina
<kbd><</kbd>: scroll naar boven
<kbd>></kbd>: scroll naar beneden
<kbd>/</kbd>: start met zoeken
<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>]</kbd>: volgende tabblad
<kbd>[</kbd>: vorige tabblad
</pre>
@ -163,17 +163,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Patch Bouwen
<pre>
<kbd>esc</kbd>: sluit lijn-bij-lijn modus
<kbd>o</kbd>: open bestand
<kbd></kbd>: selecteer de vorige lijn
<kbd></kbd>: selecteer de volgende lijn
<kbd></kbd>: selecteer de vorige hunk
<kbd></kbd>: selecteer de volgende hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>space</kbd>: voeg toe/verwijder lijn(en) in patch
<kbd>v</kbd>: toggle drag selecteer
<kbd>V</kbd>: toggle drag selecteer
<kbd>a</kbd>: toggle selecteer hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: open bestand
<kbd>e</kbd>: verander bestand
<kbd>space</kbd>: voeg toe/verwijder lijn(en) in patch
<kbd>esc</kbd>: sluit lijn-bij-lijn modus
</pre>
## Reflog
@ -217,20 +216,18 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Staging
<pre>
<kbd>esc</kbd>: ga terug naar het bestanden paneel
<kbd>space</kbd>: toggle lijnen staged / unstaged
<kbd>d</kbd>: verwijdert change (git reset)
<kbd>tab</kbd>: ga naar een ander paneel
<kbd>o</kbd>: open bestand
<kbd></kbd>: selecteer de vorige lijn
<kbd></kbd>: selecteer de volgende lijn
<kbd></kbd>: selecteer de vorige hunk
<kbd></kbd>: selecteer de volgende hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>e</kbd>: verander bestand
<kbd>v</kbd>: toggle drag selecteer
<kbd>V</kbd>: toggle drag selecteer
<kbd>a</kbd>: toggle selecteer hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: open bestand
<kbd>e</kbd>: verander bestand
<kbd>esc</kbd>: ga terug naar het bestanden paneel
<kbd>tab</kbd>: ga naar een ander paneel
<kbd>space</kbd>: toggle lijnen staged / unstaged
<kbd>d</kbd>: verwijdert change (git reset)
<kbd>E</kbd>: edit hunk
</pre>

View File

@ -30,13 +30,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## List Panel Navigation
<pre>
<kbd>.</kbd>: next page
<kbd>,</kbd>: previous page
<kbd><</kbd>: scroll to top
<kbd>></kbd>: scroll to bottom
<kbd>/</kbd>: start search
<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>]</kbd>: next tab
<kbd>[</kbd>: previous tab
</pre>
@ -99,17 +99,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Main Panel (Patch Building)
<pre>
<kbd>esc</kbd>: wyście z trybu "linia po linii"
<kbd>o</kbd>: otwórz plik
<kbd></kbd>: poprzednia linia
<kbd></kbd>: następna linia
<kbd></kbd>: poprzedni kawałek
<kbd></kbd>: następny kawałek
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>space</kbd>: add/remove line(s) to patch
<kbd>v</kbd>: toggle drag select
<kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: otwórz plik
<kbd>e</kbd>: edytuj plik
<kbd>space</kbd>: add/remove line(s) to patch
<kbd>esc</kbd>: wyście z trybu "linia po linii"
</pre>
## Pliki
@ -156,20 +155,18 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Poczekalnia
<pre>
<kbd>esc</kbd>: wróć do panelu plików
<kbd>space</kbd>: toggle line staged / unstaged
<kbd>d</kbd>: delete change (git reset)
<kbd>tab</kbd>: switch to other panel
<kbd>o</kbd>: otwórz plik
<kbd></kbd>: poprzednia linia
<kbd></kbd>: następna linia
<kbd></kbd>: poprzedni kawałek
<kbd></kbd>: następny kawałek
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>e</kbd>: edytuj plik
<kbd>v</kbd>: toggle drag select
<kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: otwórz plik
<kbd>e</kbd>: edytuj plik
<kbd>esc</kbd>: wróć do panelu plików
<kbd>tab</kbd>: switch to other panel (staged/unstaged changes)
<kbd>space</kbd>: toggle line staged / unstaged
<kbd>d</kbd>: delete change (git reset)
<kbd>E</kbd>: edit hunk
</pre>

View File

@ -30,13 +30,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 列表面板导航
<pre>
<kbd>.</kbd>: 下一页
<kbd>,</kbd>: 上一页
<kbd><</kbd>: 滚动到顶部
<kbd>></kbd>: 滚动到底部
<kbd>/</kbd>: 开始搜索
<kbd>H</kbd>: 向左滚动
<kbd>L</kbd>: 向右滚动
<kbd>,</kbd>: 上一页
<kbd>.</kbd>: 下一页
<kbd><</kbd>: 滚动到顶部
<kbd>/</kbd>: 开始搜索
<kbd>></kbd>: 滚动到底部
<kbd>]</kbd>: 下一个标签
<kbd>[</kbd>: 上一个标签
</pre>
@ -183,17 +183,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 构建补丁中
<pre>
<kbd>esc</kbd>: 退出逐行模式
<kbd>o</kbd>: 打开文件
<kbd></kbd>: 选择上一行
<kbd></kbd>: 选择下一行
<kbd></kbd>: 选择上一个区块
<kbd></kbd>: 选择下一个区块
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
<kbd>space</kbd>: 添加/移除 行到补丁
<kbd>v</kbd>: 切换拖动选择
<kbd>V</kbd>: 切换拖动选择
<kbd>a</kbd>: 切换选择区块
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
<kbd>o</kbd>: 打开文件
<kbd>e</kbd>: 编辑文件
<kbd>space</kbd>: 添加/移除 行到补丁
<kbd>esc</kbd>: 退出逐行模式
</pre>
## 标签页面
@ -226,20 +225,18 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 正在暂存
<pre>
<kbd>esc</kbd>: 返回文件面板
<kbd>space</kbd>: 切换行暂存状态
<kbd>d</kbd>: 取消变更 (git reset)
<kbd>tab</kbd>: 切换到其他面板
<kbd>o</kbd>: 打开文件
<kbd></kbd>: 选择上一行
<kbd></kbd>: 选择下一行
<kbd></kbd>: 选择上一个区块
<kbd></kbd>: 选择下一个区块
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
<kbd>e</kbd>: 编辑文件
<kbd>v</kbd>: 切换拖动选择
<kbd>V</kbd>: 切换拖动选择
<kbd>a</kbd>: 切换选择区块
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
<kbd>o</kbd>: 打开文件
<kbd>e</kbd>: 编辑文件
<kbd>esc</kbd>: 返回文件面板
<kbd>tab</kbd>: 切换到其他面板
<kbd>space</kbd>: 切换行暂存状态
<kbd>d</kbd>: 取消变更 (git reset)
<kbd>E</kbd>: edit hunk
</pre>

10
go.mod
View File

@ -17,7 +17,7 @@ require (
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4
github.com/jesseduffield/gocui v0.3.1-0.20220723050330-1f853fadb335
github.com/jesseduffield/gocui v0.3.1-0.20220806032055-dfd3eb22e18a
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
github.com/jesseduffield/yaml v2.1.0+incompatible
@ -42,7 +42,7 @@ require (
github.com/emirpasic/gods v1.12.0 // indirect
github.com/fatih/color v1.9.0 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/gdamore/tcell/v2 v2.5.1 // indirect
github.com/gdamore/tcell/v2 v2.5.2 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.0.0 // indirect
github.com/go-logfmt/logfmt v0.5.0 // indirect
@ -58,14 +58,14 @@ 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/rivo/uniseg v0.2.0 // 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
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect

19
go.sum
View File

@ -35,8 +35,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
github.com/gdamore/tcell/v2 v2.5.1 h1:zc3LPdpK184lBW7syF2a5C6MV827KmErk9jGVnmsl/I=
github.com/gdamore/tcell/v2 v2.5.1/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
github.com/gdamore/tcell/v2 v2.5.2 h1:tKzG29kO9p2V++3oBY2W9zUjYu7IK1MENFeY/BzJSVY=
github.com/gdamore/tcell/v2 v2.5.2/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
@ -72,8 +72,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4 h1:GOQrmaE8i+KEdB8NzAegKYd4tPn/inM0I1uo0NXFerg=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
github.com/jesseduffield/gocui v0.3.1-0.20220723050330-1f853fadb335 h1:36XGBaxzg5umrZO99Ir7Y7zpJPlOzOq8rDZUuTUrCvI=
github.com/jesseduffield/gocui v0.3.1-0.20220723050330-1f853fadb335/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
github.com/jesseduffield/gocui v0.3.1-0.20220806032055-dfd3eb22e18a h1:k+lnojvZ6FoSzOIsSVVBlB9v3EZ+L5Qn/GS5PEzyTAA=
github.com/jesseduffield/gocui v0.3.1-0.20220806032055-dfd3eb22e18a/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e h1:uw/oo+kg7t/oeMs6sqlAwr85ND/9cpO3up3VxphxY0U=
@ -131,8 +131,9 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw=
github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/samber/lo v1.10.1 h1:0D3h7i0U3hRAbaCeQ82DLe67n0A7Bbl0/cEoWqFGp+U=
@ -189,11 +190,11 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 h1:9vYwv7OjYaky/tlAeD7C4oC9EsPTlaFl1H2jS++V+ME=
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=

View File

@ -21,7 +21,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/integration"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
@ -105,10 +104,9 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
"commits": tr.CommitsTitle,
"confirmation": tr.ConfirmationTitle,
"information": tr.InformationTitle,
"main": tr.MainTitle,
"main": tr.NormalTitle,
"patchBuilding": tr.PatchBuildingTitle,
"merging": tr.MergingTitle,
"normal": tr.NormalTitle,
"staging": tr.StagingTitle,
"menu": tr.MenuTitle,
"search": tr.SearchTitle,
@ -127,12 +125,17 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
}
func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*bindingSection {
excludedViews := []string{"stagingSecondary", "patchBuildingSecondary"}
bindingsToDisplay := slices.Filter(bindings, func(binding *types.Binding) bool {
return binding.Description != "" || binding.Alternative != ""
if lo.Contains(excludedViews, binding.ViewName) {
return false
}
return (binding.Description != "" || binding.Alternative != "")
})
bindingsByHeader := utils.MuiltiGroupBy(bindingsToDisplay, func(binding *types.Binding) []header {
return getHeaders(binding, tr)
bindingsByHeader := lo.GroupBy(bindingsToDisplay, func(binding *types.Binding) header {
return getHeader(binding, tr)
})
bindingGroups := maps.MapToSlice(
@ -164,24 +167,16 @@ func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*b
})
}
// a binding may belong to multiple headers if it is applicable to multiple contexts,
// for example the copy-to-clipboard binding.
func getHeaders(binding *types.Binding, tr *i18n.TranslationSet) []header {
func getHeader(binding *types.Binding, tr *i18n.TranslationSet) header {
if binding.Tag == "navigation" {
return []header{{priority: 2, title: localisedTitle(tr, "navigation")}}
return header{priority: 2, title: localisedTitle(tr, "navigation")}
}
if binding.ViewName == "" {
return []header{{priority: 3, title: localisedTitle(tr, "global")}}
return header{priority: 3, title: localisedTitle(tr, "global")}
}
if len(binding.Contexts) == 0 {
return []header{}
}
return slices.Map(binding.Contexts, func(context string) header {
return header{priority: 1, title: localisedTitle(tr, context)}
})
return header{priority: 1, title: localisedTitle(tr, binding.ViewName)}
}
func formatSections(tr *i18n.TranslationSet, bindingSections []*bindingSection) string {

View File

@ -26,7 +26,6 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "stage file",
},
},
@ -36,7 +35,6 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "stage file",
},
},
@ -69,17 +67,14 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "files",
Description: "stage file",
Contexts: []string{"files"},
},
{
ViewName: "files",
Description: "unstage file",
Contexts: []string{"files"},
},
{
ViewName: "files",
ViewName: "submodules",
Description: "drop submodule",
Contexts: []string{"submodules"},
},
},
expected: []*bindingSection{
@ -89,12 +84,10 @@ func TestGetBindingSections(t *testing.T) {
{
ViewName: "files",
Description: "stage file",
Contexts: []string{"files"},
},
{
ViewName: "files",
Description: "unstage file",
Contexts: []string{"files"},
},
},
},
@ -102,9 +95,8 @@ func TestGetBindingSections(t *testing.T) {
title: "Submodules",
bindings: []*types.Binding{
{
ViewName: "files",
ViewName: "submodules",
Description: "drop submodule",
Contexts: []string{"submodules"},
},
},
},
@ -115,23 +107,19 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "stage file",
},
{
ViewName: "files",
Contexts: []string{"files"},
Description: "unstage file",
},
{
ViewName: "files",
Contexts: []string{"files"},
Description: "scroll",
Tag: "navigation",
},
{
ViewName: "commits",
Contexts: []string{"commits"},
Description: "revert commit",
},
},
@ -141,7 +129,6 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "scroll",
Tag: "navigation",
},
@ -152,7 +139,6 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "commits",
Contexts: []string{"commits"},
Description: "revert commit",
},
},
@ -162,12 +148,10 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "stage file",
},
{
ViewName: "files",
Contexts: []string{"files"},
Description: "unstage file",
},
},
@ -179,34 +163,28 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "stage file",
},
{
ViewName: "files",
Contexts: []string{"files"},
Description: "unstage file",
},
{
ViewName: "files",
Contexts: []string{"files"},
Description: "scroll",
Tag: "navigation",
},
{
ViewName: "commits",
Contexts: []string{"commits"},
Description: "revert commit",
},
{
ViewName: "commits",
Contexts: []string{"commits"},
Description: "scroll",
Tag: "navigation",
},
{
ViewName: "commits",
Contexts: []string{"commits"},
Description: "page up",
Tag: "navigation",
},
@ -217,13 +195,11 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "scroll",
Tag: "navigation",
},
{
ViewName: "commits",
Contexts: []string{"commits"},
Description: "page up",
Tag: "navigation",
},
@ -234,7 +210,6 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "commits",
Contexts: []string{"commits"},
Description: "revert commit",
},
},
@ -244,12 +219,10 @@ func TestGetBindingSections(t *testing.T) {
bindings: []*types.Binding{
{
ViewName: "files",
Contexts: []string{"files"},
Description: "stage file",
},
{
ViewName: "files",
Contexts: []string{"files"},
Description: "unstage file",
},
},

View File

@ -11,7 +11,7 @@ import (
// this takes something like:
// * (HEAD detached at 264fc6f5)
// remotes
// remotes
// and returns '264fc6f5' as the second match
const CurrentBranchNameRegex = `(?m)^\*.*?([^ ]*?)\)?$`

View File

@ -190,7 +190,7 @@ func (p *PatchManager) RenderPatchForFile(filename string, plain bool, reverse b
parser := NewPatchParser(p.Log, patch)
// not passing included lines because we don't want to see them in the secondary panel
return parser.Render(-1, -1, nil)
return parser.Render(false, -1, -1, nil)
}
func (p *PatchManager) renderEachFilePatch(plain bool) []string {

View File

@ -183,7 +183,7 @@ func parsePatch(patch string) ([]int, []int, []*PatchLine) {
}
// Render returns the coloured string of the diff with any selected lines highlighted
func (p *PatchParser) Render(firstLineIndex int, lastLineIndex int, incLineIndices []int) string {
func (p *PatchParser) Render(isFocused bool, firstLineIndex int, lastLineIndex int, incLineIndices []int) string {
contentToDisplay := slices.Some(p.PatchLines, func(line *PatchLine) bool {
return line.Content != ""
})
@ -192,7 +192,7 @@ func (p *PatchParser) Render(firstLineIndex int, lastLineIndex int, incLineIndic
}
renderedLines := slices.MapWithIndex(p.PatchLines, func(patchLine *PatchLine, index int) string {
selected := index >= firstLineIndex && index <= lastLineIndex
selected := isFocused && index >= firstLineIndex && index <= lastLineIndex
included := lo.Contains(incLineIndices, index)
return patchLine.render(selected, included)
})
@ -202,12 +202,18 @@ func (p *PatchParser) Render(firstLineIndex int, lastLineIndex int, incLineIndic
return result
}
// PlainRenderLines returns the non-coloured string of diff part from firstLineIndex to
// lastLineIndex
func (p *PatchParser) PlainRenderLines(firstLineIndex, lastLineIndex int) string {
linesToCopy := p.PatchLines[firstLineIndex : lastLineIndex+1]
func (p *PatchParser) RenderPlain() string {
return renderLinesPlain(p.PatchLines)
}
renderedLines := slices.Map(linesToCopy, func(line *PatchLine) string {
// RenderLinesPlain returns the non-coloured string of diff part from firstLineIndex to
// lastLineIndex
func (p *PatchParser) RenderLinesPlain(firstLineIndex, lastLineIndex int) string {
return renderLinesPlain(p.PatchLines[firstLineIndex : lastLineIndex+1])
}
func renderLinesPlain(lines []*PatchLine) string {
renderedLines := slices.Map(lines, func(line *PatchLine) string {
return line.Content
})

View File

@ -105,20 +105,13 @@ func (gui *Gui) mainSectionChildren() []*boxlayout.Box {
}
}
main := "main"
secondary := "secondary"
if gui.secondaryViewFocused() {
// when you think you've focused the secondary view, we've actually just swapped them around in the layout
main, secondary = secondary, main
}
return []*boxlayout.Box{
{
Window: main,
Window: "main",
Weight: 1,
},
{
Window: secondary,
Window: "secondary",
Weight: 1,
},
}

View File

@ -12,6 +12,7 @@ func (gui *Gui) branchesRenderToMain() error {
}
return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{
title: gui.c.Tr.LogTitle,
task: task,

View File

@ -12,10 +12,10 @@ import (
)
// our UI command log looks like this:
// Stage File
// git add -- 'filename'
// Unstage File
// git reset HEAD 'filename'
// Stage File:
// git add -- 'filename'
// Unstage File:
// git reset HEAD 'filename'
//
// The 'Stage File' and 'Unstage File' lines are actions i.e they group up a set
// of command logs (typically there's only one command under an action but there may be more).

View File

@ -2,14 +2,9 @@ package gui
import (
"github.com/jesseduffield/lazygit/pkg/gui/controllers"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
// TODO: do we need this?
func (gui *Gui) onCommitFileFocus() error {
gui.escapeLineByLinePanel()
return nil
}
func (gui *Gui) commitFilesRenderToMain() error {
node := gui.State.Contexts.CommitFiles.GetSelected()
if node == nil {
@ -23,16 +18,16 @@ func (gui *Gui) commitFilesRenderToMain() error {
cmdObj := gui.git.WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false)
task := NewRunPtyTask(cmdObj.GetCmd())
mainContext := gui.State.Contexts.Normal
pair := gui.normalMainContextPair()
if node.File != nil {
mainContext = gui.State.Contexts.PatchBuilding
pair = gui.patchBuildingMainContextPair()
}
return gui.refreshMainViews(refreshMainOpts{
pair: pair,
main: &viewUpdateOpts{
title: "Patch",
task: task,
context: mainContext,
title: gui.Tr.Patch,
task: task,
},
secondary: gui.secondaryPatchPanelUpdateOpts(),
})
@ -46,33 +41,11 @@ func (gui *Gui) SwitchToCommitFilesContext(opts controllers.SwitchToCommitFilesC
gui.State.Contexts.CommitFiles.SetParentContext(opts.Context)
gui.State.Contexts.CommitFiles.SetWindowName(opts.Context.GetWindowName())
if err := gui.refreshCommitFilesContext(); err != nil {
if err := gui.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.COMMIT_FILES},
}); err != nil {
return err
}
return gui.c.PushContext(gui.State.Contexts.CommitFiles)
}
func (gui *Gui) refreshCommitFilesContext() error {
ref := gui.State.Contexts.CommitFiles.GetRef()
to := ref.RefName()
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
files, err := gui.git.Loaders.CommitFiles.GetFilesInDiff(from, to, reverse)
if err != nil {
return gui.c.Error(err)
}
gui.State.Model.CommitFiles = files
gui.State.Contexts.CommitFiles.CommitFileTreeViewModel.SetTree()
return gui.c.PostRefreshUpdate(gui.State.Contexts.CommitFiles)
}
func (gui *Gui) getSelectedCommitFileName() string {
node := gui.State.Contexts.CommitFiles.GetSelected()
if node == nil {
return ""
}
return node.Path
}

View File

@ -25,8 +25,6 @@ func (gui *Gui) onCommitFocus() error {
})
}
gui.escapeLineByLinePanel()
return nil
}
@ -41,6 +39,7 @@ func (gui *Gui) branchCommitsRenderToMain() error {
}
return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{
title: "Patch",
task: task,
@ -49,6 +48,19 @@ func (gui *Gui) branchCommitsRenderToMain() error {
})
}
func (gui *Gui) secondaryPatchPanelUpdateOpts() *viewUpdateOpts {
if gui.git.Patch.PatchManager.Active() {
patch := gui.git.Patch.PatchManager.RenderAggregatedPatchColored(false)
return &viewUpdateOpts{
task: NewRenderStringWithoutScrollTask(patch),
title: gui.Tr.CustomPatch,
}
}
return nil
}
func (gui *Gui) refForLog() string {
bisectInfo := gui.git.Bisect.GetInfo()
gui.State.Model.BisectInfo = bisectInfo

View File

@ -5,7 +5,6 @@ import (
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
@ -221,25 +220,21 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
bindings := []*types.Binding{
{
ViewName: "confirmation",
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.Confirm),
Handler: onConfirm,
},
{
ViewName: "confirmation",
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
Handler: onConfirm,
},
{
ViewName: "confirmation",
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(opts.HandleClose),
},
{
ViewName: "confirmation",
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
Handler: func() error {
if len(gui.State.Suggestions) > 0 {
@ -250,25 +245,21 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
},
{
ViewName: "suggestions",
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.Confirm),
Handler: onSuggestionConfirm,
},
{
ViewName: "suggestions",
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
Handler: onSuggestionConfirm,
},
{
ViewName: "suggestions",
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(opts.HandleClose),
},
{
ViewName: "suggestions",
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
Handler: func() error { return gui.replaceContext(gui.State.Contexts.Confirmation) },
},

View File

@ -1,8 +1,6 @@
package gui
import (
"errors"
"fmt"
"sort"
"strings"
@ -28,24 +26,6 @@ func (gui *Gui) popupViewNames() []string {
})
}
func (gui *Gui) currentContextKeyIgnoringPopups() types.ContextKey {
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
stack := gui.State.ContextManager.ContextStack
for i := range stack {
reversedIndex := len(stack) - 1 - i
context := stack[reversedIndex]
kind := stack[reversedIndex].GetKind()
if kind != types.TEMPORARY_POPUP && kind != types.PERSISTENT_POPUP {
return context.GetKey()
}
}
return ""
}
// use replaceContext when you don't want to return to the original context upon
// hitting escape: you want to go that context's parent instead.
func (gui *Gui) replaceContext(c types.Context) error {
@ -64,31 +44,43 @@ func (gui *Gui) replaceContext(c types.Context) error {
defer gui.State.ContextManager.Unlock()
return gui.activateContext(c)
return gui.activateContext(c, types.OnFocusOpts{})
}
func (gui *Gui) pushContext(c types.Context, opts ...types.OnFocusOpts) error {
// using triple dot but you should only ever pass one of these opt structs
if len(opts) > 1 {
return errors.New("cannot pass multiple opts to pushContext")
}
func (gui *Gui) pushContext(c types.Context, opts types.OnFocusOpts) error {
if !c.IsFocusable() {
return nil
}
contextsToDeactivate := gui.pushToContextStack(c)
for _, contextToDeactivate := range contextsToDeactivate {
if err := gui.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
return err
}
}
return gui.activateContext(c, opts)
}
// Adjusts the context stack based on the context that's being pushed and returns contexts to deactivate
func (gui *Gui) pushToContextStack(c types.Context) []types.Context {
contextsToDeactivate := []types.Context{}
gui.State.ContextManager.Lock()
defer gui.State.ContextManager.Unlock()
if len(gui.State.ContextManager.ContextStack) == 0 {
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
} else if c.GetKind() == types.SIDE_CONTEXT {
// if we are switching to a side context, remove all other contexts in the stack
contextsToDeactivate = gui.State.ContextManager.ContextStack
gui.State.ContextManager.ContextStack = []types.Context{c}
} else if c.GetKind() == types.MAIN_CONTEXT {
// if we're switching to a main context, remove all other main contexts in the stack
for _, stackContext := range gui.State.ContextManager.ContextStack {
if stackContext.GetKey() != c.GetKey() {
if err := gui.deactivateContext(stackContext); err != nil {
gui.State.ContextManager.Unlock()
return err
}
if stackContext.GetKind() == types.MAIN_CONTEXT {
contextsToDeactivate = append(contextsToDeactivate, stackContext)
}
}
gui.State.ContextManager.ContextStack = []types.Context{c}
@ -101,12 +93,11 @@ func (gui *Gui) pushContext(c types.Context, opts ...types.OnFocusOpts) error {
// escape back to previous temporary popups, but because we're currently reusing
// views for this, you might not be able to get back to where you previously were.
// The exception is when going to the search context e.g. for searching a menu.
if topContext.GetKind() == types.TEMPORARY_POPUP && c.GetKey() != context.SEARCH_CONTEXT_KEY {
if err := gui.deactivateContext(topContext); err != nil {
gui.State.ContextManager.Unlock()
return err
}
if (topContext.GetKind() == types.TEMPORARY_POPUP && c.GetKey() != context.SEARCH_CONTEXT_KEY) ||
// we only ever want one main context on the stack at a time.
(topContext.GetKind() == types.MAIN_CONTEXT && c.GetKind() == types.MAIN_CONTEXT) {
contextsToDeactivate = append(contextsToDeactivate, topContext)
_, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
}
@ -114,16 +105,7 @@ func (gui *Gui) pushContext(c types.Context, opts ...types.OnFocusOpts) error {
}
}
gui.State.ContextManager.Unlock()
return gui.activateContext(c, opts...)
}
// pushContextWithView is to be used when you don't know which context you
// want to switch to: you only know the view that you want to switch to. It will
// look up the context currently active for that view and switch to that context
func (gui *Gui) pushContextWithView(viewName string) error {
return gui.c.PushContext(gui.State.ViewContextMap.Get(viewName))
return contextsToDeactivate
}
func (gui *Gui) popContext() error {
@ -140,18 +122,16 @@ func (gui *Gui) popContext() error {
newContext := gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
gui.g.SetCurrentContext(string(newContext.GetKey()))
gui.State.ContextManager.Unlock()
if err := gui.deactivateContext(currentContext); err != nil {
if err := gui.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
return err
}
return gui.activateContext(newContext)
return gui.activateContext(newContext, types.OnFocusOpts{})
}
func (gui *Gui) deactivateContext(c types.Context) error {
func (gui *Gui) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
view, _ := gui.g.View(c.GetViewName())
if view != nil && view.IsSearching() {
@ -167,7 +147,7 @@ func (gui *Gui) deactivateContext(c types.Context) error {
view.Visible = false
}
if err := c.HandleFocusLost(); err != nil {
if err := c.HandleFocusLost(opts); err != nil {
return err
}
@ -178,16 +158,12 @@ func (gui *Gui) deactivateContext(c types.Context) error {
// if the context's view is set to another context we do nothing.
// if the context's view is the current view we trigger a focus; re-selecting the current item.
func (gui *Gui) postRefreshUpdate(c types.Context) error {
if gui.State.ViewContextMap.Get(c.GetViewName()).GetKey() != c.GetKey() {
return nil
}
if err := c.HandleRender(); err != nil {
return err
}
if gui.currentViewName() == c.GetViewName() {
if err := c.HandleFocus(); err != nil {
if err := c.HandleFocus(types.OnFocusOpts{}); err != nil {
return err
}
}
@ -195,29 +171,16 @@ func (gui *Gui) postRefreshUpdate(c types.Context) error {
return nil
}
func (gui *Gui) activateContext(c types.Context, opts ...types.OnFocusOpts) error {
func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
viewName := c.GetViewName()
v, err := gui.g.View(viewName)
if err != nil {
return err
}
originalViewContext := gui.State.ViewContextMap.Get(viewName)
var originalViewContextKey types.ContextKey = ""
if originalViewContext != nil {
originalViewContextKey = originalViewContext.GetKey()
}
gui.setWindowContext(c)
gui.setViewTabForContext(c)
if viewName == "main" {
gui.changeMainViewsContext(c)
} else {
gui.changeMainViewsContext(gui.State.Contexts.Normal)
}
gui.g.SetCurrentContext(string(c.GetKey()))
gui.moveToTopOfWindow(c)
if _, err := gui.g.SetCurrentView(viewName); err != nil {
return err
}
@ -229,15 +192,6 @@ func (gui *Gui) activateContext(c types.Context, opts ...types.OnFocusOpts) erro
v.Visible = true
// if the new context's view was previously displaying another context, render the new context
if originalViewContextKey != c.GetKey() {
if err := c.HandleRender(); err != nil {
return err
}
}
gui.ViewContextMapSet(viewName, c)
gui.g.Cursor = v.Editable
// render the options available for the current context at the bottom of the screen
@ -247,7 +201,7 @@ func (gui *Gui) activateContext(c types.Context, opts ...types.OnFocusOpts) erro
}
gui.renderOptionsMap(optionsMap)
if err := c.HandleFocus(opts...); err != nil {
if err := c.HandleFocus(opts); err != nil {
return err
}
@ -266,16 +220,6 @@ func (gui *Gui) renderOptionsMap(optionsMap map[string]string) {
_ = gui.renderString(gui.Views.Options, gui.optionsMapToString(optionsMap))
}
// also setting context on view for now. We'll need to pick one of these two approaches to stick with.
func (gui *Gui) ViewContextMapSet(viewName string, c types.Context) {
gui.State.ViewContextMap.Set(viewName, c)
view, err := gui.g.View(viewName)
if err != nil {
panic(err)
}
view.Context = string(c.GetKey())
}
// // currently unused
// func (gui *Gui) renderContextStack() string {
// result := ""
@ -339,6 +283,10 @@ func (gui *Gui) currentStaticContext() types.Context {
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
return gui.currentStaticContextWithoutLock()
}
func (gui *Gui) currentStaticContextWithoutLock() types.Context {
stack := gui.State.ContextManager.ContextStack
if len(stack) == 0 {
@ -400,57 +348,14 @@ func (gui *Gui) TransientContexts() []types.Context {
})
}
// changeContext is a helper function for when we want to change a 'main' context
// which currently just means a context that affects both the main and secondary views
// other views can have their context changed directly but this function helps
// keep the main and secondary views in sync
func (gui *Gui) changeMainViewsContext(c types.Context) {
if gui.State.MainContext == c.GetKey() {
return
}
switch c.GetKey() {
case context.MAIN_NORMAL_CONTEXT_KEY, context.MAIN_PATCH_BUILDING_CONTEXT_KEY, context.MAIN_STAGING_CONTEXT_KEY, context.MAIN_MERGING_CONTEXT_KEY:
gui.ViewContextMapSet(gui.Views.Main.Name(), c)
gui.ViewContextMapSet(gui.Views.Secondary.Name(), c)
default:
panic(fmt.Sprintf("unknown context for main: %s", c.GetKey()))
}
gui.State.MainContext = c.GetKey()
}
func (gui *Gui) viewTabNames(viewName string) []string {
tabContexts := gui.State.ViewTabContextMap[viewName]
return slices.Map(tabContexts, func(tabContext context.TabContext) string {
return tabContext.Tab
})
}
func (gui *Gui) setViewTabForContext(c types.Context) {
// search for the context in our map and if we find it, set the tab for the corresponding view
tabContexts, ok := gui.State.ViewTabContextMap[c.GetViewName()]
if !ok {
return
}
for tabIndex, tabContext := range tabContexts {
if tabContext.Context.GetKey() == c.GetKey() {
// get the view, set the tab
v, err := gui.g.View(c.GetViewName())
if err != nil {
gui.c.Log.Error(err)
return
}
v.TabIndex = tabIndex
return
}
}
}
func (gui *Gui) rerenderView(view *gocui.View) error {
return gui.State.ViewContextMap.Get(view.Name()).HandleRender()
context, ok := gui.contextForView(view.Name())
if !ok {
gui.Log.Errorf("no context found for view %s", view.Name())
return nil
}
return context.HandleRender()
}
func (gui *Gui) getSideContextSelectedItemId() string {
@ -462,11 +367,6 @@ func (gui *Gui) getSideContextSelectedItemId() string {
return currentSideContext.GetSelectedItemId()
}
func (gui *Gui) isContextVisible(c types.Context) bool {
return gui.State.WindowViewNameMap[c.GetWindowName()] == c.GetViewName() &&
gui.State.ViewContextMap.Get(c.GetViewName()).GetKey() == c.GetKey()
}
// currently unused
// func (gui *Gui) getCurrentSideView() *gocui.View {
// currentSideContext := gui.currentSideContext()

View File

@ -8,7 +8,8 @@ import (
type BaseContext struct {
kind types.ContextKind
key types.ContextKey
ViewName string
view *gocui.View
viewTrait types.IViewTrait
windowName string
onGetOptionsMap func() map[string]string
@ -16,8 +17,9 @@ type BaseContext struct {
mouseKeybindingsFns []types.MouseKeybindingsFn
onClickFn func() error
focusable bool
transient bool
focusable bool
transient bool
hasControlledBounds bool
*ParentContextMgr
}
@ -25,26 +27,33 @@ type BaseContext struct {
var _ types.IBaseContext = &BaseContext{}
type NewBaseContextOpts struct {
Kind types.ContextKind
Key types.ContextKey
ViewName string
WindowName string
Focusable bool
Transient bool
Kind types.ContextKind
Key types.ContextKey
View *gocui.View
WindowName string
Focusable bool
Transient bool
HasUncontrolledBounds bool // negating for the sake of making false the default
OnGetOptionsMap func() map[string]string
}
func NewBaseContext(opts NewBaseContextOpts) *BaseContext {
viewTrait := NewViewTrait(opts.View)
hasControlledBounds := !opts.HasUncontrolledBounds
return &BaseContext{
kind: opts.Kind,
key: opts.Key,
ViewName: opts.ViewName,
windowName: opts.WindowName,
onGetOptionsMap: opts.OnGetOptionsMap,
focusable: opts.Focusable,
transient: opts.Transient,
ParentContextMgr: &ParentContextMgr{},
kind: opts.Kind,
key: opts.Key,
view: opts.View,
windowName: opts.WindowName,
onGetOptionsMap: opts.OnGetOptionsMap,
focusable: opts.Focusable,
transient: opts.Transient,
hasControlledBounds: hasControlledBounds,
ParentContextMgr: &ParentContextMgr{},
viewTrait: viewTrait,
}
}
@ -64,7 +73,20 @@ func (self *BaseContext) GetWindowName() string {
}
func (self *BaseContext) GetViewName() string {
return self.ViewName
// for the sake of the global context which has no view
if self.view == nil {
return ""
}
return self.view.Name()
}
func (self *BaseContext) GetView() *gocui.View {
return self.view
}
func (self *BaseContext) GetViewTrait() types.IViewTrait {
return self.viewTrait
}
func (self *BaseContext) GetKind() types.ContextKind {
@ -123,6 +145,10 @@ func (self *BaseContext) IsTransient() bool {
return self.transient
}
func (self *BaseContext) HasControlledBounds() bool {
return self.hasControlledBounds
}
func (self *BaseContext) Title() string {
return ""
}

View File

@ -18,9 +18,9 @@ func NewBranchesContext(
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *BranchesContext {
@ -30,7 +30,7 @@ func NewBranchesContext(
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "branches",
View: view,
WindowName: "branches",
Key: LOCAL_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
@ -41,7 +41,6 @@ func NewBranchesContext(
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},

View File

@ -20,9 +20,9 @@ func NewCommitFilesContext(
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *CommitFilesContext {
@ -34,7 +34,7 @@ func NewCommitFilesContext(
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
ViewName: "commitFiles",
View: view,
WindowName: "commits",
Key: COMMIT_FILES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
@ -47,7 +47,6 @@ func NewCommitFilesContext(
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},

View File

@ -1,39 +1,48 @@
package context
import (
"sync"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
const (
GLOBAL_CONTEXT_KEY types.ContextKey = "global"
STATUS_CONTEXT_KEY types.ContextKey = "status"
FILES_CONTEXT_KEY types.ContextKey = "files"
LOCAL_BRANCHES_CONTEXT_KEY types.ContextKey = "localBranches"
REMOTES_CONTEXT_KEY types.ContextKey = "remotes"
REMOTE_BRANCHES_CONTEXT_KEY types.ContextKey = "remoteBranches"
TAGS_CONTEXT_KEY types.ContextKey = "tags"
LOCAL_COMMITS_CONTEXT_KEY types.ContextKey = "commits"
REFLOG_COMMITS_CONTEXT_KEY types.ContextKey = "reflogCommits"
SUB_COMMITS_CONTEXT_KEY types.ContextKey = "subCommits"
COMMIT_FILES_CONTEXT_KEY types.ContextKey = "commitFiles"
STASH_CONTEXT_KEY types.ContextKey = "stash"
MAIN_NORMAL_CONTEXT_KEY types.ContextKey = "normal"
MAIN_MERGING_CONTEXT_KEY types.ContextKey = "merging"
MAIN_PATCH_BUILDING_CONTEXT_KEY types.ContextKey = "patchBuilding"
MAIN_STAGING_CONTEXT_KEY types.ContextKey = "staging"
MENU_CONTEXT_KEY types.ContextKey = "menu"
CONFIRMATION_CONTEXT_KEY types.ContextKey = "confirmation"
SEARCH_CONTEXT_KEY types.ContextKey = "search"
COMMIT_MESSAGE_CONTEXT_KEY types.ContextKey = "commitMessage"
SUBMODULES_CONTEXT_KEY types.ContextKey = "submodules"
SUGGESTIONS_CONTEXT_KEY types.ContextKey = "suggestions"
COMMAND_LOG_CONTEXT_KEY types.ContextKey = "cmdLog"
GLOBAL_CONTEXT_KEY types.ContextKey = "global"
STATUS_CONTEXT_KEY types.ContextKey = "status"
FILES_CONTEXT_KEY types.ContextKey = "files"
LOCAL_BRANCHES_CONTEXT_KEY types.ContextKey = "localBranches"
REMOTES_CONTEXT_KEY types.ContextKey = "remotes"
REMOTE_BRANCHES_CONTEXT_KEY types.ContextKey = "remoteBranches"
TAGS_CONTEXT_KEY types.ContextKey = "tags"
LOCAL_COMMITS_CONTEXT_KEY types.ContextKey = "commits"
REFLOG_COMMITS_CONTEXT_KEY types.ContextKey = "reflogCommits"
SUB_COMMITS_CONTEXT_KEY types.ContextKey = "subCommits"
COMMIT_FILES_CONTEXT_KEY types.ContextKey = "commitFiles"
STASH_CONTEXT_KEY types.ContextKey = "stash"
NORMAL_MAIN_CONTEXT_KEY types.ContextKey = "normal"
NORMAL_SECONDARY_CONTEXT_KEY types.ContextKey = "normalSecondary"
STAGING_MAIN_CONTEXT_KEY types.ContextKey = "staging"
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"
// these shouldn't really be needed for anything but I'm giving them unique keys nonetheless
OPTIONS_CONTEXT_KEY types.ContextKey = "options"
APP_STATUS_CONTEXT_KEY types.ContextKey = "appStatus"
SEARCH_PREFIX_CONTEXT_KEY types.ContextKey = "searchPrefix"
INFORMATION_CONTEXT_KEY types.ContextKey = "information"
LIMIT_CONTEXT_KEY types.ContextKey = "limit"
MENU_CONTEXT_KEY types.ContextKey = "menu"
CONFIRMATION_CONTEXT_KEY types.ContextKey = "confirmation"
SEARCH_CONTEXT_KEY types.ContextKey = "search"
COMMIT_MESSAGE_CONTEXT_KEY types.ContextKey = "commitMessage"
SUBMODULES_CONTEXT_KEY types.ContextKey = "submodules"
SUGGESTIONS_CONTEXT_KEY types.ContextKey = "suggestions"
COMMAND_LOG_CONTEXT_KEY types.ContextKey = "cmdLog"
)
var AllContextKeys = []types.ContextKey{
GLOBAL_CONTEXT_KEY, // not focusable
GLOBAL_CONTEXT_KEY,
STATUS_CONTEXT_KEY,
FILES_CONTEXT_KEY,
LOCAL_BRANCHES_CONTEXT_KEY,
@ -45,10 +54,14 @@ var AllContextKeys = []types.ContextKey{
SUB_COMMITS_CONTEXT_KEY,
COMMIT_FILES_CONTEXT_KEY,
STASH_CONTEXT_KEY,
MAIN_NORMAL_CONTEXT_KEY, // not focusable
MAIN_MERGING_CONTEXT_KEY,
MAIN_PATCH_BUILDING_CONTEXT_KEY,
MAIN_STAGING_CONTEXT_KEY, // not focusable for secondary view
NORMAL_MAIN_CONTEXT_KEY,
NORMAL_SECONDARY_CONTEXT_KEY,
STAGING_MAIN_CONTEXT_KEY,
STAGING_SECONDARY_CONTEXT_KEY,
PATCH_BUILDING_MAIN_CONTEXT_KEY,
PATCH_BUILDING_SECONDARY_CONTEXT_KEY,
MERGING_MAIN_CONTEXT_KEY,
MENU_CONTEXT_KEY,
CONFIRMATION_CONTEXT_KEY,
SEARCH_CONTEXT_KEY,
@ -59,87 +72,81 @@ var AllContextKeys = []types.ContextKey{
}
type ContextTree struct {
Global types.Context
Status types.Context
Files *WorkingTreeContext
Menu *MenuContext
Branches *BranchesContext
Tags *TagsContext
LocalCommits *LocalCommitsContext
CommitFiles *CommitFilesContext
Remotes *RemotesContext
Submodules *SubmodulesContext
RemoteBranches *RemoteBranchesContext
ReflogCommits *ReflogCommitsContext
SubCommits *SubCommitsContext
Stash *StashContext
Suggestions *SuggestionsContext
Normal types.Context
Staging types.Context
PatchBuilding types.Context
Merging types.Context
Confirmation types.Context
CommitMessage types.Context
Search types.Context
CommandLog types.Context
Global types.Context
Status types.Context
Files *WorkingTreeContext
Menu *MenuContext
Branches *BranchesContext
Tags *TagsContext
LocalCommits *LocalCommitsContext
CommitFiles *CommitFilesContext
Remotes *RemotesContext
Submodules *SubmodulesContext
RemoteBranches *RemoteBranchesContext
ReflogCommits *ReflogCommitsContext
SubCommits *SubCommitsContext
Stash *StashContext
Suggestions *SuggestionsContext
Normal types.Context
NormalSecondary types.Context
Staging *PatchExplorerContext
StagingSecondary *PatchExplorerContext
CustomPatchBuilder *PatchExplorerContext
CustomPatchBuilderSecondary types.Context
Merging types.Context
Confirmation types.Context
CommitMessage types.Context
CommandLog types.Context
// display contexts
AppStatus types.Context
Options types.Context
SearchPrefix types.Context
Search types.Context
Information types.Context
Limit types.Context
}
// the order of this decides which context is initially at the top of its window
func (self *ContextTree) Flatten() []types.Context {
return []types.Context{
self.Global,
self.Status,
self.Files,
self.Submodules,
self.Branches,
self.Files,
self.SubCommits,
self.Remotes,
self.RemoteBranches,
self.Tags,
self.LocalCommits,
self.Branches,
self.CommitFiles,
self.ReflogCommits,
self.LocalCommits,
self.Stash,
self.Menu,
self.Confirmation,
self.CommitMessage,
self.Normal,
self.Staging,
self.Merging,
self.PatchBuilding,
self.SubCommits,
self.StagingSecondary,
self.Staging,
self.CustomPatchBuilderSecondary,
self.CustomPatchBuilder,
self.NormalSecondary,
self.Normal,
self.Suggestions,
self.CommandLog,
self.AppStatus,
self.Options,
self.SearchPrefix,
self.Search,
self.Information,
self.Limit,
}
}
type ViewContextMap struct {
content map[string]types.Context
sync.RWMutex
}
func NewViewContextMap() *ViewContextMap {
return &ViewContextMap{content: map[string]types.Context{}}
}
func (self *ViewContextMap) Get(viewName string) types.Context {
self.RLock()
defer self.RUnlock()
return self.content[viewName]
}
func (self *ViewContextMap) Set(viewName string, context types.Context) {
self.Lock()
defer self.Unlock()
self.content[viewName] = context
}
func (self *ViewContextMap) Entries() map[string]types.Context {
self.Lock()
defer self.Unlock()
return self.content
}
type TabContext struct {
Tab string
Context types.Context
type TabView struct {
Tab string
ViewName string
}

View File

@ -12,7 +12,6 @@ type ListContextTrait struct {
c *types.HelperCommon
list types.IList
viewTrait *ViewTrait
getDisplayStrings func(startIdx int, length int) [][]string
}
@ -20,43 +19,39 @@ func (self *ListContextTrait) GetList() types.IList {
return self.list
}
func (self *ListContextTrait) GetViewTrait() types.IViewTrait {
return self.viewTrait
}
func (self *ListContextTrait) FocusLine() {
// we need a way of knowing whether we've rendered to the view yet.
self.viewTrait.FocusPoint(self.list.GetSelectedLineIdx())
self.GetViewTrait().FocusPoint(self.list.GetSelectedLineIdx())
self.setFooter()
}
func (self *ListContextTrait) setFooter() {
self.viewTrait.SetFooter(formatListFooter(self.list.GetSelectedLineIdx(), self.list.Len()))
self.GetViewTrait().SetFooter(formatListFooter(self.list.GetSelectedLineIdx(), self.list.Len()))
}
func formatListFooter(selectedLineIdx int, length int) string {
return fmt.Sprintf("%d of %d", selectedLineIdx+1, length)
}
func (self *ListContextTrait) HandleFocus(opts ...types.OnFocusOpts) error {
func (self *ListContextTrait) HandleFocus(opts types.OnFocusOpts) error {
self.FocusLine()
self.viewTrait.SetHighlight(self.list.Len() > 0)
self.GetViewTrait().SetHighlight(self.list.Len() > 0)
return self.Context.HandleFocus(opts...)
return self.Context.HandleFocus(opts)
}
func (self *ListContextTrait) HandleFocusLost() error {
self.viewTrait.SetOriginX(0)
func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) error {
self.GetViewTrait().SetOriginX(0)
return self.Context.HandleFocusLost()
return self.Context.HandleFocusLost(opts)
}
// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view
func (self *ListContextTrait) HandleRender() error {
self.list.RefreshSelectedIdx()
content := utils.RenderDisplayStrings(self.getDisplayStrings(0, self.list.Len()))
self.viewTrait.SetContent(content)
self.GetViewTrait().SetContent(content)
self.c.Render()
self.setFooter()
@ -65,5 +60,5 @@ func (self *ListContextTrait) HandleRender() error {
func (self *ListContextTrait) OnSearchSelect(selectedLineIdx int) error {
self.GetList().SetSelectedLineIdx(selectedLineIdx)
return self.HandleFocus()
return self.HandleFocus(types.OnFocusOpts{})
}

View File

@ -18,9 +18,9 @@ func NewLocalCommitsContext(
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *LocalCommitsContext {
@ -31,7 +31,7 @@ func NewLocalCommitsContext(
ViewportListContextTrait: &ViewportListContextTrait{
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "commits",
View: view,
WindowName: "commits",
Key: LOCAL_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
@ -42,7 +42,6 @@ func NewLocalCommitsContext(
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},

View File

@ -24,7 +24,7 @@ func NewMenuContext(
) *MenuContext {
viewModel := NewMenuViewModel()
onFocus := func(...types.OnFocusOpts) error {
onFocus := func(types.OnFocusOpts) error {
selectedMenuItem := viewModel.GetSelected()
renderToDescriptionView(selectedMenuItem.Tooltip)
return nil
@ -34,17 +34,18 @@ func NewMenuContext(
MenuViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "menu",
Key: "menu",
Kind: types.TEMPORARY_POPUP,
OnGetOptionsMap: getOptionsMap,
Focusable: true,
View: view,
WindowName: "menu",
Key: "menu",
Kind: types.TEMPORARY_POPUP,
OnGetOptionsMap: getOptionsMap,
Focusable: true,
HasUncontrolledBounds: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
}),
getDisplayStrings: viewModel.GetDisplayStrings,
list: viewModel,
viewTrait: NewViewTrait(view),
c: c,
},
}

View File

@ -0,0 +1,134 @@
package context
import (
"sync"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type PatchExplorerContext struct {
*SimpleContext
state *patch_exploring.State
viewTrait *ViewTrait
getIncludedLineIndices func() []int
c *types.HelperCommon
mutex *sync.Mutex
}
var _ types.IPatchExplorerContext = (*PatchExplorerContext)(nil)
func NewPatchExplorerContext(
view *gocui.View,
windowName string,
key types.ContextKey,
onFocus func(types.OnFocusOpts) error,
onFocusLost func(opts types.OnFocusLostOpts) error,
getIncludedLineIndices func() []int,
c *types.HelperCommon,
) *PatchExplorerContext {
return &PatchExplorerContext{
state: nil,
viewTrait: NewViewTrait(view),
c: c,
mutex: &sync.Mutex{},
getIncludedLineIndices: getIncludedLineIndices,
SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view,
WindowName: windowName,
Key: key,
Kind: types.MAIN_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
}),
}
}
func (self *PatchExplorerContext) GetState() *patch_exploring.State {
return self.state
}
func (self *PatchExplorerContext) SetState(state *patch_exploring.State) {
self.state = state
}
func (self *PatchExplorerContext) GetViewTrait() types.IViewTrait {
return self.viewTrait
}
func (self *PatchExplorerContext) GetIncludedLineIndices() []int {
return self.getIncludedLineIndices()
}
func (self *PatchExplorerContext) RenderAndFocus(isFocused bool) error {
self.GetView().SetContent(self.GetContentToRender(isFocused))
if err := self.focusSelection(); err != nil {
return err
}
self.c.Render()
return nil
}
func (self *PatchExplorerContext) Render(isFocused bool) error {
self.GetView().SetContent(self.GetContentToRender(isFocused))
self.c.Render()
return nil
}
func (self *PatchExplorerContext) Focus() error {
if err := self.focusSelection(); err != nil {
return err
}
self.c.Render()
return nil
}
func (self *PatchExplorerContext) focusSelection() error {
view := self.GetView()
state := self.GetState()
_, viewHeight := view.Size()
bufferHeight := viewHeight - 1
_, origin := view.Origin()
selectedLineIdx := state.GetSelectedLineIdx()
newOrigin := state.CalculateOrigin(origin, bufferHeight)
if err := view.SetOriginY(newOrigin); err != nil {
return err
}
return view.SetCursor(0, selectedLineIdx-newOrigin)
}
func (self *PatchExplorerContext) GetContentToRender(isFocused bool) string {
if self.GetState() == nil {
return ""
}
return self.GetState().RenderForLineIndices(isFocused, self.GetIncludedLineIndices())
}
func (self *PatchExplorerContext) NavigateTo(isFocused bool, selectedLineIdx int) error {
self.GetState().SetLineSelectMode()
self.GetState().SelectLine(selectedLineIdx)
return self.RenderAndFocus(isFocused)
}
func (self *PatchExplorerContext) GetMutex() *sync.Mutex {
return self.mutex
}

View File

@ -18,9 +18,9 @@ func NewReflogCommitsContext(
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *ReflogCommitsContext {
@ -30,7 +30,7 @@ func NewReflogCommitsContext(
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "commits",
View: view,
WindowName: "commits",
Key: REFLOG_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
@ -41,7 +41,6 @@ func NewReflogCommitsContext(
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},

View File

@ -19,9 +19,9 @@ func NewRemoteBranchesContext(
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *RemoteBranchesContext {
@ -32,7 +32,7 @@ func NewRemoteBranchesContext(
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle),
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "remoteBranches",
View: view,
WindowName: "branches",
Key: REMOTE_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
@ -44,7 +44,6 @@ func NewRemoteBranchesContext(
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},

View File

@ -18,9 +18,9 @@ func NewRemotesContext(
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *RemotesContext {
@ -30,7 +30,7 @@ func NewRemotesContext(
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "branches",
View: view,
WindowName: "branches",
Key: REMOTES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
@ -41,7 +41,6 @@ func NewRemotesContext(
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},

View File

@ -1,25 +1,25 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type SimpleContext struct {
OnFocus func(opts ...types.OnFocusOpts) error
OnFocusLost func() error
OnFocus func(opts types.OnFocusOpts) error
OnFocusLost func(opts types.OnFocusLostOpts) error
OnRender func() error
// this is for pushing some content to the main view
OnRenderToMain func(opts ...types.OnFocusOpts) error
OnRenderToMain func() error
*BaseContext
}
type ContextCallbackOpts struct {
OnFocus func(opts ...types.OnFocusOpts) error
OnFocusLost func() error
OnRender func() error
// this is for pushing some content to the main view
OnRenderToMain func(opts ...types.OnFocusOpts) error
OnFocus func(opts types.OnFocusOpts) error
OnFocusLost func(opts types.OnFocusLostOpts) error
OnRender func() error
OnRenderToMain func() error
}
func NewSimpleContext(baseContext *BaseContext, opts ContextCallbackOpts) *SimpleContext {
@ -34,15 +34,30 @@ func NewSimpleContext(baseContext *BaseContext, opts ContextCallbackOpts) *Simpl
var _ types.Context = &SimpleContext{}
func (self *SimpleContext) HandleFocus(opts ...types.OnFocusOpts) error {
// A Display context only renders a view. It has no keybindings and is not focusable.
func NewDisplayContext(key types.ContextKey, view *gocui.View, windowName string) types.Context {
return NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.DISPLAY_CONTEXT,
Key: key,
View: view,
WindowName: windowName,
Focusable: false,
Transient: false,
}),
ContextCallbackOpts{},
)
}
func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
if self.OnFocus != nil {
if err := self.OnFocus(opts...); err != nil {
if err := self.OnFocus(opts); err != nil {
return err
}
}
if self.OnRenderToMain != nil {
if err := self.OnRenderToMain(opts...); err != nil {
if err := self.OnRenderToMain(); err != nil {
return err
}
}
@ -50,9 +65,9 @@ func (self *SimpleContext) HandleFocus(opts ...types.OnFocusOpts) error {
return nil
}
func (self *SimpleContext) HandleFocusLost() error {
func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) error {
if self.OnFocusLost != nil {
return self.OnFocusLost()
return self.OnFocusLost(opts)
}
return nil
}

View File

@ -18,9 +18,9 @@ func NewStashContext(
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *StashContext {
@ -30,7 +30,7 @@ func NewStashContext(
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "stash",
View: view,
WindowName: "stash",
Key: STASH_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
@ -41,7 +41,6 @@ func NewStashContext(
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},

View File

@ -22,9 +22,9 @@ func NewSubCommitsContext(
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *SubCommitsContext {
@ -39,7 +39,7 @@ func NewSubCommitsContext(
ViewportListContextTrait: &ViewportListContextTrait{
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "subCommits",
View: view,
WindowName: "branches",
Key: SUB_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
@ -51,7 +51,6 @@ func NewSubCommitsContext(
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},

View File

@ -18,9 +18,9 @@ func NewSubmodulesContext(
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *SubmodulesContext {
@ -30,7 +30,7 @@ func NewSubmodulesContext(
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "files",
View: view,
WindowName: "files",
Key: SUBMODULES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
@ -41,7 +41,6 @@ func NewSubmodulesContext(
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},

View File

@ -17,9 +17,9 @@ func NewSuggestionsContext(
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *SuggestionsContext {
@ -29,7 +29,7 @@ func NewSuggestionsContext(
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "suggestions",
View: view,
WindowName: "suggestions",
Key: SUGGESTIONS_CONTEXT_KEY,
Kind: types.PERSISTENT_POPUP,
@ -40,7 +40,6 @@ func NewSuggestionsContext(
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},

View File

@ -18,9 +18,9 @@ func NewTagsContext(
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *TagsContext {
@ -30,7 +30,7 @@ func NewTagsContext(
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "branches",
View: view,
WindowName: "branches",
Key: TAGS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
@ -41,7 +41,6 @@ func NewTagsContext(
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},

View File

@ -61,12 +61,12 @@ func (self *ViewTrait) horizontalScrollAmount() int {
return self.view.InnerWidth() / HORIZONTAL_SCROLL_FACTOR
}
func (self *ViewTrait) ScrollUp() {
self.view.ScrollUp(1)
func (self *ViewTrait) ScrollUp(value int) {
self.view.ScrollUp(value)
}
func (self *ViewTrait) ScrollDown() {
self.view.ScrollDown(1)
func (self *ViewTrait) ScrollDown(value int) {
self.view.ScrollDown(value)
}
// this returns the amount we'll scroll if we want to scroll by a page.

View File

@ -19,9 +19,9 @@ func NewWorkingTreeContext(
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error,
onFocusLost func() error,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *WorkingTreeContext {
@ -31,7 +31,7 @@ func NewWorkingTreeContext(
FileTreeViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "files",
View: view,
WindowName: "files",
Key: FILES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
@ -42,7 +42,6 @@ func NewWorkingTreeContext(
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings,
c: c,
},

View File

@ -9,26 +9,27 @@ func (gui *Gui) contextTree() *context.ContextTree {
return &context.ContextTree{
Global: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.GLOBAL_CONTEXT,
ViewName: "",
WindowName: "",
Key: context.GLOBAL_CONTEXT_KEY,
Focusable: false,
Kind: types.GLOBAL_CONTEXT,
View: nil,
WindowName: "",
Key: context.GLOBAL_CONTEXT_KEY,
Focusable: false,
HasUncontrolledBounds: true, // setting to true because the global context doesn't even have a view
}),
context.ContextCallbackOpts{
OnRenderToMain: OnFocusWrapper(gui.statusRenderToMain),
OnRenderToMain: gui.statusRenderToMain,
},
),
Status: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.SIDE_CONTEXT,
ViewName: "status",
View: gui.Views.Status,
WindowName: "status",
Key: context.STATUS_CONTEXT_KEY,
Focusable: true,
}),
context.ContextCallbackOpts{
OnRenderToMain: OnFocusWrapper(gui.statusRenderToMain),
OnRenderToMain: gui.statusRenderToMain,
},
),
Files: gui.filesListContext(),
@ -47,84 +48,150 @@ func (gui *Gui) contextTree() *context.ContextTree {
Normal: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
ViewName: "main",
View: gui.Views.Main,
WindowName: "main",
Key: context.MAIN_NORMAL_CONTEXT_KEY,
Key: context.NORMAL_MAIN_CONTEXT_KEY,
Focusable: false,
}),
context.ContextCallbackOpts{
OnFocus: func(opts ...types.OnFocusOpts) error {
OnFocus: func(opts types.OnFocusOpts) error {
return nil // TODO: should we do something here? We should allow for scrolling the panel
},
},
),
Staging: context.NewSimpleContext(
NormalSecondary: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
ViewName: "main",
WindowName: "main",
Key: context.MAIN_STAGING_CONTEXT_KEY,
Focusable: true,
View: gui.Views.Secondary,
WindowName: "secondary",
Key: context.NORMAL_SECONDARY_CONTEXT_KEY,
Focusable: false,
}),
context.ContextCallbackOpts{
OnFocus: func(opts ...types.OnFocusOpts) error {
forceSecondaryFocused := false
selectedLineIdx := -1
if len(opts) > 0 && opts[0].ClickedViewName != "" {
if opts[0].ClickedViewName == "main" || opts[0].ClickedViewName == "secondary" {
selectedLineIdx = opts[0].ClickedViewLineIdx
}
if opts[0].ClickedViewName == "secondary" {
forceSecondaryFocused = true
}
}
return gui.onStagingFocus(forceSecondaryFocused, selectedLineIdx)
},
},
context.ContextCallbackOpts{},
),
PatchBuilding: context.NewSimpleContext(
Staging: context.NewPatchExplorerContext(
gui.Views.Staging,
"main",
context.STAGING_MAIN_CONTEXT_KEY,
func(opts types.OnFocusOpts) error {
gui.Views.Staging.Wrap = false
gui.Views.StagingSecondary.Wrap = false
return gui.refreshStagingPanel(opts)
},
func(opts types.OnFocusLostOpts) error {
gui.State.Contexts.Staging.SetState(nil)
if opts.NewContextKey != context.STAGING_SECONDARY_CONTEXT_KEY {
gui.Views.Staging.Wrap = true
gui.Views.StagingSecondary.Wrap = true
_ = gui.State.Contexts.Staging.Render(false)
_ = gui.State.Contexts.StagingSecondary.Render(false)
}
return nil
},
func() []int { return nil },
gui.c,
),
StagingSecondary: context.NewPatchExplorerContext(
gui.Views.StagingSecondary,
"secondary",
context.STAGING_SECONDARY_CONTEXT_KEY,
func(opts types.OnFocusOpts) error {
gui.Views.Staging.Wrap = false
gui.Views.StagingSecondary.Wrap = false
return gui.refreshStagingPanel(opts)
},
func(opts types.OnFocusLostOpts) error {
gui.State.Contexts.StagingSecondary.SetState(nil)
if opts.NewContextKey != context.STAGING_MAIN_CONTEXT_KEY {
gui.Views.Staging.Wrap = true
gui.Views.StagingSecondary.Wrap = true
_ = gui.State.Contexts.Staging.Render(false)
_ = gui.State.Contexts.StagingSecondary.Render(false)
}
return nil
},
func() []int { return nil },
gui.c,
),
CustomPatchBuilder: context.NewPatchExplorerContext(
gui.Views.PatchBuilding,
"main",
context.PATCH_BUILDING_MAIN_CONTEXT_KEY,
func(opts types.OnFocusOpts) error {
// no need to change wrap on the secondary view because it can't be interacted with
gui.Views.PatchBuilding.Wrap = false
return gui.refreshPatchBuildingPanel(opts)
},
func(opts types.OnFocusLostOpts) error {
gui.Views.PatchBuilding.Wrap = true
if gui.git.Patch.PatchManager.IsEmpty() {
gui.git.Patch.PatchManager.Reset()
}
return nil
},
func() []int {
filename := gui.State.Contexts.CommitFiles.GetSelectedPath()
includedLineIndices, err := gui.git.Patch.PatchManager.GetFileIncLineIndices(filename)
if err != nil {
gui.Log.Error(err)
return nil
}
return includedLineIndices
},
gui.c,
),
CustomPatchBuilderSecondary: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
ViewName: "main",
WindowName: "main",
Key: context.MAIN_PATCH_BUILDING_CONTEXT_KEY,
Focusable: true,
View: gui.Views.PatchBuildingSecondary,
WindowName: "secondary",
Key: context.PATCH_BUILDING_SECONDARY_CONTEXT_KEY,
Focusable: false,
}),
context.ContextCallbackOpts{
OnFocus: func(opts ...types.OnFocusOpts) error {
selectedLineIdx := -1
if len(opts) > 0 && (opts[0].ClickedViewName == "main" || opts[0].ClickedViewName == "secondary") {
selectedLineIdx = opts[0].ClickedViewLineIdx
}
return gui.onPatchBuildingFocus(selectedLineIdx)
},
},
context.ContextCallbackOpts{},
),
Merging: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
ViewName: "main",
View: gui.Views.Merging,
WindowName: "main",
Key: context.MAIN_MERGING_CONTEXT_KEY,
Key: context.MERGING_MAIN_CONTEXT_KEY,
OnGetOptionsMap: gui.getMergingOptions,
Focusable: true,
}),
context.ContextCallbackOpts{
OnFocus: OnFocusWrapper(func() error { return gui.renderConflictsWithLock(true) }),
OnFocus: OnFocusWrapper(func() error {
gui.Views.Merging.Wrap = false
return gui.renderConflictsWithLock(true)
}),
OnFocusLost: func(types.OnFocusLostOpts) error {
gui.Views.Merging.Wrap = true
return nil
},
},
),
Confirmation: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.TEMPORARY_POPUP,
ViewName: "confirmation",
WindowName: "confirmation",
Key: context.CONFIRMATION_CONTEXT_KEY,
Focusable: true,
Kind: types.TEMPORARY_POPUP,
View: gui.Views.Confirmation,
WindowName: "confirmation",
Key: context.CONFIRMATION_CONTEXT_KEY,
Focusable: true,
HasUncontrolledBounds: true,
}),
context.ContextCallbackOpts{
OnFocus: OnFocusWrapper(gui.handleAskFocused),
OnFocusLost: func() error {
OnFocusLost: func(types.OnFocusLostOpts) error {
gui.deactivateConfirmationPrompt()
return nil
},
@ -132,11 +199,12 @@ func (gui *Gui) contextTree() *context.ContextTree {
),
CommitMessage: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP,
ViewName: "commitMessage",
WindowName: "commitMessage",
Key: context.COMMIT_MESSAGE_CONTEXT_KEY,
Focusable: true,
Kind: types.PERSISTENT_POPUP,
View: gui.Views.CommitMessage,
WindowName: "commitMessage",
Key: context.COMMIT_MESSAGE_CONTEXT_KEY,
Focusable: true,
HasUncontrolledBounds: true,
}),
context.ContextCallbackOpts{
OnFocus: OnFocusWrapper(gui.handleCommitMessageFocused),
@ -145,7 +213,7 @@ func (gui *Gui) contextTree() *context.ContextTree {
Search: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP,
ViewName: "search",
View: gui.Views.Search,
WindowName: "search",
Key: context.SEARCH_CONTEXT_KEY,
Focusable: true,
@ -155,26 +223,39 @@ func (gui *Gui) contextTree() *context.ContextTree {
CommandLog: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.EXTRAS_CONTEXT,
ViewName: "extras",
View: gui.Views.Extras,
WindowName: "extras",
Key: context.COMMAND_LOG_CONTEXT_KEY,
OnGetOptionsMap: gui.getMergingOptions,
Focusable: true,
}),
context.ContextCallbackOpts{
OnFocusLost: func() error {
OnFocusLost: func(opts types.OnFocusLostOpts) error {
gui.Views.Extras.Autoscroll = true
return nil
},
},
),
Options: context.NewDisplayContext(context.OPTIONS_CONTEXT_KEY, gui.Views.Options, "options"),
AppStatus: context.NewDisplayContext(context.APP_STATUS_CONTEXT_KEY, gui.Views.AppStatus, "appStatus"),
SearchPrefix: context.NewDisplayContext(context.SEARCH_PREFIX_CONTEXT_KEY, gui.Views.SearchPrefix, "searchPrefix"),
Information: context.NewDisplayContext(context.INFORMATION_CONTEXT_KEY, gui.Views.Information, "information"),
Limit: context.NewDisplayContext(context.LIMIT_CONTEXT_KEY, gui.Views.Limit, "limit"),
}
}
// using this wrapper for when an onFocus function doesn't care about any potential
// props that could be passed
func OnFocusWrapper(f func() error) func(opts ...types.OnFocusOpts) error {
return func(opts ...types.OnFocusOpts) error {
func OnFocusWrapper(f func() error) func(opts types.OnFocusOpts) error {
return func(opts types.OnFocusOpts) error {
return f()
}
}
func (gui *Gui) getPatchExplorerContexts() []types.IPatchExplorerContext {
return []types.IPatchExplorerContext{
gui.State.Contexts.Staging,
gui.State.Contexts.StagingSecondary,
gui.State.Contexts.CustomPatchBuilder,
}
}

View File

@ -27,7 +27,7 @@ func (gui *Gui) resetControllers() {
gui.helpers = &helpers.Helpers{
Refs: refsHelper,
Host: helpers.NewHostHelper(helperCommon, gui.git),
PatchBuilding: helpers.NewPatchBuildingHelper(helperCommon, gui.git),
PatchBuilding: helpers.NewPatchBuildingHelper(helperCommon, gui.git, gui.State.Contexts),
Bisect: helpers.NewBisectHelper(helperCommon, gui.git),
Suggestions: suggestionsHelper,
Files: helpers.NewFilesHelper(helperCommon, gui.git, osCommand),
@ -120,11 +120,18 @@ func (gui *Gui) resetControllers() {
)
undoController := controllers.NewUndoController(common)
globalController := controllers.NewGlobalController(common)
contextLinesController := controllers.NewContextLinesController(common)
verticalScrollControllerFactory := controllers.NewVerticalScrollControllerFactory(common)
branchesController := controllers.NewBranchesController(common)
gitFlowController := controllers.NewGitFlowController(common)
filesRemoveController := controllers.NewFilesRemoveController(common)
stashController := controllers.NewStashController(common)
commitFilesController := controllers.NewCommitFilesController(common)
patchExplorerControllerFactory := controllers.NewPatchExplorerControllerFactory(common)
stagingController := controllers.NewStagingController(common, gui.State.Contexts.Staging, gui.State.Contexts.StagingSecondary, false)
stagingSecondaryController := controllers.NewStagingController(common, gui.State.Contexts.StagingSecondary, gui.State.Contexts.Staging, true)
patchBuildingController := controllers.NewPatchBuildingController(common)
setSubCommits := func(commits []*models.Commit) { gui.State.Model.SubCommits = commits }
@ -157,19 +164,87 @@ func (gui *Gui) resetControllers() {
controllers.AttachControllers(context, controllers.NewBasicCommitsController(common, context))
}
controllers.AttachControllers(gui.State.Contexts.Files, filesController, filesRemoveController)
controllers.AttachControllers(gui.State.Contexts.Tags, tagsController)
controllers.AttachControllers(gui.State.Contexts.Submodules, submodulesController)
controllers.AttachControllers(gui.State.Contexts.LocalCommits, localCommitsController, bisectController)
controllers.AttachControllers(gui.State.Contexts.Branches, branchesController, gitFlowController)
controllers.AttachControllers(gui.State.Contexts.LocalCommits, localCommitsController, bisectController)
controllers.AttachControllers(gui.State.Contexts.CommitFiles, commitFilesController)
controllers.AttachControllers(gui.State.Contexts.Remotes, remotesController)
controllers.AttachControllers(gui.State.Contexts.Stash, stashController)
controllers.AttachControllers(gui.State.Contexts.Menu, menuController)
controllers.AttachControllers(gui.State.Contexts.CommitMessage, commitMessageController)
controllers.AttachControllers(gui.State.Contexts.RemoteBranches, remoteBranchesController)
controllers.AttachControllers(gui.State.Contexts.Global, syncController, undoController, globalController)
// TODO: add scroll controllers for main panels (need to bring some more functionality across for that e.g. reading more from the currently displayed git command)
controllers.AttachControllers(gui.State.Contexts.Staging,
stagingController,
patchExplorerControllerFactory.Create(gui.State.Contexts.Staging),
verticalScrollControllerFactory.Create(gui.State.Contexts.Staging),
)
controllers.AttachControllers(gui.State.Contexts.StagingSecondary,
stagingSecondaryController,
patchExplorerControllerFactory.Create(gui.State.Contexts.StagingSecondary),
verticalScrollControllerFactory.Create(gui.State.Contexts.StagingSecondary),
)
controllers.AttachControllers(gui.State.Contexts.CustomPatchBuilder,
patchBuildingController,
patchExplorerControllerFactory.Create(gui.State.Contexts.CustomPatchBuilder),
verticalScrollControllerFactory.Create(gui.State.Contexts.CustomPatchBuilder),
)
controllers.AttachControllers(gui.State.Contexts.CustomPatchBuilderSecondary,
verticalScrollControllerFactory.Create(gui.State.Contexts.CustomPatchBuilder),
)
controllers.AttachControllers(gui.State.Contexts.Files,
filesController,
filesRemoveController,
)
controllers.AttachControllers(gui.State.Contexts.Tags,
tagsController,
)
controllers.AttachControllers(gui.State.Contexts.Submodules,
submodulesController,
)
controllers.AttachControllers(gui.State.Contexts.LocalCommits,
localCommitsController,
bisectController,
)
controllers.AttachControllers(gui.State.Contexts.Branches,
branchesController,
gitFlowController,
)
controllers.AttachControllers(gui.State.Contexts.LocalCommits,
localCommitsController,
bisectController,
)
controllers.AttachControllers(gui.State.Contexts.CommitFiles,
commitFilesController,
)
controllers.AttachControllers(gui.State.Contexts.Remotes,
remotesController,
)
controllers.AttachControllers(gui.State.Contexts.Stash,
stashController,
)
controllers.AttachControllers(gui.State.Contexts.Menu,
menuController,
)
controllers.AttachControllers(gui.State.Contexts.CommitMessage,
commitMessageController,
)
controllers.AttachControllers(gui.State.Contexts.RemoteBranches,
remoteBranchesController,
)
controllers.AttachControllers(gui.State.Contexts.Global,
syncController,
undoController,
globalController,
contextLinesController,
)
// this must come last so that we've got our click handlers defined against the context
listControllerFactory := controllers.NewListControllerFactory(gui.c)

View File

@ -222,7 +222,7 @@ func (self *BisectController) selectCurrentBisectCommit() {
for i, commit := range self.model.Commits {
if commit.Sha == info.GetCurrentSha() {
self.context().SetSelectedLineIdx(i)
_ = self.context().HandleFocus()
_ = self.context().HandleFocus(types.OnFocusOpts{})
break
}
}

View File

@ -75,10 +75,10 @@ func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []
func (self *CommitFilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{
{
ViewName: "main",
ViewName: "patchBuilding",
Key: gocui.MouseLeft,
Handler: self.onClickMain,
FromContext: string(self.context().GetKey()),
FocusedView: self.context().GetViewName(),
},
}
}
@ -107,7 +107,7 @@ func (self *CommitFilesController) onClickMain(opts gocui.ViewMouseBindingOpts)
if node == nil {
return nil
}
return self.enterCommitFile(node, types.OnFocusOpts{ClickedViewName: "main", ClickedViewLineIdx: opts.Y})
return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: "main", ClickedViewLineIdx: opts.Y})
}
func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error {
@ -220,7 +220,7 @@ func (self *CommitFilesController) startPatchManager() error {
}
func (self *CommitFilesController) enter(node *filetree.CommitFileNode) error {
return self.enterCommitFile(node, types.OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1})
return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1})
}
func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode, opts types.OnFocusOpts) error {
@ -235,7 +235,7 @@ func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode
}
}
return self.c.PushContext(self.contexts.PatchBuilding, opts)
return self.c.PushContext(self.contexts.CustomPatchBuilder, opts)
}
if self.git.Patch.PatchManager.Active() && self.git.Patch.PatchManager.To != self.context().GetRef().RefName() {

View File

@ -0,0 +1,116 @@
package controllers
import (
"errors"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
// This controller lets you change the context size for diffs. The 'context' in 'context size' refers to the conventional meaning of the word 'context' in a diff, as opposed to lazygit's own idea of a 'context'.
var CONTEXT_KEYS_SHOWING_DIFFS = []types.ContextKey{
context.FILES_CONTEXT_KEY,
context.COMMIT_FILES_CONTEXT_KEY,
context.STASH_CONTEXT_KEY,
context.LOCAL_COMMITS_CONTEXT_KEY,
context.SUB_COMMITS_CONTEXT_KEY,
context.STAGING_MAIN_CONTEXT_KEY,
context.STAGING_SECONDARY_CONTEXT_KEY,
context.PATCH_BUILDING_MAIN_CONTEXT_KEY,
context.PATCH_BUILDING_SECONDARY_CONTEXT_KEY,
}
type ContextLinesController struct {
baseController
*controllerCommon
}
var _ types.IController = &ContextLinesController{}
func NewContextLinesController(
common *controllerCommon,
) *ContextLinesController {
return &ContextLinesController{
baseController: baseController{},
controllerCommon: common,
}
}
func (self *ContextLinesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.IncreaseContextInDiffView),
Handler: self.Increase,
Description: self.c.Tr.IncreaseContextInDiffView,
},
{
Key: opts.GetKey(opts.Config.Universal.DecreaseContextInDiffView),
Handler: self.Decrease,
Description: self.c.Tr.DecreaseContextInDiffView,
},
}
return bindings
}
func (self *ContextLinesController) Context() types.Context {
return nil
}
func (self *ContextLinesController) Increase() error {
if self.isShowingDiff() {
if err := self.checkCanChangeContext(); err != nil {
return self.c.Error(err)
}
self.c.UserConfig.Git.DiffContextSize = self.c.UserConfig.Git.DiffContextSize + 1
return self.applyChange()
}
return nil
}
func (self *ContextLinesController) Decrease() error {
old_size := self.c.UserConfig.Git.DiffContextSize
if self.isShowingDiff() && old_size > 1 {
if err := self.checkCanChangeContext(); err != nil {
return self.c.Error(err)
}
self.c.UserConfig.Git.DiffContextSize = old_size - 1
return self.applyChange()
}
return nil
}
func (self *ContextLinesController) applyChange() error {
currentContext := self.c.CurrentStaticContext()
switch currentContext.GetKey() {
// we make an exception for our staging and patch building contexts because they actually need to refresh their state afterwards.
case context.PATCH_BUILDING_MAIN_CONTEXT_KEY:
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.PATCH_BUILDING}})
case context.STAGING_MAIN_CONTEXT_KEY, context.STAGING_SECONDARY_CONTEXT_KEY:
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STAGING}})
default:
return currentContext.HandleRenderToMain()
}
}
func (self *ContextLinesController) checkCanChangeContext() error {
if self.git.Patch.PatchManager.Active() {
return errors.New(self.c.Tr.CantChangeContextSizeError)
}
return nil
}
func (self *ContextLinesController) isShowingDiff() bool {
return lo.Contains(
CONTEXT_KEYS_SHOWING_DIFFS,
self.c.CurrentStaticContext().GetKey(),
)
}

View File

@ -152,13 +152,31 @@ func (self *FilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []*
ViewName: "main",
Key: gocui.MouseLeft,
Handler: self.onClickMain,
FromContext: string(self.context().GetKey()),
FocusedView: self.context().GetViewName(),
},
{
ViewName: "patchBuilding",
Key: gocui.MouseLeft,
Handler: self.onClickMain,
FocusedView: self.context().GetViewName(),
},
{
ViewName: "merging",
Key: gocui.MouseLeft,
Handler: self.onClickMain,
FocusedView: self.context().GetViewName(),
},
{
ViewName: "secondary",
Key: gocui.MouseLeft,
Handler: self.onClickSecondary,
FromContext: string(self.context().GetKey()),
FocusedView: self.context().GetViewName(),
},
{
ViewName: "patchBuildingSecondary",
Key: gocui.MouseLeft,
Handler: self.onClickSecondary,
FocusedView: self.context().GetViewName(),
},
}
}
@ -318,7 +336,7 @@ func (self *FilesController) press(node *filetree.FileNode) error {
return err
}
return self.context().HandleFocus()
return self.context().HandleFocus(types.OnFocusOpts{})
}
func (self *FilesController) checkSelectedFileNode(callback func(*filetree.FileNode) error) func() error {
@ -349,7 +367,7 @@ func (self *FilesController) getSelectedFile() *models.File {
}
func (self *FilesController) enter() error {
return self.EnterFile(types.OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1})
return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1})
}
func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
@ -389,7 +407,7 @@ func (self *FilesController) toggleStagedAll() error {
return err
}
return self.context().HandleFocus()
return self.context().HandleFocus(types.OnFocusOpts{})
}
func (self *FilesController) toggleStagedAllWithLock() error {
@ -828,11 +846,11 @@ func (self *FilesController) handleStashSave(stashFunc func(message string) erro
}
func (self *FilesController) onClickMain(opts gocui.ViewMouseBindingOpts) error {
return self.EnterFile(types.OnFocusOpts{ClickedViewName: "main", ClickedViewLineIdx: opts.Y})
return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "main", ClickedViewLineIdx: opts.Y})
}
func (self *FilesController) onClickSecondary(opts gocui.ViewMouseBindingOpts) error {
return self.EnterFile(types.OnFocusOpts{ClickedViewName: "secondary", ClickedViewLineIdx: opts.Y})
return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "secondary", ClickedViewLineIdx: opts.Y})
}
func (self *FilesController) fetch() error {

View File

@ -3,6 +3,7 @@ package helpers
import (
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@ -11,17 +12,20 @@ type IPatchBuildingHelper interface {
}
type PatchBuildingHelper struct {
c *types.HelperCommon
git *commands.GitCommand
c *types.HelperCommon
git *commands.GitCommand
contexts *context.ContextTree
}
func NewPatchBuildingHelper(
c *types.HelperCommon,
git *commands.GitCommand,
contexts *context.ContextTree,
) *PatchBuildingHelper {
return &PatchBuildingHelper{
c: c,
git: git,
c: c,
git: git,
contexts: contexts,
}
}
@ -31,3 +35,28 @@ func (self *PatchBuildingHelper) ValidateNormalWorkingTreeState() (bool, error)
}
return true, nil
}
// takes us from the patch building panel back to the commit files panel
func (self *PatchBuildingHelper) Escape() error {
return self.c.PushContext(self.contexts.CommitFiles)
}
// kills the custom patch and returns us back to the commit files panel if needed
func (self *PatchBuildingHelper) Reset() error {
self.git.Patch.PatchManager.Reset()
if self.c.CurrentStaticContext().GetKind() != types.SIDE_CONTEXT {
if err := self.Escape(); err != nil {
return err
}
}
if err := self.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.COMMIT_FILES},
}); err != nil {
return err
}
// refreshing the current context so that the secondary panel is hidden if necessary.
return self.c.PostRefreshUpdate(self.c.CurrentContext())
}

View File

@ -51,22 +51,24 @@ func (self *ListController) HandleScrollRight() error {
}
func (self *ListController) HandleScrollUp() error {
self.context.GetViewTrait().ScrollUp()
scrollHeight := self.c.UserConfig.Gui.ScrollHeight
self.context.GetViewTrait().ScrollUp(scrollHeight)
// we only need to do a line change if our line has been pushed out of the viewport, because
// at the moment much logic depends on the selected line always being visible
if !self.isSelectedLineInViewPort() {
return self.handleLineChange(-1)
return self.handleLineChange(-scrollHeight)
}
return nil
}
func (self *ListController) HandleScrollDown() error {
self.context.GetViewTrait().ScrollDown()
scrollHeight := self.c.UserConfig.Gui.ScrollHeight
self.context.GetViewTrait().ScrollDown(scrollHeight)
if !self.isSelectedLineInViewPort() {
return self.handleLineChange(1)
return self.handleLineChange(scrollHeight)
}
return nil
@ -81,7 +83,7 @@ func (self *ListController) isSelectedLineInViewPort() bool {
func (self *ListController) scrollHorizontal(scrollFunc func()) error {
scrollFunc()
return self.context.HandleFocus()
return self.context.HandleFocus(types.OnFocusOpts{})
}
func (self *ListController) handleLineChange(change int) error {
@ -96,7 +98,7 @@ func (self *ListController) handleLineChange(change int) error {
// doing this check so that if we're holding the up key at the start of the list
// we're not constantly re-rendering the main view.
if before != after {
return self.context.HandleFocus()
return self.context.HandleFocus(types.OnFocusOpts{})
}
return nil
@ -136,7 +138,7 @@ func (self *ListController) HandleClick(opts gocui.ViewMouseBindingOpts) error {
if prevSelectedLineIdx == newSelectedLineIdx && alreadyFocused && self.context.GetOnClick() != nil {
return self.context.GetOnClick()()
}
return self.context.HandleFocus()
return self.context.HandleFocus(types.OnFocusOpts{})
}
func (self *ListController) pushContextIfNotFocused() error {
@ -182,22 +184,19 @@ func (self *ListController) GetKeybindings(opts types.KeybindingsOpts) []*types.
func (self *ListController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{
{
ViewName: self.context.GetViewName(),
ToContext: string(self.context.GetKey()),
Key: gocui.MouseWheelUp,
Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollUp() },
ViewName: self.context.GetViewName(),
Key: gocui.MouseWheelUp,
Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollUp() },
},
{
ViewName: self.context.GetViewName(),
ToContext: string(self.context.GetKey()),
Key: gocui.MouseLeft,
Handler: func(opts gocui.ViewMouseBindingOpts) error { return self.HandleClick(opts) },
ViewName: self.context.GetViewName(),
Key: gocui.MouseLeft,
Handler: func(opts gocui.ViewMouseBindingOpts) error { return self.HandleClick(opts) },
},
{
ViewName: self.context.GetViewName(),
ToContext: string(self.context.GetKey()),
Key: gocui.MouseWheelDown,
Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollDown() },
ViewName: self.context.GetViewName(),
Key: gocui.MouseWheelDown,
Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollDown() },
},
}
}

View File

@ -0,0 +1,138 @@
package controllers
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
type PatchBuildingController struct {
baseController
*controllerCommon
}
var _ types.IController = &PatchBuildingController{}
func NewPatchBuildingController(
common *controllerCommon,
) *PatchBuildingController {
return &PatchBuildingController{
baseController: baseController{},
controllerCommon: common,
}
}
func (self *PatchBuildingController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
return []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.OpenFile,
Description: self.c.Tr.LcOpenFile,
},
{
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.EditFile,
Description: self.c.Tr.LcEditFile,
},
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.ToggleSelectionAndRefresh,
Description: self.c.Tr.ToggleSelectionForPatch,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.Escape,
Description: self.c.Tr.ExitCustomPatchBuilder,
},
}
}
func (self *PatchBuildingController) Context() types.Context {
return self.contexts.CustomPatchBuilder
}
func (self *PatchBuildingController) context() types.IPatchExplorerContext {
return self.contexts.CustomPatchBuilder
}
func (self *PatchBuildingController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{}
}
func (self *PatchBuildingController) OpenFile() error {
self.context().GetMutex().Lock()
defer self.context().GetMutex().Unlock()
path := self.contexts.CommitFiles.GetSelectedPath()
if path == "" {
return nil
}
lineNumber := self.context().GetState().CurrentLineNumber()
return self.helpers.Files.OpenFileAtLine(path, lineNumber)
}
func (self *PatchBuildingController) EditFile() error {
self.context().GetMutex().Lock()
defer self.context().GetMutex().Unlock()
path := self.contexts.CommitFiles.GetSelectedPath()
if path == "" {
return nil
}
lineNumber := self.context().GetState().CurrentLineNumber()
return self.helpers.Files.EditFileAtLine(path, lineNumber)
}
func (self *PatchBuildingController) ToggleSelectionAndRefresh() error {
if err := self.toggleSelection(); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.PATCH_BUILDING, types.COMMIT_FILES},
})
}
func (self *PatchBuildingController) toggleSelection() error {
self.context().GetMutex().Lock()
defer self.context().GetMutex().Unlock()
toggleFunc := self.git.Patch.PatchManager.AddFileLineRange
filename := self.contexts.CommitFiles.GetSelectedPath()
if filename == "" {
return nil
}
state := self.context().GetState()
includedLineIndices, err := self.git.Patch.PatchManager.GetFileIncLineIndices(filename)
if err != nil {
return err
}
currentLineIsStaged := lo.Contains(includedLineIndices, state.GetSelectedLineIdx())
if currentLineIsStaged {
toggleFunc = self.git.Patch.PatchManager.RemoveFileLineRange
}
// add range of lines to those set for the file
firstLineIdx, lastLineIdx := state.SelectedRange()
if err := toggleFunc(filename, firstLineIdx, lastLineIdx); err != nil {
// might actually want to return an error here
self.c.Log.Error(err)
}
if state.SelectingRange() {
state.SetLineSelectMode()
}
return nil
}
func (self *PatchBuildingController) Escape() error {
return self.helpers.PatchBuilding.Escape()
}

View File

@ -0,0 +1,289 @@
package controllers
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type PatchExplorerControllerFactory struct {
*controllerCommon
}
func NewPatchExplorerControllerFactory(c *controllerCommon) *PatchExplorerControllerFactory {
return &PatchExplorerControllerFactory{
controllerCommon: c,
}
}
func (self *PatchExplorerControllerFactory) Create(context types.IPatchExplorerContext) *PatchExplorerController {
return &PatchExplorerController{
baseController: baseController{},
controllerCommon: self.controllerCommon,
context: context,
}
}
type PatchExplorerController struct {
baseController
*controllerCommon
context types.IPatchExplorerContext
}
func (self *PatchExplorerController) Context() types.Context {
return self.context
}
func (self *PatchExplorerController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
return []*types.Binding{
{
Tag: "navigation",
Key: opts.GetKey(opts.Config.Universal.PrevItemAlt),
Handler: self.withRenderAndFocus(self.HandlePrevLine),
},
{
Tag: "navigation",
Key: opts.GetKey(opts.Config.Universal.PrevItem),
Handler: self.withRenderAndFocus(self.HandlePrevLine),
},
{
Tag: "navigation",
Key: opts.GetKey(opts.Config.Universal.NextItemAlt),
Handler: self.withRenderAndFocus(self.HandleNextLine),
},
{
Tag: "navigation",
Key: opts.GetKey(opts.Config.Universal.NextItem),
Handler: self.withRenderAndFocus(self.HandleNextLine),
},
{
Key: opts.GetKey(opts.Config.Universal.PrevBlock),
Handler: self.withRenderAndFocus(self.HandlePrevHunk),
Description: self.c.Tr.PrevHunk,
},
{
Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt),
Handler: self.withRenderAndFocus(self.HandlePrevHunk),
},
{
Key: opts.GetKey(opts.Config.Universal.NextBlock),
Handler: self.withRenderAndFocus(self.HandleNextHunk),
Description: self.c.Tr.NextHunk,
},
{
Key: opts.GetKey(opts.Config.Universal.NextBlockAlt),
Handler: self.withRenderAndFocus(self.HandleNextHunk),
},
{
Key: opts.GetKey(opts.Config.Main.ToggleDragSelect),
Handler: self.withRenderAndFocus(self.HandleToggleSelectRange),
Description: self.c.Tr.ToggleDragSelect,
},
{
Key: opts.GetKey(opts.Config.Main.ToggleDragSelectAlt),
Handler: self.withRenderAndFocus(self.HandleToggleSelectRange),
Description: self.c.Tr.ToggleDragSelect,
},
{
Key: opts.GetKey(opts.Config.Main.ToggleSelectHunk),
Handler: self.withRenderAndFocus(self.HandleToggleSelectHunk),
Description: self.c.Tr.ToggleSelectHunk,
},
{
Tag: "navigation",
Key: opts.GetKey(opts.Config.Universal.PrevPage),
Handler: self.withRenderAndFocus(self.HandlePrevPage),
Description: self.c.Tr.LcPrevPage,
},
{
Tag: "navigation",
Key: opts.GetKey(opts.Config.Universal.NextPage),
Handler: self.withRenderAndFocus(self.HandleNextPage),
Description: self.c.Tr.LcNextPage,
},
{
Tag: "navigation",
Key: opts.GetKey(opts.Config.Universal.GotoTop),
Handler: self.withRenderAndFocus(self.HandleGotoTop),
Description: self.c.Tr.LcGotoTop,
},
{
Tag: "navigation",
Key: opts.GetKey(opts.Config.Universal.GotoBottom),
Description: self.c.Tr.LcGotoBottom,
Handler: self.withRenderAndFocus(self.HandleGotoBottom),
},
{
Tag: "navigation",
Key: opts.GetKey(opts.Config.Universal.ScrollLeft),
Handler: self.withRenderAndFocus(self.HandleScrollLeft),
},
{
Tag: "navigation",
Key: opts.GetKey(opts.Config.Universal.ScrollRight),
Handler: self.withRenderAndFocus(self.HandleScrollRight),
},
{
Tag: "navigation",
Key: opts.GetKey(opts.Config.Universal.StartSearch),
Handler: func() error { self.c.OpenSearch(); return nil },
Description: self.c.Tr.LcStartSearch,
},
{
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.withLock(self.CopySelectedToClipboard),
Description: self.c.Tr.LcCopySelectedTexToClipboard,
},
}
}
func (self *PatchExplorerController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{
{
ViewName: self.context.GetViewName(),
Key: gocui.MouseLeft,
Handler: func(opts gocui.ViewMouseBindingOpts) error {
if self.isFocused() {
return self.withRenderAndFocus(self.HandleMouseDown)()
}
return self.c.PushContext(self.context, types.OnFocusOpts{
ClickedWindowName: self.context.GetWindowName(),
ClickedViewLineIdx: opts.Y,
})
},
},
{
ViewName: self.context.GetViewName(),
Key: gocui.MouseLeft,
Modifier: gocui.ModMotion,
Handler: func(gocui.ViewMouseBindingOpts) error {
return self.withRenderAndFocus(self.HandleMouseDrag)()
},
},
}
}
func (self *PatchExplorerController) HandlePrevLine() error {
self.context.GetState().CycleSelection(false)
return nil
}
func (self *PatchExplorerController) HandleNextLine() error {
self.context.GetState().CycleSelection(true)
return nil
}
func (self *PatchExplorerController) HandlePrevHunk() error {
self.context.GetState().CycleHunk(false)
return nil
}
func (self *PatchExplorerController) HandleNextHunk() error {
self.context.GetState().CycleHunk(true)
return nil
}
func (self *PatchExplorerController) HandleToggleSelectRange() error {
self.context.GetState().ToggleSelectRange()
return nil
}
func (self *PatchExplorerController) HandleToggleSelectHunk() error {
self.context.GetState().ToggleSelectHunk()
return nil
}
func (self *PatchExplorerController) HandleScrollLeft() error {
self.context.GetViewTrait().ScrollLeft()
return nil
}
func (self *PatchExplorerController) HandleScrollRight() error {
self.context.GetViewTrait().ScrollRight()
return nil
}
func (self *PatchExplorerController) HandlePrevPage() error {
self.context.GetState().SetLineSelectMode()
self.context.GetState().AdjustSelectedLineIdx(-self.context.GetViewTrait().PageDelta())
return nil
}
func (self *PatchExplorerController) HandleNextPage() error {
self.context.GetState().SetLineSelectMode()
self.context.GetState().AdjustSelectedLineIdx(self.context.GetViewTrait().PageDelta())
return nil
}
func (self *PatchExplorerController) HandleGotoTop() error {
self.context.GetState().SelectTop()
return nil
}
func (self *PatchExplorerController) HandleGotoBottom() error {
self.context.GetState().SelectBottom()
return nil
}
func (self *PatchExplorerController) HandleMouseDown() error {
self.context.GetState().SelectNewLineForRange(self.context.GetViewTrait().SelectedLineIdx())
return nil
}
func (self *PatchExplorerController) HandleMouseDrag() error {
self.context.GetState().SelectLine(self.context.GetViewTrait().SelectedLineIdx())
return nil
}
func (self *PatchExplorerController) CopySelectedToClipboard() error {
selected := self.context.GetState().PlainRenderSelected()
self.c.LogAction(self.c.Tr.Actions.CopySelectedTextToClipboard)
if err := self.os.CopyToClipboard(selected); err != nil {
return self.c.Error(err)
}
return nil
}
func (self *PatchExplorerController) isFocused() bool {
return self.c.CurrentContext().GetKey() == self.context.GetKey()
}
func (self *PatchExplorerController) 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 *PatchExplorerController) 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()
}
}

View File

@ -0,0 +1,70 @@
package controllers
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
// given we have no fields here, arguably we shouldn't even need this factory
// struct, but we're maintaining consistency with the other files.
type VerticalScrollControllerFactory struct {
controllerCommon *controllerCommon
}
func NewVerticalScrollControllerFactory(c *controllerCommon) *VerticalScrollControllerFactory {
return &VerticalScrollControllerFactory{controllerCommon: c}
}
func (self *VerticalScrollControllerFactory) Create(context types.Context) types.IController {
return &VerticalScrollController{
baseController: baseController{},
controllerCommon: self.controllerCommon,
context: context,
}
}
type VerticalScrollController struct {
baseController
*controllerCommon
context types.Context
}
func (self *VerticalScrollController) Context() types.Context {
return self.context
}
func (self *VerticalScrollController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
return []*types.Binding{}
}
func (self *VerticalScrollController) 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 *VerticalScrollController) HandleScrollUp() error {
self.context.GetViewTrait().ScrollUp(self.c.UserConfig.Gui.ScrollHeight)
return nil
}
func (self *VerticalScrollController) HandleScrollDown() error {
self.context.GetViewTrait().ScrollDown(self.c.UserConfig.Gui.ScrollHeight)
return nil
}

View File

@ -0,0 +1,242 @@
package controllers
import (
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type StagingController struct {
baseController
*controllerCommon
context types.IPatchExplorerContext
otherContext types.IPatchExplorerContext
// if true, we're dealing with the secondary context i.e. dealing with staged file changes
staged bool
}
var _ types.IController = &StagingController{}
func NewStagingController(
common *controllerCommon,
context types.IPatchExplorerContext,
otherContext types.IPatchExplorerContext,
staged bool,
) *StagingController {
return &StagingController{
baseController: baseController{},
controllerCommon: common,
context: context,
otherContext: otherContext,
staged: staged,
}
}
func (self *StagingController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
return []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.OpenFile,
Description: self.c.Tr.LcOpenFile,
},
{
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.EditFile,
Description: self.c.Tr.LcEditFile,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.Escape,
Description: self.c.Tr.ReturnToFilesPanel,
},
{
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
Handler: self.TogglePanel,
Description: self.c.Tr.ToggleStagingPanel,
},
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.ToggleStaged,
Description: self.c.Tr.StageSelection,
},
{
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.ResetSelection,
Description: self.c.Tr.ResetSelection,
},
{
Key: opts.GetKey(opts.Config.Main.EditSelectHunk),
Handler: self.EditHunkAndRefresh,
Description: self.c.Tr.EditHunk,
},
}
}
func (self *StagingController) Context() types.Context {
return self.context
}
func (self *StagingController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{}
}
func (self *StagingController) OpenFile() error {
self.context.GetMutex().Lock()
defer self.context.GetMutex().Unlock()
path := self.FilePath()
if path == "" {
return nil
}
lineNumber := self.context.GetState().CurrentLineNumber()
return self.helpers.Files.OpenFileAtLine(path, lineNumber)
}
func (self *StagingController) EditFile() error {
self.context.GetMutex().Lock()
defer self.context.GetMutex().Unlock()
path := self.FilePath()
if path == "" {
return nil
}
lineNumber := self.context.GetState().CurrentLineNumber()
return self.helpers.Files.EditFileAtLine(path, lineNumber)
}
func (self *StagingController) Escape() error {
return self.c.PushContext(self.contexts.Files)
}
func (self *StagingController) TogglePanel() error {
if self.otherContext.GetState() != nil {
return self.c.PushContext(self.otherContext)
}
return nil
}
func (self *StagingController) ToggleStaged() error {
return self.applySelectionAndRefresh(self.staged)
}
func (self *StagingController) ResetSelection() error {
reset := func() error { return self.applySelectionAndRefresh(true) }
if !self.staged && !self.c.UserConfig.Gui.SkipUnstageLineWarning {
return self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.UnstageLinesTitle,
Prompt: self.c.Tr.UnstageLinesPrompt,
HandleConfirm: reset,
})
}
return reset()
}
func (self *StagingController) applySelectionAndRefresh(reverse bool) error {
if err := self.applySelection(reverse); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.STAGING}})
}
func (self *StagingController) applySelection(reverse bool) error {
self.context.GetMutex().Lock()
defer self.context.GetMutex().Unlock()
state := self.context.GetState()
path := self.FilePath()
if path == "" {
return nil
}
firstLineIdx, lastLineIdx := state.SelectedRange()
patch := patch.ModifiedPatchForRange(self.c.Log, path, state.GetDiff(), firstLineIdx, lastLineIdx, reverse, false)
if patch == "" {
return nil
}
// apply the patch then refresh this panel
// create a new temp file with the patch, then call git apply with that patch
applyFlags := []string{}
if !reverse || self.staged {
applyFlags = append(applyFlags, "cached")
}
self.c.LogAction(self.c.Tr.Actions.ApplyPatch)
err := self.git.WorkingTree.ApplyPatch(patch, applyFlags...)
if err != nil {
return self.c.Error(err)
}
if state.SelectingRange() {
state.SetLineSelectMode()
}
return nil
}
func (self *StagingController) EditHunkAndRefresh() error {
if err := self.editHunk(); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.STAGING}})
}
func (self *StagingController) editHunk() error {
self.context.GetMutex().Lock()
defer self.context.GetMutex().Unlock()
state := self.context.GetState()
path := self.FilePath()
if path == "" {
return nil
}
hunk := state.CurrentHunk()
patchText := patch.ModifiedPatchForRange(
self.c.Log, path, state.GetDiff(), hunk.FirstLineIdx, hunk.LastLineIdx(), self.staged, false,
)
patchFilepath, err := self.git.WorkingTree.SaveTemporaryPatch(patchText)
if err != nil {
return err
}
lineOffset := 3
lineIdxInHunk := state.GetSelectedLineIdx() - hunk.FirstLineIdx
if err := self.helpers.Files.EditFileAtLine(patchFilepath, lineIdxInHunk+lineOffset); err != nil {
return err
}
editedPatchText, err := self.git.File.Cat(patchFilepath)
if err != nil {
return err
}
self.c.LogAction(self.c.Tr.Actions.ApplyPatch)
lineCount := strings.Count(editedPatchText, "\n") + 1
newPatchText := patch.ModifiedPatchForRange(
self.c.Log, path, editedPatchText, 0, lineCount, false, false,
)
if err := self.git.WorkingTree.ApplyPatch(newPatchText, "cached"); err != nil {
return self.c.Error(err)
}
return nil
}
func (self *StagingController) FilePath() string {
return self.contexts.Files.GetSelectedPath()
}

View File

@ -4,7 +4,6 @@ import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@ -16,7 +15,7 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error {
menuItems := []*types.MenuItem{
{
Label: "reset patch",
OnPress: gui.handleResetPatch,
OnPress: gui.helpers.PatchBuilding.Reset,
Key: 'c',
},
{
@ -89,9 +88,9 @@ func (gui *Gui) validateNormalWorkingTreeState() (bool, error) {
return true, nil
}
func (gui *Gui) returnFocusFromLineByLinePanelIfNecessary() error {
if gui.State.MainContext == context.MAIN_PATCH_BUILDING_CONTEXT_KEY {
return gui.handleEscapePatchBuildingPanel()
func (gui *Gui) returnFocusFromPatchExplorerIfNecessary() error {
if gui.currentContext().GetKey() == gui.State.Contexts.CustomPatchBuilder.GetKey() {
return gui.helpers.PatchBuilding.Escape()
}
return nil
}
@ -101,7 +100,7 @@ func (gui *Gui) handleDeletePatchFromCommit() error {
return err
}
if err := gui.returnFocusFromLineByLinePanelIfNecessary(); err != nil {
if err := gui.returnFocusFromPatchExplorerIfNecessary(); err != nil {
return err
}
@ -118,7 +117,7 @@ func (gui *Gui) handleMovePatchToSelectedCommit() error {
return err
}
if err := gui.returnFocusFromLineByLinePanelIfNecessary(); err != nil {
if err := gui.returnFocusFromPatchExplorerIfNecessary(); err != nil {
return err
}
@ -135,7 +134,7 @@ func (gui *Gui) handleMovePatchIntoWorkingTree() error {
return err
}
if err := gui.returnFocusFromLineByLinePanelIfNecessary(); err != nil {
if err := gui.returnFocusFromPatchExplorerIfNecessary(); err != nil {
return err
}
@ -166,7 +165,7 @@ func (gui *Gui) handlePullPatchIntoNewCommit() error {
return err
}
if err := gui.returnFocusFromLineByLinePanelIfNecessary(); err != nil {
if err := gui.returnFocusFromPatchExplorerIfNecessary(); err != nil {
return err
}
@ -179,7 +178,7 @@ func (gui *Gui) handlePullPatchIntoNewCommit() error {
}
func (gui *Gui) handleApplyPatch(reverse bool) error {
if err := gui.returnFocusFromLineByLinePanelIfNecessary(); err != nil {
if err := gui.returnFocusFromPatchExplorerIfNecessary(); err != nil {
return err
}
@ -193,18 +192,3 @@ func (gui *Gui) handleApplyPatch(reverse bool) error {
}
return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
}
func (gui *Gui) handleResetPatch() error {
gui.git.Patch.PatchManager.Reset()
if gui.currentContextKeyIgnoringPopups() == context.MAIN_PATCH_BUILDING_CONTEXT_KEY {
if err := gui.c.PushContext(gui.State.Contexts.CommitFiles); err != nil {
return err
}
}
if err := gui.handleEscapePatchBuildingPanel(); err != nil {
return err
}
return gui.refreshCommitFilesContext()
}

View File

@ -1,78 +0,0 @@
package gui
import (
"errors"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
var CONTEXT_KEYS_SHOWING_DIFFS = []types.ContextKey{
context.FILES_CONTEXT_KEY,
context.COMMIT_FILES_CONTEXT_KEY,
context.STASH_CONTEXT_KEY,
context.LOCAL_COMMITS_CONTEXT_KEY,
context.SUB_COMMITS_CONTEXT_KEY,
context.MAIN_STAGING_CONTEXT_KEY,
context.MAIN_PATCH_BUILDING_CONTEXT_KEY,
}
func isShowingDiff(gui *Gui) bool {
key := gui.currentStaticContext().GetKey()
for _, contextKey := range CONTEXT_KEYS_SHOWING_DIFFS {
if key == contextKey {
return true
}
}
return false
}
func (gui *Gui) IncreaseContextInDiffView() error {
if isShowingDiff(gui) {
if err := gui.CheckCanChangeContext(); err != nil {
return gui.c.Error(err)
}
gui.c.UserConfig.Git.DiffContextSize = gui.c.UserConfig.Git.DiffContextSize + 1
return gui.handleDiffContextSizeChange()
}
return nil
}
func (gui *Gui) DecreaseContextInDiffView() error {
old_size := gui.c.UserConfig.Git.DiffContextSize
if isShowingDiff(gui) && old_size > 1 {
if err := gui.CheckCanChangeContext(); err != nil {
return gui.c.Error(err)
}
gui.c.UserConfig.Git.DiffContextSize = old_size - 1
return gui.handleDiffContextSizeChange()
}
return nil
}
func (gui *Gui) handleDiffContextSizeChange() error {
currentContext := gui.currentStaticContext()
switch currentContext.GetKey() {
// we make an exception for our staging and patch building contexts because they actually need to refresh their state afterwards.
case context.MAIN_PATCH_BUILDING_CONTEXT_KEY:
return gui.handleRefreshPatchBuildingPanel(-1)
case context.MAIN_STAGING_CONTEXT_KEY:
return gui.handleRefreshStagingPanel(false, -1)
default:
return currentContext.HandleRenderToMain()
}
}
func (gui *Gui) CheckCanChangeContext() error {
if gui.git.Patch.PatchManager.Active() {
return errors.New(gui.c.Tr.CantChangeContextSizeError)
}
return nil
}

View File

@ -21,6 +21,7 @@ func (gui *Gui) renderDiff() error {
task := NewRunPtyTask(cmdObj.GetCmd())
return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{
title: "Diff",
task: task,

View File

@ -34,8 +34,9 @@ func (gui *Gui) filesRenderToMain() error {
if node == nil {
return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{
title: "",
title: gui.c.Tr.DiffTitle,
task: NewRenderStringTask(gui.c.Tr.NoChangedFiles),
},
})
@ -53,31 +54,38 @@ func (gui *Gui) filesRenderToMain() error {
gui.resetMergeStateWithLock()
mainContext := gui.State.Contexts.Normal
pair := gui.normalMainContextPair()
if node.File != nil {
mainContext = gui.State.Contexts.Staging
pair = gui.stagingMainContextPair()
}
split := gui.c.UserConfig.Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges())
mainShowsStaged := !split && node.GetHasStagedChanges()
cmdObj := gui.git.WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged, gui.IgnoreWhitespaceInDiffView)
refreshOpts := refreshMainOpts{main: &viewUpdateOpts{
title: gui.c.Tr.UnstagedChanges,
task: NewRunPtyTask(cmdObj.GetCmd()),
context: mainContext,
}}
title := gui.c.Tr.UnstagedChanges
if mainShowsStaged {
refreshOpts.main.title = gui.c.Tr.StagedChanges
title = gui.c.Tr.StagedChanges
}
refreshOpts := refreshMainOpts{
pair: pair,
main: &viewUpdateOpts{
task: NewRunPtyTask(cmdObj.GetCmd()),
title: title,
},
}
if split {
cmdObj := gui.git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.IgnoreWhitespaceInDiffView)
title := gui.c.Tr.StagedChanges
if mainShowsStaged {
title = gui.c.Tr.UnstagedChanges
}
refreshOpts.secondary = &viewUpdateOpts{
title: gui.c.Tr.StagedChanges,
task: NewRunPtyTask(cmdObj.GetCmd()),
context: mainContext,
title: title,
task: NewRunPtyTask(cmdObj.GetCmd()),
}
}
@ -89,6 +97,8 @@ func (gui *Gui) onFocusFile() error {
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
@ -98,3 +108,5 @@ func (gui *Gui) getSetTextareaTextFn(getView func() *gocui.View) func(string) {
view.RenderTextArea()
}
}
// test

View File

@ -15,8 +15,9 @@ const HORIZONTAL_SCROLL_FACTOR = 3
// these views need to be re-rendered when the screen mode changes. The commits view,
// for example, will show authorship information in half and full screen mode.
func (gui *Gui) rerenderViewsWithScreenModeDependentContent() error {
for _, view := range []*gocui.View{gui.Views.Branches, gui.Views.Commits} {
if err := gui.rerenderView(view); err != nil {
// for now we re-render all list views.
for _, context := range gui.getListContexts() {
if err := gui.rerenderView(context.GetView()); err != nil {
return err
}
}
@ -24,7 +25,6 @@ func (gui *Gui) rerenderViewsWithScreenModeDependentContent() error {
return nil
}
// TODO: GENERICS
func nextIntInCycle(sl []WindowMaximisation, current WindowMaximisation) WindowMaximisation {
for i, val := range sl {
if val == current {
@ -37,7 +37,6 @@ func nextIntInCycle(sl []WindowMaximisation, current WindowMaximisation) WindowM
return sl[0]
}
// TODO: GENERICS
func prevIntInCycle(sl []WindowMaximisation, current WindowMaximisation) WindowMaximisation {
for i, val := range sl {
if val == current {
@ -80,7 +79,14 @@ func (gui *Gui) scrollUpMain() error {
gui.State.Panels.Merging.UserVerticalScrolling = true
}
gui.scrollUpView(gui.Views.Main)
var view *gocui.View
if gui.c.CurrentContext().GetWindowName() == "secondary" {
view = gui.secondaryView()
} else {
view = gui.mainView()
}
gui.scrollUpView(view)
return nil
}
@ -90,19 +96,38 @@ func (gui *Gui) scrollDownMain() error {
gui.State.Panels.Merging.UserVerticalScrolling = true
}
gui.scrollDownView(gui.Views.Main)
var view *gocui.View
if gui.c.CurrentContext().GetWindowName() == "secondary" {
view = gui.secondaryView()
} else {
view = gui.mainView()
}
gui.scrollDownView(view)
return nil
}
func (gui *Gui) mainView() *gocui.View {
viewName := gui.getViewNameForWindow("main")
view, _ := gui.g.View(viewName)
return view
}
func (gui *Gui) secondaryView() *gocui.View {
viewName := gui.getViewNameForWindow("secondary")
view, _ := gui.g.View(viewName)
return view
}
func (gui *Gui) scrollLeftMain() error {
gui.scrollLeft(gui.Views.Main)
gui.scrollLeft(gui.mainView())
return nil
}
func (gui *Gui) scrollRightMain() error {
gui.scrollRight(gui.Views.Main)
gui.scrollRight(gui.mainView())
return nil
}
@ -117,13 +142,15 @@ func (gui *Gui) scrollRight(view *gocui.View) {
}
func (gui *Gui) scrollUpSecondary() error {
gui.scrollUpView(gui.Views.Secondary)
gui.scrollUpView(gui.secondaryView())
return nil
}
func (gui *Gui) scrollDownSecondary() error {
gui.scrollDownView(gui.Views.Secondary)
secondaryView := gui.secondaryView()
gui.scrollDownView(secondaryView)
return nil
}

View File

@ -19,7 +19,6 @@ 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/lbl"
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
@ -175,11 +174,8 @@ type GuiRepoState struct {
Ptmx *os.File
StartupStage StartupStage // Allows us to not load everything at once
MainContext types.ContextKey // used to keep the main and secondary views' contexts in sync
ContextManager ContextManager
Contexts *context.ContextTree
ViewContextMap *context.ViewContextMap
ViewTabContextMap map[string][]context.TabContext
ContextManager ContextManager
Contexts *context.ContextTree
// WindowViewNameMap is a mapping of windows to the current view of that window.
// Some views move between windows for example the commitFiles view and when cycling through
@ -200,14 +196,6 @@ type GuiRepoState struct {
CurrentPopupOpts *types.CreatePopupPanelOpts
}
// for now the staging panel state, unlike the other panel states, is going to be
// non-mutative, so that we don't accidentally end up
// with mismatches of data. We might change this in the future
type LblPanelState struct {
*lbl.State
SecondaryFocused bool // this is for if we show the left or right panel
}
type MergingPanelState struct {
*mergeconflicts.State
@ -219,8 +207,7 @@ type MergingPanelState struct {
// 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 {
LineByLine *LblPanelState
Merging *MergingPanelState
Merging *MergingPanelState
}
type searchingState struct {
@ -284,7 +271,6 @@ func (gui *Gui) resetState(startArgs types.StartArgs, reuseState bool) {
gui.State.CurrentPopupOpts = nil
gui.Mutexes.PopupMutex.Unlock()
gui.syncViewContexts()
return
}
} else {
@ -297,10 +283,7 @@ func (gui *Gui) resetState(startArgs types.StartArgs, reuseState bool) {
initialContext := initialContext(contextTree, startArgs)
initialScreenMode := initialScreenMode(startArgs)
viewContextMap := context.NewViewContextMap()
for viewName, context := range initialViewContextMapping(contextTree) {
viewContextMap.Set(viewName, context)
}
initialWindowViewNameMap := gui.initialWindowViewNameMap(contextTree)
gui.State = &GuiRepoState{
Model: &types.Model{
@ -326,16 +309,13 @@ func (gui *Gui) resetState(startArgs types.StartArgs, reuseState bool) {
CherryPicking: cherrypicking.New(),
Diffing: diffing.New(),
},
ViewContextMap: viewContextMap,
ViewTabContextMap: gui.initialViewTabContextMap(contextTree),
ScreenMode: initialScreenMode,
ScreenMode: initialScreenMode,
// TODO: put contexts in the context manager
ContextManager: NewContextManager(initialContext),
Contexts: contextTree,
ContextManager: NewContextManager(initialContext),
Contexts: contextTree,
WindowViewNameMap: initialWindowViewNameMap,
}
gui.syncViewContexts()
gui.RepoStateMap[Repo(currentDir)] = gui.State
}
@ -370,16 +350,6 @@ func initialContext(contextTree *context.ContextTree, startArgs types.StartArgs)
return initialContext
}
func (gui *Gui) syncViewContexts() {
for viewName, context := range gui.State.ViewContextMap.Entries() {
view, err := gui.g.View(viewName)
if err != nil {
panic(err)
}
view.Context = string(context.GetKey())
}
}
// for now the split view will always be on
// NewGui builds a new gui handler
func NewGui(
@ -411,9 +381,9 @@ func NewGui(
RefreshingStatusMutex: &sync.Mutex{},
SyncMutex: &sync.Mutex{},
LocalCommitsMutex: &sync.Mutex{},
LineByLinePanelMutex: &sync.Mutex{},
SubprocessMutex: &sync.Mutex{},
PopupMutex: &sync.Mutex{},
PtyMutex: &sync.Mutex{},
},
InitialDir: initialDir,
}
@ -482,40 +452,40 @@ func (gui *Gui) initGocui() (*gocui.Gui, error) {
return g, nil
}
func (gui *Gui) initialViewTabContextMap(contextTree *context.ContextTree) map[string][]context.TabContext {
return map[string][]context.TabContext{
func (gui *Gui) viewTabMap() map[string][]context.TabView {
return map[string][]context.TabView{
"branches": {
{
Tab: gui.c.Tr.LocalBranchesTitle,
Context: contextTree.Branches,
Tab: gui.c.Tr.LocalBranchesTitle,
ViewName: "localBranches",
},
{
Tab: gui.c.Tr.RemotesTitle,
Context: contextTree.Remotes,
Tab: gui.c.Tr.RemotesTitle,
ViewName: "remotes",
},
{
Tab: gui.c.Tr.TagsTitle,
Context: contextTree.Tags,
Tab: gui.c.Tr.TagsTitle,
ViewName: "tags",
},
},
"commits": {
{
Tab: gui.c.Tr.CommitsTitle,
Context: contextTree.LocalCommits,
Tab: gui.c.Tr.CommitsTitle,
ViewName: "commits",
},
{
Tab: gui.c.Tr.ReflogCommitsTitle,
Context: contextTree.ReflogCommits,
Tab: gui.c.Tr.ReflogCommitsTitle,
ViewName: "reflogCommits",
},
},
"files": {
{
Tab: gui.c.Tr.FilesTitle,
Context: contextTree.Files,
Tab: gui.c.Tr.FilesTitle,
ViewName: "files",
},
{
Tab: gui.c.Tr.SubmodulesTitle,
Context: contextTree.Submodules,
Tab: gui.c.Tr.SubmodulesTitle,
ViewName: "submodules",
},
},
}

View File

@ -1,6 +1,8 @@
package gui
import (
"errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/types"
@ -39,7 +41,17 @@ func (self *guiCommon) RunSubprocess(cmdObj oscommands.ICmdObj) (bool, error) {
}
func (self *guiCommon) PushContext(context types.Context, opts ...types.OnFocusOpts) error {
return self.gui.pushContext(context, opts...)
singleOpts := types.OnFocusOpts{}
if len(opts) > 0 {
// using triple dot but you should only ever pass one of these opt structs
if len(opts) > 1 {
return errors.New("cannot pass multiple opts to pushContext")
}
singleOpts = opts[0]
}
return self.gui.pushContext(context, singleOpts)
}
func (self *guiCommon) PopContext() error {
@ -50,6 +62,14 @@ func (self *guiCommon) CurrentContext() types.Context {
return self.gui.currentContext()
}
func (self *guiCommon) CurrentStaticContext() types.Context {
return self.gui.currentStaticContext()
}
func (self *guiCommon) IsCurrentContext(c types.Context) bool {
return self.CurrentContext().GetKey() == c.GetKey()
}
func (self *guiCommon) GetAppState() *config.AppState {
return self.gui.Config.GetAppState()
}

View File

@ -7,7 +7,6 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/constants"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
@ -46,6 +45,11 @@ func (gui *Gui) noPopupPanel(f func() error) func() error {
// only to be called from the cheatsheet generate script. This mutates the Gui struct.
func (self *Gui) GetCheatsheetKeybindings() []*types.Binding {
self.g = &gocui.Gui{}
if err := self.createAllViews(); err != nil {
panic(err)
}
// need to instantiate views
self.helpers = helpers.NewStubHelpers()
self.State = &GuiRepoState{}
self.State.Contexts = self.contextTree()
@ -173,7 +177,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
},
{
ViewName: "status",
Contexts: []string{string(context.STATUS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.handleEditConfig,
Description: self.c.Tr.EditConfig,
@ -192,70 +195,60 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
},
{
ViewName: "status",
Contexts: []string{string(context.STATUS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.handleOpenConfig,
Description: self.c.Tr.OpenConfig,
},
{
ViewName: "status",
Contexts: []string{string(context.STATUS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Status.CheckForUpdate),
Handler: self.handleCheckForUpdate,
Description: self.c.Tr.LcCheckForUpdate,
},
{
ViewName: "status",
Contexts: []string{string(context.STATUS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Status.RecentRepos),
Handler: self.handleCreateRecentReposMenu,
Description: self.c.Tr.SwitchRepo,
},
{
ViewName: "status",
Contexts: []string{string(context.STATUS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Status.AllBranchesLogGraph),
Handler: self.handleShowAllBranchLogs,
Description: self.c.Tr.LcAllBranchesLogGraph,
},
{
ViewName: "files",
Contexts: []string{string(context.FILES_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.LcCopyFileNameToClipboard,
},
{
ViewName: "branches",
Contexts: []string{string(context.LOCAL_BRANCHES_CONTEXT_KEY)},
ViewName: "localBranches",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.LcCopyBranchNameToClipboard,
},
{
ViewName: "commits",
Contexts: []string{string(context.LOCAL_COMMITS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.LcCopyCommitShaToClipboard,
},
{
ViewName: "commits",
Contexts: []string{string(context.LOCAL_COMMITS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Commits.ResetCherryPick),
Handler: self.helpers.CherryPick.Reset,
Description: self.c.Tr.LcResetCherryPick,
},
{
ViewName: "commits",
Contexts: []string{string(context.REFLOG_COMMITS_CONTEXT_KEY)},
ViewName: "reflogCommits",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.LcCopyCommitShaToClipboard,
},
{
ViewName: "subCommits",
Contexts: []string{string(context.SUB_COMMITS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.LcCopyCommitShaToClipboard,
@ -268,7 +261,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
},
{
ViewName: "commitFiles",
Contexts: []string{string(context.COMMIT_FILES_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.LcCopyCommitFileNameToClipboard,
@ -315,7 +307,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_NORMAL_CONTEXT_KEY)},
Key: gocui.MouseWheelDown,
Handler: self.scrollDownMain,
Description: self.c.Tr.ScrollDown,
@ -323,7 +314,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_NORMAL_CONTEXT_KEY)},
Key: gocui.MouseWheelUp,
Handler: self.scrollUpMain,
Description: self.c.Tr.ScrollUp,
@ -331,370 +321,110 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
},
{
ViewName: "secondary",
Contexts: []string{string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: gocui.MouseLeft,
Modifier: gocui.ModNone,
Handler: self.handleTogglePanelClick,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.handleStagingEscape,
Description: self.c.Tr.ReturnToFilesPanel,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.handleToggleStagedSelection,
Description: self.c.Tr.StageSelection,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.Remove),
Handler: self.handleResetSelection,
Description: self.c.Tr.ResetSelection,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
Handler: self.handleTogglePanel,
Description: self.c.Tr.TogglePanel,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.handleEscapePatchBuildingPanel,
Description: self.c.Tr.ExitLineByLineMode,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.handleOpenFileAtLine,
Description: self.c.Tr.LcOpenFile,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.PrevItem),
Handler: self.handleSelectPrevLine,
Description: self.c.Tr.PrevLine,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.NextItem),
Handler: self.handleSelectNextLine,
Description: self.c.Tr.NextLine,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.PrevItemAlt),
Modifier: gocui.ModNone,
Handler: self.handleSelectPrevLine,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.NextItemAlt),
Modifier: gocui.ModNone,
Handler: self.handleSelectNextLine,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: gocui.MouseWheelUp,
Modifier: gocui.ModNone,
Handler: self.scrollUpMain,
Handler: self.scrollUpSecondary,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: gocui.MouseWheelDown,
Modifier: gocui.ModNone,
Handler: self.scrollDownMain,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.PrevBlock),
Handler: self.handleSelectPrevHunk,
Description: self.c.Tr.PrevHunk,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt),
Modifier: gocui.ModNone,
Handler: self.handleSelectPrevHunk,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.NextBlock),
Handler: self.handleSelectNextHunk,
Description: self.c.Tr.NextHunk,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.NextBlockAlt),
Modifier: gocui.ModNone,
Handler: self.handleSelectNextHunk,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Modifier: gocui.ModNone,
Handler: self.copySelectedToClipboard,
Description: self.c.Tr.LcCopySelectedTexToClipboard,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.handleLineByLineEdit,
Description: self.c.Tr.LcEditFile,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.HandleOpenFile,
Description: self.c.Tr.LcOpenFile,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.NextPage),
Modifier: gocui.ModNone,
Handler: self.handleLineByLineNextPage,
Description: self.c.Tr.LcNextPage,
Tag: "navigation",
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.PrevPage),
Modifier: gocui.ModNone,
Handler: self.handleLineByLinePrevPage,
Description: self.c.Tr.LcPrevPage,
Tag: "navigation",
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.GotoTop),
Modifier: gocui.ModNone,
Handler: self.handleLineByLineGotoTop,
Description: self.c.Tr.LcGotoTop,
Tag: "navigation",
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.GotoBottom),
Modifier: gocui.ModNone,
Handler: self.handleLineByLineGotoBottom,
Description: self.c.Tr.LcGotoBottom,
Tag: "navigation",
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.StartSearch),
Handler: func() error { return self.handleOpenSearch("main") },
Description: self.c.Tr.LcStartSearch,
Tag: "navigation",
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.handleToggleSelectionForPatch,
Description: self.c.Tr.ToggleSelectionForPatch,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Main.ToggleDragSelect),
Handler: self.handleToggleSelectRange,
Description: self.c.Tr.ToggleDragSelect,
},
// Alias 'V' -> 'v'
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Main.ToggleDragSelectAlt),
Handler: self.handleToggleSelectRange,
Description: self.c.Tr.ToggleDragSelect,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Main.ToggleSelectHunk),
Handler: self.handleToggleSelectHunk,
Description: self.c.Tr.ToggleSelectHunk,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Main.EditSelectHunk),
Handler: self.handleEditHunk,
Description: self.c.Tr.EditHunk,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: gocui.MouseLeft,
Modifier: gocui.ModNone,
Handler: self.handleLBLMouseDown,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: gocui.MouseLeft,
Modifier: gocui.ModMotion,
Handler: self.handleMouseDrag,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: gocui.MouseWheelUp,
Modifier: gocui.ModNone,
Handler: self.scrollUpMain,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY)},
Key: gocui.MouseWheelDown,
Modifier: gocui.ModNone,
Handler: self.scrollDownMain,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY), string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Universal.ScrollLeft),
Handler: self.scrollLeftMain,
Description: self.c.Tr.LcScrollLeft,
Tag: "navigation",
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY), string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Universal.ScrollRight),
Handler: self.scrollRightMain,
Description: self.c.Tr.LcScrollRight,
Tag: "navigation",
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.handleEscapeMerge,
Description: self.c.Tr.ReturnToFilesPanel,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
Handler: self.helpers.WorkingTree.OpenMergeTool,
Description: self.c.Tr.LcOpenMergeTool,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.handlePickHunk,
Description: self.c.Tr.PickHunk,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Main.PickBothHunks),
Handler: self.handlePickAllHunks,
Description: self.c.Tr.PickAllHunks,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Universal.PrevBlock),
Handler: self.handleSelectPrevConflict,
Description: self.c.Tr.PrevConflict,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Universal.NextBlock),
Handler: self.handleSelectNextConflict,
Description: self.c.Tr.NextConflict,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Universal.PrevItem),
Handler: self.handleSelectPrevConflictHunk,
Description: self.c.Tr.SelectPrevHunk,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Universal.NextItem),
Handler: self.handleSelectNextConflictHunk,
Description: self.c.Tr.SelectNextHunk,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt),
Modifier: gocui.ModNone,
Handler: self.handleSelectPrevConflict,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Universal.NextBlockAlt),
Modifier: gocui.ModNone,
Handler: self.handleSelectNextConflict,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Universal.PrevItemAlt),
Modifier: gocui.ModNone,
Handler: self.handleSelectPrevConflictHunk,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Universal.NextItemAlt),
Modifier: gocui.ModNone,
Handler: self.handleSelectNextConflictHunk,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.handleMergeConflictEditFileAtLine,
Description: self.c.Tr.LcEditFile,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.handleMergeConflictOpenFileAtLine,
Description: self.c.Tr.LcOpenFile,
},
{
ViewName: "main",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
ViewName: "merging",
Key: opts.GetKey(opts.Config.Universal.Undo),
Handler: self.handleMergeConflictUndo,
Description: self.c.Tr.LcUndo,
@ -742,31 +472,17 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
Handler: self.scrollDownConfirmationPanel,
},
{
ViewName: "files",
Contexts: []string{string(context.SUBMODULES_CONTEXT_KEY)},
ViewName: "submodules",
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.LcCopySubmoduleNameToClipboard,
},
{
ViewName: "files",
Contexts: []string{string(context.FILES_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.ToggleWhitespaceInDiffView),
Handler: self.toggleWhitespaceInDiffView,
Description: self.c.Tr.ToggleWhitespaceInDiffView,
},
{
ViewName: "",
Key: opts.GetKey(opts.Config.Universal.IncreaseContextInDiffView),
Handler: self.IncreaseContextInDiffView,
Description: self.c.Tr.IncreaseContextInDiffView,
},
{
ViewName: "",
Key: opts.GetKey(opts.Config.Universal.DecreaseContextInDiffView),
Handler: self.DecreaseContextInDiffView,
Description: self.c.Tr.DecreaseContextInDiffView,
},
{
ViewName: "extras",
Key: gocui.MouseWheelUp,
@ -777,17 +493,9 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
Key: gocui.MouseWheelDown,
Handler: self.scrollDownExtra,
},
{
ViewName: "extras",
Key: opts.GetKey(opts.Config.Universal.ExtrasMenu),
Handler: self.handleCreateExtrasMenuPanel,
Description: self.c.Tr.LcOpenExtrasMenu,
OpensMenu: true,
},
{
ViewName: "extras",
Tag: "navigation",
Contexts: []string{string(context.COMMAND_LOG_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.PrevItemAlt),
Modifier: gocui.ModNone,
Handler: self.scrollUpExtra,
@ -795,7 +503,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
{
ViewName: "extras",
Tag: "navigation",
Contexts: []string{string(context.COMMAND_LOG_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.PrevItem),
Modifier: gocui.ModNone,
Handler: self.scrollUpExtra,
@ -803,7 +510,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
{
ViewName: "extras",
Tag: "navigation",
Contexts: []string{string(context.COMMAND_LOG_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.NextItem),
Modifier: gocui.ModNone,
Handler: self.scrollDownExtra,
@ -811,7 +517,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
{
ViewName: "extras",
Tag: "navigation",
Contexts: []string{string(context.COMMAND_LOG_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.NextItemAlt),
Modifier: gocui.ModNone,
Handler: self.scrollDownExtra,
@ -828,12 +533,8 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
mouseKeybindings := []*gocui.ViewMouseBinding{}
for _, c := range self.State.Contexts.Flatten() {
viewName := c.GetViewName()
contextKey := c.GetKey()
for _, binding := range c.GetKeybindings(opts) {
// TODO: move all mouse keybindings into the mouse keybindings approach below
if !gocui.IsMouseKey(binding.Key) && contextKey != context.GLOBAL_CONTEXT_KEY {
binding.Contexts = []string{string(contextKey)}
}
binding.ViewName = viewName
bindings = append(bindings, binding)
}
@ -841,7 +542,7 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
mouseKeybindings = append(mouseKeybindings, c.GetMouseKeybindings(opts)...)
}
for _, viewName := range []string{"status", "branches", "remoteBranches", "files", "commits", "commitFiles", "subCommits", "stash"} {
for _, viewName := range []string{"status", "remotes", "tags", "localBranches", "remoteBranches", "files", "submodules", "reflogCommits", "commits", "commitFiles", "subCommits", "stash"} {
bindings = append(bindings, []*types.Binding{
{ViewName: viewName, Key: opts.GetKey(opts.Config.Universal.PrevBlock), Modifier: gocui.ModNone, Handler: self.previousSideWindow},
{ViewName: viewName, Key: opts.GetKey(opts.Config.Universal.NextBlock), Modifier: gocui.ModNone, Handler: self.nextSideWindow},
@ -868,24 +569,22 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
}
}
for viewName := range self.initialViewTabContextMap(self.State.Contexts) {
bindings = append(bindings, []*types.Binding{
{
ViewName: viewName,
Key: opts.GetKey(opts.Config.Universal.NextTab),
Handler: self.handleNextTab,
Description: self.c.Tr.LcNextTab,
Tag: "navigation",
},
{
ViewName: viewName,
Key: opts.GetKey(opts.Config.Universal.PrevTab),
Handler: self.handlePrevTab,
Description: self.c.Tr.LcPrevTab,
Tag: "navigation",
},
}...)
}
bindings = append(bindings, []*types.Binding{
{
ViewName: "",
Key: opts.GetKey(opts.Config.Universal.NextTab),
Handler: self.handleNextTab,
Description: self.c.Tr.LcNextTab,
Tag: "navigation",
},
{
ViewName: "",
Key: opts.GetKey(opts.Config.Universal.PrevTab),
Handler: self.handlePrevTab,
Description: self.c.Tr.LcPrevTab,
Tag: "navigation",
},
}...)
return bindings, mouseKeybindings
}
@ -914,12 +613,14 @@ func (gui *Gui) resetKeybindings() error {
}
}
for viewName := range gui.initialViewTabContextMap(gui.State.Contexts) {
viewName := viewName
tabClickCallback := func(tabIndex int) error { return gui.onViewTabClick(viewName, tabIndex) }
for _, values := range gui.viewTabMap() {
for _, value := range values {
viewName := value.ViewName
tabClickCallback := func(tabIndex int) error { return gui.onViewTabClick(gui.windowForView(viewName), tabIndex) }
if err := gui.g.SetTabClickBinding(viewName, tabClickCallback); err != nil {
return err
if err := gui.g.SetTabClickBinding(viewName, tabClickCallback); err != nil {
return err
}
}
}
@ -946,7 +647,7 @@ func (gui *Gui) SetKeybinding(binding *types.Binding) error {
}
}
return gui.g.SetKeybinding(binding.ViewName, binding.Contexts, binding.Key, binding.Modifier, gui.wrappedHandler(handler))
return gui.g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, gui.wrappedHandler(handler))
}
// warning: mutates the binding

View File

@ -1,7 +1,9 @@
package gui
import (
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/theme"
)
@ -41,26 +43,30 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
}
setViewFromDimensions := func(viewName string, windowName string, frame bool) (*gocui.View, error) {
// we assume that the view has already been created.
setViewFromDimensions := func(viewName string, windowName string) (*gocui.View, error) {
dimensionsObj, ok := viewDimensions[windowName]
view, err := g.View(viewName)
if err != nil {
return nil, err
}
if !ok {
// view not specified in dimensions object: so create the view and hide it
// making the view take up the whole space in the background in case it needs
// to render content as soon as it appears, because lazyloaded content (via a pty task)
// cares about the size of the view.
view, err := g.SetView(viewName, 0, 0, width, height, 0)
if view != nil {
view.Visible = false
}
_, err := g.SetView(viewName, 0, 0, width, height, 0)
view.Visible = false
return view, err
}
frameOffset := 1
if frame {
if view.Frame {
frameOffset = 0
}
view, err := g.SetView(
_, err = g.SetView(
viewName,
dimensionsObj.X0-frameOffset,
dimensionsObj.Y0-frameOffset,
@ -68,16 +74,17 @@ func (gui *Gui) layout(g *gocui.Gui) error {
dimensionsObj.Y1+frameOffset,
0,
)
if view != nil {
view.Frame = frame
view.Visible = true
}
view.Visible = true
return view, err
}
for _, arg := range gui.controlledViews() {
_, err := setViewFromDimensions(arg.viewName, arg.windowName, arg.frame)
for _, context := range gui.State.Contexts.Flatten() {
if !context.HasControlledBounds() {
continue
}
_, err := setViewFromDimensions(context.GetViewName(), context.GetWindowName())
if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG {
return err
}
@ -124,10 +131,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
continue
}
if !gui.isContextVisible(listContext) {
continue
}
listContext.FocusLine()
view.SelBgColor = theme.GocuiSelectedLineBgColor
@ -136,7 +139,16 @@ func (gui *Gui) layout(g *gocui.Gui) error {
view.SetOnSelectItem(gui.onSelectItemWrapper(listContext.OnSearchSelect))
}
gui.Views.Main.SetOnSelectItem(gui.onSelectItemWrapper(gui.handlelineByLineNavigateTo))
for _, context := range gui.getPatchExplorerContexts() {
context := context
context.GetView().SetOnSelectItem(gui.onSelectItemWrapper(
func(selectedLineIdx int) error {
context.GetMutex().Lock()
defer context.GetMutex().Unlock()
return context.NavigateTo(gui.c.IsCurrentContext(context), selectedLineIdx)
}),
)
}
mainViewWidth, mainViewHeight := gui.Views.Main.Size()
if mainViewWidth != gui.PrevLayout.MainWidth || mainViewHeight != gui.PrevLayout.MainHeight {
@ -188,11 +200,19 @@ func (gui *Gui) onInitialViewsCreation() error {
gui.g.Mutexes.ViewsMutex.Lock()
// add tabs to views
for _, view := range gui.g.Views() {
tabs := gui.viewTabNames(view.Name())
if len(tabs) == 0 {
continue
// if the view is in our mapping, we'll set the tabs and the tab index
for _, values := range gui.viewTabMap() {
index := slices.IndexFunc(values, func(tabContext context.TabView) bool {
return tabContext.ViewName == view.Name()
})
if index != -1 {
view.Tabs = slices.Map(values, func(tabContext context.TabView) string {
return tabContext.Tab
})
view.TabIndex = index
}
}
view.Tabs = tabs
}
gui.g.Mutexes.ViewsMutex.Unlock()

View File

@ -1,275 +0,0 @@
package gui
import (
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/gui/lbl"
)
// Currently there are two 'pseudo-panels' that make use of this 'pseudo-panel'.
// One is the staging panel where we stage files line-by-line, the other is the
// patch building panel where we add lines of an old commit's file to a patch.
// This file contains the logic around selecting lines and displaying the diffs
// staging_panel.go and patch_building_panel.go have functions specific to their
// use cases
// returns whether the patch is empty so caller can escape if necessary
// both diffs should be non-coloured because we'll parse them and colour them here
func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, secondaryFocused bool, selectedLineIdx int) (bool, error) {
gui.splitMainPanel(true)
var oldState *lbl.State
if gui.State.Panels.LineByLine != nil {
oldState = gui.State.Panels.LineByLine.State
}
state := lbl.NewState(diff, selectedLineIdx, oldState, gui.Log)
if state == nil {
return true, nil
}
gui.State.Panels.LineByLine = &LblPanelState{
State: state,
SecondaryFocused: secondaryFocused,
}
if err := gui.refreshMainViewForLineByLine(gui.State.Panels.LineByLine); err != nil {
return false, err
}
if err := gui.focusSelection(gui.State.Panels.LineByLine); err != nil {
return false, err
}
gui.Views.Secondary.Highlight = true
gui.Views.Secondary.Wrap = false
secondaryPatchParser := patch.NewPatchParser(gui.Log, secondaryDiff)
gui.setViewContent(gui.Views.Secondary, secondaryPatchParser.Render(-1, -1, nil))
return false, nil
}
func (gui *Gui) handleSelectPrevLine() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
state.CycleSelection(false)
return gui.refreshAndFocusLblPanel(state)
})
}
func (gui *Gui) handleSelectNextLine() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
state.CycleSelection(true)
return gui.refreshAndFocusLblPanel(state)
})
}
func (gui *Gui) handleSelectPrevHunk() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
state.CycleHunk(false)
return gui.refreshAndFocusLblPanel(state)
})
}
func (gui *Gui) handleSelectNextHunk() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
state.CycleHunk(true)
return gui.refreshAndFocusLblPanel(state)
})
}
func (gui *Gui) copySelectedToClipboard() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
selected := state.PlainRenderSelected()
gui.c.LogAction(gui.c.Tr.Actions.CopySelectedTextToClipboard)
if err := gui.os.CopyToClipboard(selected); err != nil {
return gui.c.Error(err)
}
return nil
})
}
func (gui *Gui) refreshAndFocusLblPanel(state *LblPanelState) error {
if err := gui.refreshMainViewForLineByLine(state); err != nil {
return err
}
return gui.focusSelection(state)
}
func (gui *Gui) handleLBLMouseDown() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
state.SelectNewLineForRange(gui.Views.Main.SelectedLineIdx())
return gui.refreshAndFocusLblPanel(state)
})
}
func (gui *Gui) handleMouseDrag() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
state.SelectLine(gui.Views.Main.SelectedLineIdx())
return gui.refreshAndFocusLblPanel(state)
})
}
func (gui *Gui) refreshMainViewForLineByLine(state *LblPanelState) error {
var includedLineIndices []int
// I'd prefer not to have knowledge of contexts using this file but I'm not sure
// how to get around this
if gui.currentContext().GetKey() == gui.State.Contexts.PatchBuilding.GetKey() {
filename := gui.getSelectedCommitFileName()
var err error
includedLineIndices, err = gui.git.Patch.PatchManager.GetFileIncLineIndices(filename)
if err != nil {
return err
}
}
colorDiff := state.RenderForLineIndices(includedLineIndices)
gui.Views.Main.Highlight = true
gui.Views.Main.Wrap = false
gui.setViewContent(gui.Views.Main, colorDiff)
return nil
}
// focusSelection works out the best focus for the staging panel given the
// selected line and size of the hunk
func (gui *Gui) focusSelection(state *LblPanelState) error {
stagingView := gui.Views.Main
_, viewHeight := stagingView.Size()
bufferHeight := viewHeight - 1
_, origin := stagingView.Origin()
selectedLineIdx := state.GetSelectedLineIdx()
newOrigin := state.CalculateOrigin(origin, bufferHeight)
if err := stagingView.SetOriginY(newOrigin); err != nil {
return err
}
return stagingView.SetCursor(0, selectedLineIdx-newOrigin)
}
func (gui *Gui) handleToggleSelectRange() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
state.ToggleSelectRange()
return gui.refreshMainViewForLineByLine(state)
})
}
func (gui *Gui) handleToggleSelectHunk() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
state.ToggleSelectHunk()
return gui.refreshAndFocusLblPanel(state)
})
}
func (gui *Gui) escapeLineByLinePanel() {
gui.State.Panels.LineByLine = nil
}
func (gui *Gui) handleOpenFileAtLine() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
// again, would be good to use inheritance here (or maybe even composition)
var filename string
switch gui.State.MainContext {
case gui.State.Contexts.PatchBuilding.GetKey():
filename = gui.getSelectedCommitFileName()
case gui.State.Contexts.Staging.GetKey():
file := gui.getSelectedFile()
if file == nil {
return nil
}
filename = file.Name
default:
return errors.Errorf("unknown main context: %s", gui.State.MainContext)
}
// need to look at current index, then work out what my hunk's header information is, and see how far my line is away from the hunk header
lineNumber := state.CurrentLineNumber()
if err := gui.os.OpenFileAtLine(filename, lineNumber); err != nil {
return err
}
return nil
})
}
func (gui *Gui) handleLineByLineNextPage() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
state.SetLineSelectMode()
state.AdjustSelectedLineIdx(gui.pageDelta(gui.Views.Main))
return gui.refreshAndFocusLblPanel(state)
})
}
func (gui *Gui) handleLineByLinePrevPage() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
state.SetLineSelectMode()
state.AdjustSelectedLineIdx(-gui.pageDelta(gui.Views.Main))
return gui.refreshAndFocusLblPanel(state)
})
}
func (gui *Gui) handleLineByLineGotoBottom() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
state.SelectBottom()
return gui.refreshAndFocusLblPanel(state)
})
}
func (gui *Gui) handleLineByLineGotoTop() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
state.SelectTop()
return gui.refreshAndFocusLblPanel(state)
})
}
func (gui *Gui) handlelineByLineNavigateTo(selectedLineIdx int) error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
state.SetLineSelectMode()
state.SelectLine(selectedLineIdx)
return gui.refreshAndFocusLblPanel(state)
})
}
func (gui *Gui) withLBLActiveCheck(f func(*LblPanelState) error) error {
gui.Mutexes.LineByLinePanelMutex.Lock()
defer gui.Mutexes.LineByLinePanelMutex.Unlock()
state := gui.State.Panels.LineByLine
if state == nil {
return nil
}
return f(state)
}
func (gui *Gui) handleLineByLineEdit() error {
file := gui.getSelectedFile()
if file == nil {
return nil
}
lineNumber := gui.State.Panels.LineByLine.CurrentLineNumber()
return gui.helpers.Files.EditFileAtLine(file.Name, lineNumber)
}

View File

@ -34,7 +34,7 @@ func (gui *Gui) filesListContext() *context.WorkingTreeContext {
})
},
OnFocusWrapper(gui.onFocusFile),
OnFocusWrapper(gui.withDiffModeCheck(gui.filesRenderToMain)),
gui.withDiffModeCheck(gui.filesRenderToMain),
nil,
gui.c,
)
@ -48,7 +48,7 @@ func (gui *Gui) branchesListContext() *context.BranchesContext {
return presentation.GetBranchListDisplayStrings(gui.State.Model.Branches, gui.State.ScreenMode != SCREEN_NORMAL, gui.State.Modes.Diffing.Ref, gui.Tr)
},
nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.branchesRenderToMain)),
gui.withDiffModeCheck(gui.branchesRenderToMain),
nil,
gui.c,
)
@ -57,12 +57,12 @@ func (gui *Gui) branchesListContext() *context.BranchesContext {
func (gui *Gui) remotesListContext() *context.RemotesContext {
return context.NewRemotesContext(
func() []*models.Remote { return gui.State.Model.Remotes },
gui.Views.Branches,
gui.Views.Remotes,
func(startIdx int, length int) [][]string {
return presentation.GetRemoteListDisplayStrings(gui.State.Model.Remotes, gui.State.Modes.Diffing.Ref)
},
nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.remotesRenderToMain)),
gui.withDiffModeCheck(gui.remotesRenderToMain),
nil,
gui.c,
)
@ -76,7 +76,7 @@ func (gui *Gui) remoteBranchesListContext() *context.RemoteBranchesContext {
return presentation.GetRemoteBranchListDisplayStrings(gui.State.Model.RemoteBranches, gui.State.Modes.Diffing.Ref)
},
nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.remoteBranchesRenderToMain)),
gui.withDiffModeCheck(gui.remoteBranchesRenderToMain),
nil,
gui.c,
)
@ -95,12 +95,12 @@ func (gui *Gui) withDiffModeCheck(f func() error) func() error {
func (gui *Gui) tagsListContext() *context.TagsContext {
return context.NewTagsContext(
func() []*models.Tag { return gui.State.Model.Tags },
gui.Views.Branches,
gui.Views.Tags,
func(startIdx int, length int) [][]string {
return presentation.GetTagListDisplayStrings(gui.State.Model.Tags, gui.State.Modes.Diffing.Ref)
},
nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.tagsRenderToMain)),
gui.withDiffModeCheck(gui.tagsRenderToMain),
nil,
gui.c,
)
@ -133,7 +133,7 @@ func (gui *Gui) branchCommitsListContext() *context.LocalCommitsContext {
)
},
OnFocusWrapper(gui.onCommitFocus),
OnFocusWrapper(gui.withDiffModeCheck(gui.branchCommitsRenderToMain)),
gui.withDiffModeCheck(gui.branchCommitsRenderToMain),
nil,
gui.c,
)
@ -166,7 +166,7 @@ func (gui *Gui) subCommitsListContext() *context.SubCommitsContext {
)
},
nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.subCommitsRenderToMain)),
gui.withDiffModeCheck(gui.subCommitsRenderToMain),
nil,
gui.c,
)
@ -194,7 +194,7 @@ func (gui *Gui) shouldShowGraph() bool {
func (gui *Gui) reflogCommitsListContext() *context.ReflogCommitsContext {
return context.NewReflogCommitsContext(
func() []*models.Commit { return gui.State.Model.FilteredReflogCommits },
gui.Views.Commits,
gui.Views.ReflogCommits,
func(startIdx int, length int) [][]string {
return presentation.GetReflogCommitListDisplayStrings(
gui.State.Model.FilteredReflogCommits,
@ -206,7 +206,7 @@ func (gui *Gui) reflogCommitsListContext() *context.ReflogCommitsContext {
)
},
nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.reflogCommitsRenderToMain)),
gui.withDiffModeCheck(gui.reflogCommitsRenderToMain),
nil,
gui.c,
)
@ -220,7 +220,7 @@ func (gui *Gui) stashListContext() *context.StashContext {
return presentation.GetStashEntryListDisplayStrings(gui.State.Model.StashEntries, gui.State.Modes.Diffing.Ref)
},
nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.stashRenderToMain)),
gui.withDiffModeCheck(gui.stashRenderToMain),
nil,
gui.c,
)
@ -240,8 +240,8 @@ func (gui *Gui) commitFilesListContext() *context.CommitFilesContext {
return []string{line}
})
},
OnFocusWrapper(gui.onCommitFileFocus),
OnFocusWrapper(gui.withDiffModeCheck(gui.commitFilesRenderToMain)),
nil,
gui.withDiffModeCheck(gui.commitFilesRenderToMain),
nil,
gui.c,
)
@ -250,12 +250,12 @@ func (gui *Gui) commitFilesListContext() *context.CommitFilesContext {
func (gui *Gui) submodulesListContext() *context.SubmodulesContext {
return context.NewSubmodulesContext(
func() []*models.SubmoduleConfig { return gui.State.Model.Submodules },
gui.Views.Files,
gui.Views.Submodules,
func(startIdx int, length int) [][]string {
return presentation.GetSubmoduleListDisplayStrings(gui.State.Model.Submodules)
},
nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.submodulesRenderToMain)),
gui.withDiffModeCheck(gui.submodulesRenderToMain),
nil,
gui.c,
)

View File

@ -10,18 +10,11 @@ import (
type viewUpdateOpts struct {
title string
// awkwardly calling this noWrap because of how hard Go makes it to have
// a boolean option that defaults to true
noWrap bool
highlight bool
task updateTask
context types.Context
}
type refreshMainOpts struct {
pair MainContextPair
main *viewUpdateOpts
secondary *viewUpdateOpts
}
@ -99,15 +92,21 @@ func (gui *Gui) runTaskForView(view *gocui.View, task updateTask) error {
return nil
}
func (gui *Gui) refreshMainView(opts *viewUpdateOpts, view *gocui.View) error {
view.Title = opts.title
view.Wrap = !opts.noWrap
view.Highlight = opts.highlight
context := opts.context
if context == nil {
context = gui.State.Contexts.Normal
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) refreshMainView(opts *viewUpdateOpts, context types.Context) error {
view := context.GetView()
if opts.title != "" {
view.Title = opts.title
}
gui.ViewContextMapSet(view.Name(), context)
if err := gui.runTaskForView(view, opts.task); err != nil {
gui.c.Log.Error(err)
@ -117,19 +116,75 @@ func (gui *Gui) refreshMainView(opts *viewUpdateOpts, view *gocui.View) error {
return nil
}
type MainContextPair struct {
main types.Context
secondary types.Context
}
func (gui *Gui) normalMainContextPair() MainContextPair {
return MainContextPair{
main: gui.State.Contexts.Normal,
secondary: gui.State.Contexts.NormalSecondary,
}
}
func (gui *Gui) stagingMainContextPair() MainContextPair {
return MainContextPair{
main: gui.State.Contexts.Staging,
secondary: gui.State.Contexts.StagingSecondary,
}
}
func (gui *Gui) patchBuildingMainContextPair() MainContextPair {
return MainContextPair{
main: gui.State.Contexts.CustomPatchBuilder,
secondary: gui.State.Contexts.CustomPatchBuilderSecondary,
}
}
func (gui *Gui) mergingMainContextPair() MainContextPair {
return MainContextPair{
main: gui.State.Contexts.Merging,
secondary: nil,
}
}
func (gui *Gui) allMainContextPairs() []MainContextPair {
return []MainContextPair{
gui.normalMainContextPair(),
gui.stagingMainContextPair(),
gui.patchBuildingMainContextPair(),
gui.mergingMainContextPair(),
}
}
func (gui *Gui) refreshMainViews(opts 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.secondary != nil && pair.secondary != opts.pair.secondary {
_ = pair.secondary.GetView().SetOrigin(0, 0)
}
}
if opts.main != nil {
if err := gui.refreshMainView(opts.main, gui.Views.Main); err != nil {
if err := gui.refreshMainView(opts.main, opts.pair.main); err != nil {
return err
}
}
if opts.secondary != nil {
if err := gui.refreshMainView(opts.secondary, gui.Views.Secondary); err != 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()
}
gui.moveMainContextPairToTop(opts.pair)
gui.splitMainPanel(opts.secondary != nil)
return nil

View File

@ -146,17 +146,15 @@ func (gui *Gui) renderConflicts(hasFocus bool) error {
return nil
}
gui.centerYPos(gui.Views.Main, state.GetConflictMiddle())
gui.centerYPos(gui.Views.Merging, state.GetConflictMiddle())
return nil
})
}
return gui.refreshMainViews(refreshMainOpts{
pair: gui.mergingMainContextPair(),
main: &viewUpdateOpts{
title: gui.c.Tr.MergeConflictsTitle,
task: NewRenderStringWithoutScrollTask(content),
context: gui.State.Contexts.Merging,
noWrap: true,
task: NewRenderStringWithoutScrollTask(content),
},
})
}
@ -239,14 +237,14 @@ func (gui *Gui) escapeMerge() error {
// 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.pushContext(gui.State.Contexts.Files)
return gui.c.PushContext(gui.State.Contexts.Files)
})
return nil
}
func (gui *Gui) renderingConflicts() bool {
currentView := gui.g.CurrentView()
if currentView != gui.Views.Main && currentView != gui.Views.Files {
if currentView != gui.Views.Merging && currentView != gui.Views.Files {
return false
}

View File

@ -34,7 +34,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
description: func() string {
return gui.withResetButton(gui.c.Tr.LcBuildingPatch, style.FgYellow.SetBold())
},
reset: gui.handleResetPatch,
reset: gui.helpers.PatchBuilding.Reset,
},
{
isActive: gui.State.Modes.Filtering.Active,

View File

@ -21,11 +21,11 @@ func (gui *Gui) getBindings(context types.Context) []*types.Binding {
for _, binding := range bindings {
if keybindings.GetKeyDisplay(binding.Key) != "" && binding.Description != "" {
if len(binding.Contexts) == 0 && binding.ViewName == "" {
if binding.ViewName == "" {
bindingsGlobal = append(bindingsGlobal, binding)
} else if binding.Tag == "navigation" {
bindingsNavigation = append(bindingsNavigation, binding)
} else if lo.Contains(binding.Contexts, string(context.GetKey())) {
} else if binding.ViewName == context.GetViewName() {
bindingsPanel = append(bindingsPanel, binding)
}
}

View File

@ -1,134 +0,0 @@
package gui
import "github.com/samber/lo"
func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
if !gui.git.Patch.PatchManager.Active() {
return gui.handleEscapePatchBuildingPanel()
}
gui.Views.Main.Title = "Patch"
gui.Views.Secondary.Title = "Custom Patch"
// get diff from commit file that's currently selected
node := gui.State.Contexts.CommitFiles.GetSelected()
if node == nil {
return nil
}
ref := gui.State.Contexts.CommitFiles.CommitFileTreeViewModel.GetRef()
to := ref.RefName()
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
diff, err := gui.git.WorkingTree.ShowFileDiff(from, to, reverse, node.GetPath(), true)
if err != nil {
return err
}
secondaryDiff := gui.git.Patch.PatchManager.RenderPatchForFile(node.GetPath(), true, false, true)
if err != nil {
return err
}
empty, err := gui.refreshLineByLinePanel(diff, secondaryDiff, false, selectedLineIdx)
if err != nil {
return err
}
if empty {
return gui.handleEscapePatchBuildingPanel()
}
return nil
}
func (gui *Gui) handleRefreshPatchBuildingPanel(selectedLineIdx int) error {
gui.Mutexes.LineByLinePanelMutex.Lock()
defer gui.Mutexes.LineByLinePanelMutex.Unlock()
return gui.refreshPatchBuildingPanel(selectedLineIdx)
}
func (gui *Gui) onPatchBuildingFocus(selectedLineIdx int) error {
gui.Mutexes.LineByLinePanelMutex.Lock()
defer gui.Mutexes.LineByLinePanelMutex.Unlock()
if gui.State.Panels.LineByLine == nil || selectedLineIdx != -1 {
return gui.refreshPatchBuildingPanel(selectedLineIdx)
}
return nil
}
func (gui *Gui) handleToggleSelectionForPatch() error {
err := gui.withLBLActiveCheck(func(state *LblPanelState) error {
toggleFunc := gui.git.Patch.PatchManager.AddFileLineRange
filename := gui.getSelectedCommitFileName()
includedLineIndices, err := gui.git.Patch.PatchManager.GetFileIncLineIndices(filename)
if err != nil {
return err
}
currentLineIsStaged := lo.Contains(includedLineIndices, state.GetSelectedLineIdx())
if currentLineIsStaged {
toggleFunc = gui.git.Patch.PatchManager.RemoveFileLineRange
}
// add range of lines to those set for the file
node := gui.State.Contexts.CommitFiles.GetSelected()
if node == nil {
return nil
}
firstLineIdx, lastLineIdx := state.SelectedRange()
if err := toggleFunc(node.GetPath(), firstLineIdx, lastLineIdx); err != nil {
// might actually want to return an error here
gui.c.Log.Error(err)
}
return nil
})
if err != nil {
return err
}
if err := gui.handleRefreshPatchBuildingPanel(-1); err != nil {
return err
}
if err := gui.refreshCommitFilesContext(); err != nil {
return err
}
return nil
}
func (gui *Gui) handleEscapePatchBuildingPanel() error {
gui.escapeLineByLinePanel()
if gui.git.Patch.PatchManager.IsEmpty() {
gui.git.Patch.PatchManager.Reset()
}
if gui.currentContext().GetKey() == gui.State.Contexts.PatchBuilding.GetKey() {
return gui.c.PushContext(gui.State.Contexts.CommitFiles)
} else {
// need to re-focus in case the secondary view should now be hidden
return gui.currentContext().HandleFocus()
}
}
func (gui *Gui) secondaryPatchPanelUpdateOpts() *viewUpdateOpts {
if gui.git.Patch.PatchManager.Active() {
patch := gui.git.Patch.PatchManager.RenderAggregatedPatchColored(false)
return &viewUpdateOpts{
title: "Custom Patch",
noWrap: true,
highlight: true,
context: gui.State.Contexts.PatchBuilding,
task: NewRenderStringWithoutScrollTask(patch),
}
}
return nil
}

View File

@ -1,4 +1,4 @@
package lbl
package patch_exploring
import "github.com/jesseduffield/lazygit/pkg/utils"

View File

@ -1,4 +1,4 @@
package lbl
package patch_exploring
import (
"testing"

View File

@ -1,12 +1,12 @@
package lbl
package patch_exploring
import (
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/sirupsen/logrus"
)
// State represents the current state of the line-by-line context i.e. when
// you're staging a file line-by-line or you're building a patch from an existing commit
// State represents the current state of the patch explorer context i.e. when
// you're staging a file or you're building a patch from an existing commit
// this struct holds the info about the diff you're interacting with and what's currently selected.
type State struct {
selectedLineIdx int
@ -26,6 +26,13 @@ const (
)
func NewState(diff string, selectedLineIdx int, oldState *State, log *logrus.Entry) *State {
if oldState != nil && diff == oldState.diff && selectedLineIdx == -1 {
// if we're here then we can return the old state. If selectedLineIdx was not -1
// then that would mean we were trying to click and potentiall drag a range, which
// is why in that case we continue below
return oldState
}
patchParser := patch.NewPatchParser(log, diff)
if len(patchParser.StageableLines) == 0 {
@ -178,14 +185,14 @@ func (s *State) AdjustSelectedLineIdx(change int) {
s.SelectLine(s.selectedLineIdx + change)
}
func (s *State) RenderForLineIndices(includedLineIndices []int) string {
func (s *State) RenderForLineIndices(isFocused bool, includedLineIndices []int) string {
firstLineIdx, lastLineIdx := s.SelectedRange()
return s.patchParser.Render(firstLineIdx, lastLineIdx, includedLineIndices)
return s.patchParser.Render(isFocused, firstLineIdx, lastLineIdx, includedLineIndices)
}
func (s *State) PlainRenderSelected() string {
firstLineIdx, lastLineIdx := s.SelectedRange()
return s.patchParser.PlainRenderLines(firstLineIdx, lastLineIdx)
return s.patchParser.RenderLinesPlain(firstLineIdx, lastLineIdx)
}
func (s *State) SelectBottom() {

View File

@ -5,6 +5,7 @@ package gui
import (
"io"
"os"
"os/exec"
"strings"
@ -19,14 +20,20 @@ func (gui *Gui) desiredPtySize() *pty.Winsize {
}
func (gui *Gui) onResize() error {
gui.Mutexes.PtyMutex.Lock()
defer gui.Mutexes.PtyMutex.Unlock()
if gui.State.Ptmx == nil {
return nil
}
gui.Log.Warn("resizing")
// TODO: handle resizing properly: we need to actually clear the main view
// and re-read the output from our pty. Or we could just re-run the original
// command from scratch
if err := pty.Setsize(gui.State.Ptmx, gui.desiredPtySize()); err != nil {
panic(err)
return err
}
@ -57,20 +64,27 @@ func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error
manager := gui.getManager(view)
var ptmx *os.File
start := func() (*exec.Cmd, io.Reader) {
ptmx, err := pty.StartWithSize(cmd, gui.desiredPtySize())
var err error
ptmx, err = pty.StartWithSize(cmd, gui.desiredPtySize())
if err != nil {
gui.c.Log.Error(err)
}
gui.Mutexes.PtyMutex.Lock()
gui.State.Ptmx = ptmx
gui.Mutexes.PtyMutex.Unlock()
return cmd, ptmx
}
onClose := func() {
gui.State.Ptmx.Close()
gui.Mutexes.PtyMutex.Lock()
ptmx.Close()
ptmx = nil
gui.State.Ptmx = nil
gui.Mutexes.PtyMutex.Unlock()
}
if err := manager.NewTask(manager.NewCmdTask(start, prefix, height+oy+10, onClose), cmdStr); err != nil {

View File

@ -110,6 +110,7 @@ func (gui *Gui) handleShowAllBranchLogs() error {
task := NewRunPtyTask(cmdObj.GetCmd())
return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{
title: gui.c.Tr.LogTitle,
task: task,

View File

@ -12,6 +12,7 @@ func (gui *Gui) reflogCommitsRenderToMain() error {
}
return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{
title: "Reflog Entry",
task: task,

View File

@ -13,6 +13,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
@ -31,6 +32,7 @@ func getScopeNames(scopes []types.RefreshableView) []string {
types.REMOTES: "remotes",
types.STATUS: "status",
types.BISECT_INFO: "bisect",
types.STAGING: "staging",
}
return slices.Map(scopes, func(scope types.RefreshableView) string {
@ -70,6 +72,8 @@ func (gui *Gui) Refresh(options types.RefreshOptions) error {
f := func() {
var scopeSet *set.Set[types.RefreshableView]
if len(options.Scope) == 0 {
// not refreshing staging/patch-building unless explicitly requested because we only need
// to refresh those while focused.
scopeSet = set.NewFromSlice([]types.RefreshableView{
types.COMMITS,
types.BRANCHES,
@ -105,6 +109,11 @@ func (gui *Gui) Refresh(options types.RefreshOptions) error {
refresh(func() { _ = gui.refreshRebaseCommits() })
}
// reason we're not doing this if the COMMITS type is included is that if the COMMITS type _is_ included we will refresh the commit files context anyway
if scopeSet.Includes(types.COMMIT_FILES) && !scopeSet.Includes(types.COMMITS) {
refresh(func() { _ = gui.refreshCommitFilesContext() })
}
if scopeSet.Includes(types.FILES) || scopeSet.Includes(types.SUBMODULES) {
refresh(func() { _ = gui.refreshFilesAndSubmodules() })
}
@ -121,6 +130,14 @@ func (gui *Gui) Refresh(options types.RefreshOptions) error {
refresh(func() { _ = gui.refreshRemotes() })
}
if scopeSet.Includes(types.STAGING) {
refresh(func() { _ = gui.refreshStagingPanel(types.OnFocusOpts{}) })
}
if scopeSet.Includes(types.PATCH_BUILDING) {
refresh(func() { _ = gui.refreshPatchBuildingPanel(types.OnFocusOpts{}) })
}
wg.Wait()
gui.refreshStatus()
@ -218,6 +235,21 @@ func (gui *Gui) refreshCommitsWithLimit() error {
return gui.c.PostRefreshUpdate(gui.State.Contexts.LocalCommits)
}
func (gui *Gui) refreshCommitFilesContext() error {
ref := gui.State.Contexts.CommitFiles.GetRef()
to := ref.RefName()
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
files, err := gui.git.Loaders.CommitFiles.GetFilesInDiff(from, to, reverse)
if err != nil {
return gui.c.Error(err)
}
gui.State.Model.CommitFiles = files
gui.State.Contexts.CommitFiles.CommitFileTreeViewModel.SetTree()
return gui.c.PostRefreshUpdate(gui.State.Contexts.CommitFiles)
}
func (gui *Gui) refreshRebaseCommits() error {
gui.Mutexes.LocalCommitsMutex.Lock()
defer gui.Mutexes.LocalCommitsMutex.Unlock()
@ -332,7 +364,7 @@ func (gui *Gui) refreshMergeState() error {
gui.State.Panels.Merging.Lock()
defer gui.State.Panels.Merging.Unlock()
if gui.currentContext().GetKey() != context.MAIN_MERGING_CONTEXT_KEY {
if gui.currentContext().GetKey() != context.MERGING_MAIN_CONTEXT_KEY {
return nil
}
@ -535,3 +567,137 @@ func (gui *Gui) refreshStatus() {
gui.setViewContent(gui.Views.Status, status)
}
func (gui *Gui) refreshStagingPanel(focusOpts types.OnFocusOpts) error {
secondaryFocused := gui.secondaryStagingFocused()
mainSelectedLineIdx := -1
secondarySelectedLineIdx := -1
if focusOpts.ClickedViewLineIdx > 0 {
if secondaryFocused {
secondarySelectedLineIdx = focusOpts.ClickedViewLineIdx
} else {
mainSelectedLineIdx = focusOpts.ClickedViewLineIdx
}
}
mainContext := gui.State.Contexts.Staging
secondaryContext := gui.State.Contexts.StagingSecondary
file := gui.getSelectedFile()
if file == nil || (!file.HasUnstagedChanges && !file.HasStagedChanges) {
return gui.handleStagingEscape()
}
mainDiff := gui.git.WorkingTree.WorktreeFileDiff(file, true, false, false)
secondaryDiff := gui.git.WorkingTree.WorktreeFileDiff(file, true, true, false)
// grabbing locks here and releasing before we finish the function
// because pushing say the secondary context could mean entering this function
// again, and we don't want to have a deadlock
mainContext.GetMutex().Lock()
secondaryContext.GetMutex().Lock()
mainContext.SetState(
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetState(), gui.Log),
)
secondaryContext.SetState(
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetState(), gui.Log),
)
mainState := mainContext.GetState()
secondaryState := secondaryContext.GetState()
mainContent := mainContext.GetContentToRender(!secondaryFocused)
secondaryContent := secondaryContext.GetContentToRender(secondaryFocused)
mainContext.GetMutex().Unlock()
secondaryContext.GetMutex().Unlock()
if mainState == nil && secondaryState == nil {
return gui.handleStagingEscape()
}
if mainState == nil && !secondaryFocused {
return gui.c.PushContext(secondaryContext, focusOpts)
}
if secondaryState == nil && secondaryFocused {
return gui.c.PushContext(mainContext, focusOpts)
}
return gui.refreshMainViews(refreshMainOpts{
pair: gui.stagingMainContextPair(),
main: &viewUpdateOpts{
task: NewRenderStringWithoutScrollTask(mainContent),
title: gui.Tr.UnstagedChanges,
},
secondary: &viewUpdateOpts{
task: NewRenderStringWithoutScrollTask(secondaryContent),
title: gui.Tr.StagedChanges,
},
})
}
func (gui *Gui) handleStagingEscape() error {
return gui.c.PushContext(gui.State.Contexts.Files)
}
func (gui *Gui) secondaryStagingFocused() bool {
return gui.currentStaticContext().GetKey() == gui.State.Contexts.StagingSecondary.GetKey()
}
func (gui *Gui) refreshPatchBuildingPanel(opts types.OnFocusOpts) error {
selectedLineIdx := -1
if opts.ClickedWindowName == "main" {
selectedLineIdx = opts.ClickedViewLineIdx
}
if !gui.git.Patch.PatchManager.Active() {
return gui.helpers.PatchBuilding.Escape()
}
// get diff from commit file that's currently selected
path := gui.State.Contexts.CommitFiles.GetSelectedPath()
if path == "" {
return nil
}
ref := gui.State.Contexts.CommitFiles.CommitFileTreeViewModel.GetRef()
to := ref.RefName()
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
diff, err := gui.git.WorkingTree.ShowFileDiff(from, to, reverse, path, true)
if err != nil {
return err
}
secondaryDiff := gui.git.Patch.PatchManager.RenderPatchForFile(path, false, false, true)
if err != nil {
return err
}
context := gui.State.Contexts.CustomPatchBuilder
oldState := context.GetState()
state := patch_exploring.NewState(diff, selectedLineIdx, oldState, gui.Log)
context.SetState(state)
if state == nil {
return gui.helpers.PatchBuilding.Escape()
}
mainContent := context.GetContentToRender(true)
return gui.refreshMainViews(refreshMainOpts{
pair: gui.patchBuildingMainContextPair(),
main: &viewUpdateOpts{
task: NewRenderStringWithoutScrollTask(mainContent),
title: gui.Tr.Patch,
},
secondary: &viewUpdateOpts{
task: NewRenderStringWithoutScrollTask(secondaryDiff),
title: gui.Tr.CustomPatch,
},
})
}

View File

@ -11,6 +11,7 @@ func (gui *Gui) remoteBranchesRenderToMain() error {
}
return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{
title: "Remote Branch",
task: task,

View File

@ -19,6 +19,7 @@ func (gui *Gui) remotesRenderToMain() error {
}
return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{
title: "Remote",
task: task,

View File

@ -29,7 +29,7 @@ func (self *KeybindingCreator) call(customCommand config.CustomCommand, handler
return nil, formatContextNotProvidedError(customCommand)
}
viewName, contexts, err := self.getViewNameAndContexts(customCommand)
viewName, err := self.getViewNameAndContexts(customCommand)
if err != nil {
return nil, err
}
@ -41,7 +41,6 @@ func (self *KeybindingCreator) call(customCommand config.CustomCommand, handler
return &types.Binding{
ViewName: viewName,
Contexts: contexts,
Key: self.getKey(customCommand.Key),
Modifier: gocui.ModNone,
Handler: handler,
@ -49,22 +48,18 @@ func (self *KeybindingCreator) call(customCommand config.CustomCommand, handler
}, nil
}
func (self *KeybindingCreator) getViewNameAndContexts(customCommand config.CustomCommand) (string, []string, error) {
func (self *KeybindingCreator) getViewNameAndContexts(customCommand config.CustomCommand) (string, error) {
if customCommand.Context == "global" {
return "", nil, nil
return "", nil
}
ctx, ok := self.contextForContextKey(types.ContextKey(customCommand.Context))
if !ok {
return "", nil, formatUnknownContextError(customCommand)
return "", formatUnknownContextError(customCommand)
}
// here we assume that a given context will always belong to the same view.
// Currently this is a safe bet but it's by no means guaranteed in the long term
// and we might need to make some changes in the future to support it.
viewName := ctx.GetViewName()
contexts := []string{customCommand.Context}
return viewName, contexts, nil
return viewName, nil
}
func (self *KeybindingCreator) contextForContextKey(contextKey types.ContextKey) (types.Context, bool) {

View File

@ -21,9 +21,9 @@ func (gui *Gui) nextSideWindow() error {
return err
}
viewName := gui.getViewNameForWindow(newWindow)
context := gui.getContextForWindow(newWindow)
return gui.pushContextWithView(viewName)
return gui.c.PushContext(context)
}
func (gui *Gui) previousSideWindow() error {
@ -47,13 +47,15 @@ func (gui *Gui) previousSideWindow() error {
return err
}
viewName := gui.getViewNameForWindow(newWindow)
context := gui.getContextForWindow(newWindow)
return gui.pushContextWithView(viewName)
return gui.c.PushContext(context)
}
func (gui *Gui) goToSideWindow(sideViewName string) func() error {
func (gui *Gui) goToSideWindow(window string) func() error {
return func() error {
return gui.pushContextWithView(sideViewName)
context := gui.getContextForWindow(window)
return gui.c.PushContext(context)
}
}

View File

@ -1,227 +0,0 @@
package gui
import (
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) refreshStagingPanel(forceSecondaryFocused bool, selectedLineIdx int) error {
gui.splitMainPanel(true)
file := gui.getSelectedFile()
if file == nil || (!file.HasUnstagedChanges && !file.HasStagedChanges) {
return gui.handleStagingEscape()
}
secondaryFocused := false
if forceSecondaryFocused {
secondaryFocused = true
} else if gui.State.Panels.LineByLine != nil {
secondaryFocused = gui.State.Panels.LineByLine.SecondaryFocused
}
if (secondaryFocused && !file.HasStagedChanges) || (!secondaryFocused && !file.HasUnstagedChanges) {
secondaryFocused = !secondaryFocused
}
if secondaryFocused {
gui.Views.Main.Title = gui.c.Tr.StagedChanges
gui.Views.Secondary.Title = gui.c.Tr.UnstagedChanges
} else {
gui.Views.Main.Title = gui.c.Tr.UnstagedChanges
gui.Views.Secondary.Title = gui.c.Tr.StagedChanges
}
// note for custom diffs, we'll need to send a flag here saying not to use the custom diff
diff := gui.git.WorkingTree.WorktreeFileDiff(file, true, secondaryFocused, false)
secondaryDiff := gui.git.WorkingTree.WorktreeFileDiff(file, true, !secondaryFocused, false)
// if we have e.g. a deleted file with nothing else to the diff will have only
// 4-5 lines in which case we'll swap panels
if len(strings.Split(diff, "\n")) < 5 {
if len(strings.Split(secondaryDiff, "\n")) < 5 {
return gui.handleStagingEscape()
}
secondaryFocused = !secondaryFocused
diff, secondaryDiff = secondaryDiff, diff
}
empty, err := gui.refreshLineByLinePanel(diff, secondaryDiff, secondaryFocused, selectedLineIdx)
if err != nil {
return err
}
if empty {
return gui.handleStagingEscape()
}
return nil
}
func (gui *Gui) handleTogglePanelClick() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
state.SecondaryFocused = !state.SecondaryFocused
return gui.refreshStagingPanel(false, gui.Views.Secondary.SelectedLineIdx())
})
}
func (gui *Gui) handleRefreshStagingPanel(forceSecondaryFocused bool, selectedLineIdx int) error {
gui.Mutexes.LineByLinePanelMutex.Lock()
defer gui.Mutexes.LineByLinePanelMutex.Unlock()
return gui.refreshStagingPanel(forceSecondaryFocused, selectedLineIdx)
}
func (gui *Gui) onStagingFocus(forceSecondaryFocused bool, selectedLineIdx int) error {
gui.Mutexes.LineByLinePanelMutex.Lock()
defer gui.Mutexes.LineByLinePanelMutex.Unlock()
if gui.State.Panels.LineByLine == nil || selectedLineIdx != -1 {
return gui.refreshStagingPanel(forceSecondaryFocused, selectedLineIdx)
}
return nil
}
func (gui *Gui) handleTogglePanel() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
state.SecondaryFocused = !state.SecondaryFocused
return gui.refreshStagingPanel(false, -1)
})
}
func (gui *Gui) handleStagingEscape() error {
gui.escapeLineByLinePanel()
return gui.c.PushContext(gui.State.Contexts.Files)
}
func (gui *Gui) handleToggleStagedSelection() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
return gui.applySelection(state.SecondaryFocused, state)
})
}
func (gui *Gui) handleResetSelection() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
if state.SecondaryFocused {
// for backwards compatibility
return gui.applySelection(true, state)
}
if !gui.c.UserConfig.Gui.SkipUnstageLineWarning {
return gui.c.Confirm(types.ConfirmOpts{
Title: gui.c.Tr.UnstageLinesTitle,
Prompt: gui.c.Tr.UnstageLinesPrompt,
HandleConfirm: func() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
return gui.applySelection(true, state)
})
},
})
} else {
return gui.applySelection(true, state)
}
})
}
func (gui *Gui) applySelection(reverse bool, state *LblPanelState) error {
file := gui.getSelectedFile()
if file == nil {
return nil
}
firstLineIdx, lastLineIdx := state.SelectedRange()
patch := patch.ModifiedPatchForRange(gui.Log, file.Name, state.GetDiff(), firstLineIdx, lastLineIdx, reverse, false)
if patch == "" {
return nil
}
// apply the patch then refresh this panel
// create a new temp file with the patch, then call git apply with that patch
applyFlags := []string{}
if !reverse || state.SecondaryFocused {
applyFlags = append(applyFlags, "cached")
}
gui.c.LogAction(gui.c.Tr.Actions.ApplyPatch)
err := gui.git.WorkingTree.ApplyPatch(patch, applyFlags...)
if err != nil {
return gui.c.Error(err)
}
if state.SelectingRange() {
state.SetLineSelectMode()
}
if err := gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
return err
}
if err := gui.refreshStagingPanel(false, -1); err != nil {
return err
}
return nil
}
func (gui *Gui) HandleOpenFile() error {
file := gui.getSelectedFile()
if file == nil {
return nil
}
return gui.helpers.Files.OpenFile(file.GetPath())
}
func (gui *Gui) handleEditHunk() error {
return gui.withLBLActiveCheck(func(state *LblPanelState) error {
return gui.editHunk(state.SecondaryFocused, state)
})
}
func (gui *Gui) editHunk(reverse bool, state *LblPanelState) error {
file := gui.getSelectedFile()
if file == nil {
return nil
}
hunk := state.CurrentHunk()
patchText := patch.ModifiedPatchForRange(gui.Log, file.Name, state.GetDiff(), hunk.FirstLineIdx, hunk.LastLineIdx(), reverse, false)
patchFilepath, err := gui.git.WorkingTree.SaveTemporaryPatch(patchText)
if err != nil {
return err
}
lineOffset := 3
lineIdxInHunk := state.GetSelectedLineIdx() - hunk.FirstLineIdx
if err := gui.helpers.Files.EditFileAtLine(patchFilepath, lineIdxInHunk+lineOffset); err != nil {
return err
}
editedPatchText, err := gui.git.File.Cat(patchFilepath)
if err != nil {
return err
}
applyFlags := []string{}
if !reverse || state.SecondaryFocused {
applyFlags = append(applyFlags, "cached")
}
gui.c.LogAction(gui.c.Tr.Actions.ApplyPatch)
lineCount := strings.Count(editedPatchText, "\n") + 1
newPatchText := patch.ModifiedPatchForRange(gui.Log, file.Name, editedPatchText, 0, lineCount, false, false)
if err := gui.git.WorkingTree.ApplyPatch(newPatchText, applyFlags...); err != nil {
return gui.c.Error(err)
}
if err := gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
return err
}
if err := gui.refreshStagingPanel(false, -1); err != nil {
return err
}
return nil
}

View File

@ -10,6 +10,7 @@ func (gui *Gui) stashRenderToMain() error {
}
return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{
title: "Stash",
task: task,

View File

@ -88,8 +88,9 @@ func (gui *Gui) statusRenderToMain() error {
}, "\n\n")
return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{
title: "",
title: gui.c.Tr.StatusTitle,
task: NewRenderStringTask(dashboardString),
},
})

View File

@ -14,6 +14,7 @@ func (gui *Gui) subCommitsRenderToMain() error {
}
return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{
title: "Commit",
task: task,

View File

@ -31,6 +31,7 @@ func (gui *Gui) submodulesRenderToMain() error {
}
return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{
title: "Submodule",
task: task,

View File

@ -1,16 +1,17 @@
package gui
func (self *Gui) tagsRenderToMain() error {
func (gui *Gui) tagsRenderToMain() error {
var task updateTask
tag := self.State.Contexts.Tags.GetSelected()
tag := gui.State.Contexts.Tags.GetSelected()
if tag == nil {
task = NewRenderStringTask("No tags")
} else {
cmdObj := self.git.Branch.GetGraphCmdObj(tag.FullRefName())
cmdObj := gui.git.Branch.GetGraphCmdObj(tag.FullRefName())
task = NewRunCommandTask(cmdObj.GetCmd())
}
return self.refreshMainViews(refreshMainOpts{
return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{
title: "Tag",
task: task,

View File

@ -24,19 +24,19 @@ func (gui *Gui) newCmdTask(view *gocui.View, cmd *exec.Cmd, prefix string) error
start := func() (*exec.Cmd, io.Reader) {
r, err := cmd.StdoutPipe()
if err != nil {
gui.c.Log.Warn(err)
gui.c.Log.Error(err)
}
cmd.Stderr = cmd.Stdout
if err := cmd.Start(); err != nil {
gui.c.Log.Warn(err)
gui.c.Log.Error(err)
}
return cmd, r
}
if err := manager.NewTask(manager.NewCmdTask(start, prefix, height+oy+10, nil), cmdStr); err != nil {
gui.c.Log.Warn(err)
gui.c.Log.Error(err)
}
return nil

View File

@ -37,6 +37,8 @@ type IGuiCommon interface {
PushContext(context Context, opts ...OnFocusOpts) error
PopContext() error
CurrentContext() Context
CurrentStaticContext() Context
IsCurrentContext(Context) bool
// enters search mode for the current view
OpenSearch()
@ -162,7 +164,7 @@ type Mutexes struct {
RefreshingStatusMutex *sync.Mutex
SyncMutex *sync.Mutex
LocalCommitsMutex *sync.Mutex
LineByLinePanelMutex *sync.Mutex
SubprocessMutex *sync.Mutex
PopupMutex *sync.Mutex
PtyMutex *sync.Mutex
}

View File

@ -1,8 +1,11 @@
package types
import (
"sync"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
)
type ContextKind int
@ -25,6 +28,9 @@ const (
EXTRAS_CONTEXT
// only used by the one global context, purely for the sake of defining keybindings globally
GLOBAL_CONTEXT
// a display context only renders a view. It has no keybindings associated and
// it cannot receive focus.
DISPLAY_CONTEXT
)
type ParentContexter interface {
@ -39,6 +45,8 @@ type IBaseContext interface {
GetKind() ContextKind
GetViewName() string
GetView() *gocui.View
GetViewTrait() IViewTrait
GetWindowName() string
SetWindowName(string)
GetKey() ContextKey
@ -48,6 +56,9 @@ type IBaseContext interface {
// of the same transient context can appear at once meaning one might be 'stolen'
// from another window.
IsTransient() bool
// this tells us if the view's bounds are determined by its window or if they're
// determined independently.
HasControlledBounds() bool
// returns the desired title for the view upon activation. If there is no desired title (returns empty string), then
// no title will be set
@ -67,8 +78,8 @@ type IBaseContext interface {
type Context interface {
IBaseContext
HandleFocus(opts ...OnFocusOpts) error
HandleFocusLost() error
HandleFocus(opts OnFocusOpts) error
HandleFocusLost(opts OnFocusLostOpts) error
HandleRender() error
HandleRenderToMain() error
}
@ -82,8 +93,20 @@ type IListContext interface {
OnSearchSelect(selectedLineIdx int) error
FocusLine()
}
GetViewTrait() IViewTrait
type IPatchExplorerContext interface {
Context
GetState() *patch_exploring.State
SetState(*patch_exploring.State)
GetIncludedLineIndices() []int
RenderAndFocus(isFocused bool) error
Render(isFocused bool) error
Focus() error
GetContentToRender(isFocused bool) string
NavigateTo(isFocused bool, selectedLineIdx int) error
GetMutex() *sync.Mutex
}
type IViewTrait interface {
@ -95,17 +118,22 @@ type IViewTrait interface {
ViewPortYBounds() (int, int)
ScrollLeft()
ScrollRight()
ScrollUp()
ScrollDown()
ScrollUp(value int)
ScrollDown(value int)
PageDelta() int
SelectedLineIdx() int
SetHighlight(bool)
}
type OnFocusOpts struct {
ClickedViewName string
ClickedWindowName string
ClickedViewLineIdx int
}
type OnFocusLostOpts struct {
NewContextKey ContextKey
}
type ContextKey string
type KeybindingsOpts struct {

View File

@ -9,7 +9,6 @@ type Key interface{} // FIXME: find out how to get `gocui.Key | rune`
// is ""
type Binding struct {
ViewName string
Contexts []string
Handler func() error
Key Key
Modifier gocui.Modifier

View File

@ -14,6 +14,9 @@ const (
REMOTES
STATUS
SUBMODULES
STAGING
PATCH_BUILDING
COMMIT_FILES
// not actually a view. Will refactor this later
BISECT_INFO
)

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/spkg/bom"
)
@ -116,52 +117,71 @@ func (gui *Gui) popupPanelFocused() bool {
return gui.isPopupPanel(gui.currentViewName())
}
// secondaryViewFocused tells us whether it appears that the secondary view is focused. The view is actually never focused for real: we just swap the main and secondary views and then you're still focused on the main view so that we can give you access to all its keybindings for free. I will probably regret this design decision soon enough.
func (gui *Gui) secondaryViewFocused() bool {
state := gui.State.Panels.LineByLine
return state != nil && state.SecondaryFocused
}
func (gui *Gui) onViewTabClick(windowName string, tabIndex int) error {
tabs := gui.viewTabMap()[windowName]
if len(tabs) == 0 {
return nil
}
func (gui *Gui) onViewTabClick(viewName string, tabIndex int) error {
context := gui.State.ViewTabContextMap[viewName][tabIndex].Context
viewName := tabs[tabIndex].ViewName
context, ok := gui.contextForView(viewName)
if !ok {
return nil
}
return gui.c.PushContext(context)
}
func (gui *Gui) contextForView(viewName string) (types.Context, bool) {
view, err := gui.g.View(viewName)
if err != nil {
return nil, false
}
for _, context := range gui.State.Contexts.Flatten() {
if context.GetViewName() == view.Name() {
return context, true
}
}
return nil, false
}
func (gui *Gui) handleNextTab() error {
v := getTabbedView(gui)
if v == nil {
view := getTabbedView(gui)
if view == nil {
return nil
}
return gui.onViewTabClick(
v.Name(),
utils.ModuloWithWrap(v.TabIndex+1, len(v.Tabs)),
)
for _, context := range gui.State.Contexts.Flatten() {
if context.GetViewName() == view.Name() {
return gui.onViewTabClick(
context.GetWindowName(),
utils.ModuloWithWrap(view.TabIndex+1, len(view.Tabs)),
)
}
}
return nil
}
func (gui *Gui) handlePrevTab() error {
v := getTabbedView(gui)
if v == nil {
view := getTabbedView(gui)
if view == nil {
return nil
}
return gui.onViewTabClick(
v.Name(),
utils.ModuloWithWrap(v.TabIndex-1, len(v.Tabs)),
)
}
// this is the distance we will move the cursor when paging up or down in a view
func (gui *Gui) pageDelta(view *gocui.View) int {
_, height := view.Size()
delta := height - 1
if delta == 0 {
return 1
for _, context := range gui.State.Contexts.Flatten() {
if context.GetViewName() == view.Name() {
return gui.onViewTabClick(
context.GetWindowName(),
utils.ModuloWithWrap(view.TabIndex-1, len(view.Tabs)),
)
}
}
return delta
return nil
}
func getTabbedView(gui *Gui) *gocui.View {

View File

@ -3,34 +3,43 @@ package gui
import (
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
)
type Views struct {
Status *gocui.View
Submodules *gocui.View
Files *gocui.View
Branches *gocui.View
Remotes *gocui.View
Tags *gocui.View
RemoteBranches *gocui.View
ReflogCommits *gocui.View
Commits *gocui.View
Stash *gocui.View
Main *gocui.View
Secondary *gocui.View
Options *gocui.View
Confirmation *gocui.View
Menu *gocui.View
CommitMessage *gocui.View
CommitFiles *gocui.View
SubCommits *gocui.View
Information *gocui.View
AppStatus *gocui.View
Search *gocui.View
SearchPrefix *gocui.View
Limit *gocui.View
Suggestions *gocui.View
Tooltip *gocui.View
Extras *gocui.View
Main *gocui.View
Secondary *gocui.View
Staging *gocui.View
StagingSecondary *gocui.View
PatchBuilding *gocui.View
PatchBuildingSecondary *gocui.View
Merging *gocui.View
Options *gocui.View
Confirmation *gocui.View
Menu *gocui.View
CommitMessage *gocui.View
CommitFiles *gocui.View
SubCommits *gocui.View
Information *gocui.View
AppStatus *gocui.View
Search *gocui.View
SearchPrefix *gocui.View
Limit *gocui.View
Suggestions *gocui.View
Tooltip *gocui.View
Extras *gocui.View
}
type viewNameMapping struct {
@ -49,15 +58,26 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping {
// first layer. Ordering within this layer does not matter because there are
// no overlapping views
{viewPtr: &gui.Views.Status, name: "status"},
{viewPtr: &gui.Views.Submodules, name: "submodules"},
{viewPtr: &gui.Views.Files, name: "files"},
{viewPtr: &gui.Views.Branches, name: "branches"},
{viewPtr: &gui.Views.Tags, name: "tags"},
{viewPtr: &gui.Views.Remotes, name: "remotes"},
{viewPtr: &gui.Views.Branches, name: "localBranches"},
{viewPtr: &gui.Views.RemoteBranches, name: "remoteBranches"},
{viewPtr: &gui.Views.ReflogCommits, name: "reflogCommits"},
{viewPtr: &gui.Views.Commits, name: "commits"},
{viewPtr: &gui.Views.Stash, name: "stash"},
{viewPtr: &gui.Views.SubCommits, name: "subCommits"},
{viewPtr: &gui.Views.CommitFiles, name: "commitFiles"},
{viewPtr: &gui.Views.Main, name: "main"},
{viewPtr: &gui.Views.Staging, name: "staging"},
{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.Secondary, name: "secondary"},
{viewPtr: &gui.Views.Main, name: "main"},
{viewPtr: &gui.Views.Extras, name: "extras"},
// bottom line
@ -80,35 +100,13 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping {
}
}
type controlledView struct {
viewName string
windowName string
frame bool
}
// controlled views have their size and position determined in arrangement.go.
// Some views, like the confirmation panel, are currently sized at the time of
// displaying the view, based on the view's contents.
func (gui *Gui) controlledViews() []controlledView {
return []controlledView{
{viewName: "main", windowName: "main", frame: true},
{viewName: "secondary", windowName: "secondary", frame: true},
{viewName: "status", windowName: "status", frame: true},
{viewName: "files", windowName: "files", frame: true},
{viewName: "branches", windowName: "branches", frame: true},
{viewName: "remoteBranches", windowName: "branches", frame: true},
{viewName: "commitFiles", windowName: gui.State.Contexts.CommitFiles.GetWindowName(), frame: true},
{viewName: "subCommits", windowName: gui.State.Contexts.SubCommits.GetWindowName(), frame: true},
{viewName: "commits", windowName: "commits", frame: true},
{viewName: "stash", windowName: "stash", frame: true},
{viewName: "options", windowName: "options", frame: false},
{viewName: "searchPrefix", windowName: "searchPrefix", frame: false},
{viewName: "search", windowName: "search", frame: false},
{viewName: "appStatus", windowName: "appStatus", frame: false},
{viewName: "information", windowName: "information", frame: false},
{viewName: "extras", windowName: "extras", frame: true},
{viewName: "limit", windowName: "limit", frame: true},
func (gui *Gui) windowForView(viewName string) string {
context, ok := gui.contextForView(viewName)
if !ok {
panic("todo: deal with this")
}
return context.GetWindowName()
}
func (gui *Gui) createAllViews() error {
@ -121,9 +119,11 @@ func (gui *Gui) createAllViews() error {
}
gui.Views.Options.FgColor = theme.OptionsColor
gui.Views.Options.Frame = false
gui.Views.SearchPrefix.BgColor = gocui.ColorDefault
gui.Views.SearchPrefix.FgColor = gocui.ColorGreen
gui.Views.SearchPrefix.Frame = false
gui.setViewContent(gui.Views.SearchPrefix, SEARCH_PREFIX)
gui.Views.Stash.Title = gui.c.Tr.StashTitle
@ -140,22 +140,44 @@ func (gui *Gui) createAllViews() error {
gui.Views.Branches.Title = gui.c.Tr.BranchesTitle
gui.Views.Branches.FgColor = theme.GocuiDefaultTextColor
gui.Views.Remotes.Title = gui.c.Tr.RemotesTitle
gui.Views.Remotes.FgColor = theme.GocuiDefaultTextColor
gui.Views.Tags.Title = gui.c.Tr.TagsTitle
gui.Views.Tags.FgColor = theme.GocuiDefaultTextColor
gui.Views.RemoteBranches.FgColor = theme.GocuiDefaultTextColor
gui.Views.Files.Title = gui.c.Tr.FilesTitle
gui.Views.Files.FgColor = theme.GocuiDefaultTextColor
gui.Views.Secondary.Title = gui.c.Tr.DiffTitle
gui.Views.Secondary.Wrap = true
gui.Views.Secondary.FgColor = theme.GocuiDefaultTextColor
gui.Views.Secondary.IgnoreCarriageReturns = true
gui.Views.Secondary.CanScrollPastBottom = gui.c.UserConfig.Gui.ScrollPastBottom
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} {
view.Title = gui.c.Tr.DiffTitle
view.Wrap = true
view.FgColor = theme.GocuiDefaultTextColor
view.IgnoreCarriageReturns = true
view.CanScrollPastBottom = gui.c.UserConfig.Gui.ScrollPastBottom
}
gui.Views.Main.Title = gui.c.Tr.DiffTitle
gui.Views.Main.Wrap = true
gui.Views.Main.FgColor = theme.GocuiDefaultTextColor
gui.Views.Main.IgnoreCarriageReturns = true
gui.Views.Main.CanScrollPastBottom = gui.c.UserConfig.Gui.ScrollPastBottom
gui.Views.Staging.Title = gui.c.Tr.UnstagedChanges
gui.Views.Staging.Highlight = true
gui.Views.Staging.Wrap = true
gui.Views.StagingSecondary.Title = gui.c.Tr.StagedChanges
gui.Views.StagingSecondary.Highlight = true
gui.Views.StagingSecondary.Wrap = true
gui.Views.PatchBuilding.Title = gui.Tr.Patch
gui.Views.PatchBuilding.Highlight = true
gui.Views.PatchBuilding.Wrap = true
gui.Views.PatchBuildingSecondary.Title = gui.Tr.CustomPatch
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.Limit.Title = gui.c.Tr.NotEnoughSpace
gui.Views.Limit.Wrap = true
@ -166,10 +188,12 @@ func (gui *Gui) createAllViews() error {
gui.Views.Search.BgColor = gocui.ColorDefault
gui.Views.Search.FgColor = gocui.ColorGreen
gui.Views.Search.Editable = true
gui.Views.Search.Frame = false
gui.Views.AppStatus.BgColor = gocui.ColorDefault
gui.Views.AppStatus.FgColor = gocui.ColorCyan
gui.Views.AppStatus.Visible = false
gui.Views.AppStatus.Frame = false
gui.Views.CommitMessage.Visible = false
gui.Views.CommitMessage.Title = gui.c.Tr.CommitMessage
@ -189,6 +213,7 @@ func (gui *Gui) createAllViews() error {
gui.Views.Information.BgColor = gocui.ColorDefault
gui.Views.Information.FgColor = gocui.ColorGreen
gui.Views.Information.Frame = false
gui.Views.Extras.Title = gui.c.Tr.CommandLog
gui.Views.Extras.FgColor = theme.GocuiDefaultTextColor
@ -197,22 +222,3 @@ func (gui *Gui) createAllViews() error {
return nil
}
func initialViewContextMapping(contextTree *context.ContextTree) map[string]types.Context {
return map[string]types.Context{
"status": contextTree.Status,
"files": contextTree.Files,
"branches": contextTree.Branches,
"remoteBranches": contextTree.RemoteBranches,
"commits": contextTree.LocalCommits,
"commitFiles": contextTree.CommitFiles,
"subCommits": contextTree.SubCommits,
"stash": contextTree.Stash,
"menu": contextTree.Menu,
"confirmation": contextTree.Confirmation,
"commitMessage": contextTree.CommitMessage,
"main": contextTree.Normal,
"secondary": contextTree.Normal,
"extras": contextTree.CommandLog,
}
}

View File

@ -1,7 +1,11 @@
package gui
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
// A window refers to a place on the screen which can hold one or more views.
@ -10,21 +14,38 @@ import (
// space. Right now most windows are 1:1 with views, except for commitFiles which
// is a view that moves between windows
func (gui *Gui) initialWindowViewNameMap(contextTree *context.ContextTree) map[string]string {
result := map[string]string{}
for _, context := range contextTree.Flatten() {
result[context.GetWindowName()] = context.GetViewName()
}
return result
}
func (gui *Gui) getViewNameForWindow(window string) string {
viewName, ok := gui.State.WindowViewNameMap[window]
if !ok {
return window
panic(fmt.Sprintf("Viewname not found for window: %s", window))
}
return viewName
}
// for now all we actually care about is the context's view so we're storing that
func (gui *Gui) setWindowContext(c types.Context) {
if gui.State.WindowViewNameMap == nil {
gui.State.WindowViewNameMap = map[string]string{}
func (gui *Gui) getContextForWindow(window string) types.Context {
viewName := gui.getViewNameForWindow(window)
context, ok := gui.contextForView(viewName)
if !ok {
panic("TODO: fix this")
}
return context
}
// for now all we actually care about is the context's view so we're storing that
func (gui *Gui) setWindowContext(c types.Context) {
if c.IsTransient() {
gui.resetWindowContext(c)
}
@ -40,8 +61,46 @@ func (gui *Gui) currentWindow() string {
func (gui *Gui) resetWindowContext(c types.Context) {
for windowName, viewName := range gui.State.WindowViewNameMap {
if viewName == c.GetViewName() && windowName != c.GetWindowName() {
// we assume here that the window contains as its default view a view with the same name as the window
gui.State.WindowViewNameMap[windowName] = windowName
for _, context := range gui.State.Contexts.Flatten() {
if context.GetKey() != c.GetKey() && context.GetWindowName() == windowName {
gui.State.WindowViewNameMap[windowName] = context.GetViewName()
}
}
}
}
}
func (gui *Gui) moveToTopOfWindow(context types.Context) {
view := context.GetView()
if view == nil {
return
}
window := context.GetWindowName()
// now I need to find all views in that same window, via contexts. And I guess then I need to find the index of the highest view in that list.
viewNamesInWindow := gui.viewNamesInWindow(window)
// The views list is ordered highest-last, so we're grabbing the last view of the window
topView := view
for _, currentView := range gui.g.Views() {
if lo.Contains(viewNamesInWindow, currentView.Name()) {
topView = currentView
}
}
if err := gui.g.SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
gui.Log.Error(err)
}
}
func (gui *Gui) viewNamesInWindow(windowName string) []string {
result := []string{}
for _, context := range gui.State.Contexts.Flatten() {
if context.GetWindowName() == windowName {
result = append(result, context.GetViewName())
}
}
return result
}

View File

@ -176,7 +176,7 @@ func chineseTranslationSet() TranslationSet {
ToggleDragSelect: `切换拖动选择`,
ToggleSelectHunk: `切换选择区块`,
ToggleSelectionForPatch: `添加/移除 行到补丁`,
TogglePanel: `切换到其他面板`,
ToggleStagingPanel: `切换到其他面板`,
ReturnToFilesPanel: `返回文件面板`,
FastForward: `从上游快进此分支`,
Fetching: "抓取并快进 {{.from}} -> {{.to}} ...",
@ -298,7 +298,7 @@ func chineseTranslationSet() TranslationSet {
PatchOptionsTitle: "补丁选项",
NoPatchError: "尚未创建补丁。你可以在提交中的文件上按下“空格”或使用“回车”添加其中的特定行以开始构建补丁",
LcEnterFile: "输入文件以将所选行添加到补丁中(或切换目录折叠)",
ExitLineByLineMode: `退出逐行模式`,
ExitCustomPatchBuilder: `退出逐行模式`,
EnterUpstream: `以这种格式输入上游:'<远程仓库> <分支名称>'`,
InvalidUpstream: "上游格式无效,格式应当为:'<remote> <branchname>'",
ReturnToRemotesList: `返回远程仓库列表`,

View File

@ -141,7 +141,7 @@ func dutchTranslationSet() TranslationSet {
ToggleDragSelect: `toggle drag selecteer`,
ToggleSelectHunk: `toggle selecteer hunk`,
ToggleSelectionForPatch: `voeg toe/verwijder lijn(en) in patch`,
TogglePanel: `ga naar een ander paneel`,
ToggleStagingPanel: `ga naar een ander paneel`,
ReturnToFilesPanel: `ga terug naar het bestanden paneel`,
FastForward: `fast-forward deze branch vanaf zijn upstream`,
Fetching: "fetching en fast-forwarding {{.from}} -> {{.to}} ...",
@ -258,7 +258,7 @@ func dutchTranslationSet() TranslationSet {
PatchOptionsTitle: "Patch Opties",
NoPatchError: "Nog geen patch gecreëerd. Om een patch te bouwen gebruik 'space' op een commit bestand of 'enter' om een spesiefieke lijnen toe te voegen",
LcEnterFile: "enter bestand om geselecteerde regels toe te voegen aan de patch",
ExitLineByLineMode: `sluit lijn-bij-lijn modus`,
ExitCustomPatchBuilder: `sluit lijn-bij-lijn modus`,
EnterUpstream: `Enter upstream als '<remote> <branchnaam>'`,
ReturnToRemotesList: `ga terug naar remotes lijst`,
LcAddNewRemote: `voeg een nieuwe remote toe`,

View File

@ -176,7 +176,7 @@ type TranslationSet struct {
ToggleSelectHunk string
ToggleSelectionForPatch string
EditHunk string
TogglePanel string
ToggleStagingPanel string
ReturnToFilesPanel string
FastForward string
Fetching string
@ -307,7 +307,7 @@ type TranslationSet struct {
PatchOptionsTitle string
NoPatchError string
LcEnterFile string
ExitLineByLineMode string
ExitCustomPatchBuilder string
EnterUpstream string
InvalidUpstream string
ReturnToRemotesList string
@ -503,6 +503,8 @@ type TranslationSet struct {
NukeDescription string
DiscardStagedChangesDescription string
EmptyOutput string
Patch string
CustomPatch string
Actions Actions
Bisect Bisect
}
@ -812,7 +814,7 @@ func EnglishTranslationSet() TranslationSet {
ToggleSelectHunk: `toggle select hunk`,
ToggleSelectionForPatch: `add/remove line(s) to patch`,
EditHunk: `edit hunk`,
TogglePanel: `switch to other panel`,
ToggleStagingPanel: `switch to other panel (staged/unstaged changes)`,
ReturnToFilesPanel: `return to files panel`,
FastForward: `fast-forward this branch from its upstream`,
Fetching: "fetching and fast-forwarding {{.from}} -> {{.to}} ...",
@ -944,7 +946,7 @@ func EnglishTranslationSet() TranslationSet {
PatchOptionsTitle: "Patch Options",
NoPatchError: "No patch created yet. To start building a patch, use 'space' on a commit file or enter to add specific lines",
LcEnterFile: "enter file to add selected lines to the patch (or toggle directory collapsed)",
ExitLineByLineMode: `exit line-by-line mode`,
ExitCustomPatchBuilder: `exit custom patch builder`,
EnterUpstream: `Enter upstream as '<remote> <branchname>'`,
InvalidUpstream: "Invalid upstream. Must be in the format '<remote> <branchname>'",
ReturnToRemotesList: `Return to remotes list`,
@ -1140,6 +1142,8 @@ func EnglishTranslationSet() TranslationSet {
NukeDescription: "If you want to make all the changes in the worktree go away, this is the way to do it. If there are dirty submodule changes this will stash those changes in the submodule(s).",
DiscardStagedChangesDescription: "This will create a new stash entry containing only staged files and then drop it, so that the working tree is left with only unstaged changes",
EmptyOutput: "<empty output>",
Patch: "Patch",
CustomPatch: "Custom patch",
Actions: Actions{
// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
CheckoutCommit: "Checkout commit",

View File

@ -176,7 +176,7 @@ func japaneseTranslationSet() TranslationSet {
ToggleDragSelect: `範囲選択を切り替え`,
ToggleSelectHunk: `hunk選択を切り替え`,
ToggleSelectionForPatch: `行をパッチに追加/削除`,
TogglePanel: `パネルを切り替え`,
ToggleStagingPanel: `パネルを切り替え`,
ReturnToFilesPanel: `ファイル一覧に戻る`,
// FastForward: `fast-forward this branch from its upstream`,
// Fetching: "fetching and fast-forwarding {{.from}} -> {{.to}} ...",
@ -303,7 +303,7 @@ func japaneseTranslationSet() TranslationSet {
// PatchOptionsTitle: "Patch Options",
// NoPatchError: "No patch created yet. To start building a patch, use 'space' on a commit file or enter to add specific lines",
// LcEnterFile: "enter file to add selected lines to the patch (or toggle directory collapsed)",
ExitLineByLineMode: `line-by-lineモードを終了`,
// ExitCustomPatchBuilder: ``,
EnterUpstream: `'<remote> <branchname>' の形式でupstreamを入力`,
InvalidUpstream: "upstreamの形式が正しくありません。'<remote> <branchname>' の形式で入力してください。",
ReturnToRemotesList: `リモート一覧に戻る`,

View File

@ -177,7 +177,7 @@ func koreanTranslationSet() TranslationSet {
ToggleDragSelect: `드래그 선택 전환`,
ToggleSelectHunk: `toggle select hunk`,
ToggleSelectionForPatch: `line(s)을 패치에 추가/삭제`,
TogglePanel: `패널 전환`,
ToggleStagingPanel: `패널 전환`,
ReturnToFilesPanel: `파일 목록으로 돌아가기`,
FastForward: `fast-forward this branch from its upstream`,
Fetching: "fetching and fast-forwarding {{.from}} -> {{.to}} ...",
@ -304,84 +304,84 @@ func koreanTranslationSet() TranslationSet {
PatchOptionsTitle: "Patch 옵션",
NoPatchError: "No patch created yet. To start building a patch, use 'space' on a commit file or enter to add specific lines",
LcEnterFile: "enter file to add selected lines to the patch (or toggle directory collapsed)",
ExitLineByLineMode: `line-by-line 모드 종료`,
EnterUpstream: `'<remote> <branchname>'와 같은 형식으로 입력하세요.`,
InvalidUpstream: "upstream의 형식이 잘못되었습니다.'<remote> <branchname>' 와 같은 형식으로 입력하세요.",
ReturnToRemotesList: `원격목록으로 돌아가기`,
LcAddNewRemote: `새로운 Remote 추가`,
LcNewRemoteName: `새로운 Remote 이름:`,
LcNewRemoteUrl: `새로운 Remote URL:`,
LcEditRemoteName: `{{.remoteName}} 의 새로운 Remote 이름 입력:`,
LcEditRemoteUrl: `{{.remoteName}} 의 새로운 Remote URL 입력:`,
LcRemoveRemote: `Remote를 삭제`,
LcRemoveRemotePrompt: "정말로 Remote를 삭제하시겠습니까?",
DeleteRemoteBranch: "원격 브랜치를 삭제",
DeleteRemoteBranchMessage: "정말로 원격 브랜치를 삭제하시겠습니까?",
LcSetUpstream: "set as upstream of checked-out branch",
SetUpstreamTitle: "Set upstream branch",
SetUpstreamMessage: "Are you sure you want to set the upstream branch of '{{.checkedOut}}' to '{{.selected}}'",
LcEditRemote: "Remote를 수정",
LcTagCommit: "tag commit",
TagMenuTitle: "태그 작성",
TagNameTitle: "태그 이름:",
TagMessageTitle: "태그 메시지: ",
LcAnnotatedTag: "annotated tag",
LcLightweightTag: "lightweight tag",
LcDeleteTag: "태그 삭제",
DeleteTagTitle: "태그 삭제",
DeleteTagPrompt: "정말로 태그 '{{.tagName}}' 를 삭제하시겠습니까?",
PushTagTitle: "원격에 태그 '{{.tagName}}' 를 푸시",
LcPushTag: "태그를 push",
LcCreateTag: "태그를 생성",
CreateTagTitle: "태그 이름:",
LcFetchRemote: "원격을 업데이트",
FetchingRemoteStatus: "원격을 업데이트 중",
LcCheckoutCommit: "커밋을 체크아웃",
SureCheckoutThisCommit: "정말로 선택한 커밋을 체크아웃 하시겠습니까?",
LcGitFlowOptions: "git-flow 옵션 보기",
NotAGitFlowBranch: "This does not seem to be a git flow branch",
NewGitFlowBranchPrompt: "new {{.branchType}} name:",
IgnoreTracked: "Ignore tracked file",
IgnoreTrackedPrompt: "Are you sure you want to ignore a tracked file?",
LcViewResetToUpstreamOptions: "view upstream reset options",
LcNextScreenMode: "다음 스크린 모드 (normal/half/fullscreen)",
LcPrevScreenMode: "이전 스크린 모드",
LcStartSearch: "검색 시작",
Panel: "패널",
Keybindings: "키 바인딩",
LcRenameBranch: "브랜치 이름 변경",
NewBranchNamePrompt: "새로운 브랜치 이름 입력",
RenameBranchWarning: "This branch is tracking a remote. This action will only rename the local branch name, not the name of the remote branch. Continue?",
LcOpenMenu: "매뉴 열기",
LcResetCherryPick: "reset cherry-picked (copied) commits selection",
LcNextTab: "이전 탭",
LcPrevTab: "다음 탭",
LcCantUndoWhileRebasing: "리베이스중에는 되돌릴 수 없습니다.",
LcCantRedoWhileRebasing: "리베이스중에는 다시 실행할 수 없습니다.",
MustStashWarning: "Pulling a patch out into the index requires stashing and unstashing your changes. If something goes wrong, you'll be able to access your files from the stash. Continue?",
MustStashTitle: "Must stash",
ConfirmationTitle: "확인 패널",
LcPrevPage: "이전 페이지",
LcNextPage: "다음 페이지",
LcGotoTop: "맨 위로 스크롤 ",
LcGotoBottom: "맨 아래로 스크롤 ",
LcFilteringBy: "filtering by",
ResetInParentheses: "(reset)",
LcOpenFilteringMenu: "view filter-by-path options",
LcFilterBy: "filter by",
LcExitFilterMode: "stop filtering by path",
LcFilterPathOption: "enter path to filter by",
EnterFileName: "Enter path:",
FilteringMenuTitle: "Filtering",
MustExitFilterModeTitle: "Command not available",
MustExitFilterModePrompt: "Command not available in filtered mode. Exit filtered mode?",
LcDiff: "Diff",
LcEnterRefToDiff: "enter ref to diff",
LcEnteRefName: "ref 입력:",
LcExitDiffMode: "Diff 모드 종료",
DiffingMenuTitle: "Diff",
LcSwapDiff: "reverse diff direction",
LcOpenDiffingMenu: "Diff 메뉴 열기",
// ExitCustomPatchBuilder: ``,
EnterUpstream: `'<remote> <branchname>'와 같은 형식으로 입력하세요.`,
InvalidUpstream: "upstream의 형식이 잘못되었습니다.'<remote> <branchname>' 와 같은 형식으로 입력하세요.",
ReturnToRemotesList: `원격목록으로 돌아가기`,
LcAddNewRemote: `새로운 Remote 추가`,
LcNewRemoteName: `새로운 Remote 이름:`,
LcNewRemoteUrl: `새로운 Remote URL:`,
LcEditRemoteName: `{{.remoteName}} 의 새로운 Remote 이름 입력:`,
LcEditRemoteUrl: `{{.remoteName}} 의 새로운 Remote URL 입력:`,
LcRemoveRemote: `Remote를 삭제`,
LcRemoveRemotePrompt: "정말로 Remote를 삭제하시겠습니까?",
DeleteRemoteBranch: "원격 브랜치를 삭제",
DeleteRemoteBranchMessage: "정말로 원격 브랜치를 삭제하시겠습니까?",
LcSetUpstream: "set as upstream of checked-out branch",
SetUpstreamTitle: "Set upstream branch",
SetUpstreamMessage: "Are you sure you want to set the upstream branch of '{{.checkedOut}}' to '{{.selected}}'",
LcEditRemote: "Remote를 수정",
LcTagCommit: "tag commit",
TagMenuTitle: "태그 작성",
TagNameTitle: "태그 이름:",
TagMessageTitle: "태그 메시지: ",
LcAnnotatedTag: "annotated tag",
LcLightweightTag: "lightweight tag",
LcDeleteTag: "태그 삭제",
DeleteTagTitle: "태그 삭제",
DeleteTagPrompt: "정말로 태그 '{{.tagName}}' 를 삭제하시겠습니까?",
PushTagTitle: "원격에 태그 '{{.tagName}}' 를 푸시",
LcPushTag: "태그를 push",
LcCreateTag: "태그를 생성",
CreateTagTitle: "태그 이름:",
LcFetchRemote: "원격을 업데이트",
FetchingRemoteStatus: "원격을 업데이트 중",
LcCheckoutCommit: "커밋을 체크아웃",
SureCheckoutThisCommit: "정말로 선택한 커밋을 체크아웃 하시겠습니까?",
LcGitFlowOptions: "git-flow 옵션 보기",
NotAGitFlowBranch: "This does not seem to be a git flow branch",
NewGitFlowBranchPrompt: "new {{.branchType}} name:",
IgnoreTracked: "Ignore tracked file",
IgnoreTrackedPrompt: "Are you sure you want to ignore a tracked file?",
LcViewResetToUpstreamOptions: "view upstream reset options",
LcNextScreenMode: "다음 스크린 모드 (normal/half/fullscreen)",
LcPrevScreenMode: "이전 스크린 모드",
LcStartSearch: "검색 시작",
Panel: "패널",
Keybindings: "키 바인딩",
LcRenameBranch: "브랜치 이름 변경",
NewBranchNamePrompt: "새로운 브랜치 이름 입력",
RenameBranchWarning: "This branch is tracking a remote. This action will only rename the local branch name, not the name of the remote branch. Continue?",
LcOpenMenu: "매뉴 열기",
LcResetCherryPick: "reset cherry-picked (copied) commits selection",
LcNextTab: "이전 탭",
LcPrevTab: "다음 탭",
LcCantUndoWhileRebasing: "리베이스중에는 되돌릴 수 없습니다.",
LcCantRedoWhileRebasing: "리베이스중에는 다시 실행할 수 없습니다.",
MustStashWarning: "Pulling a patch out into the index requires stashing and unstashing your changes. If something goes wrong, you'll be able to access your files from the stash. Continue?",
MustStashTitle: "Must stash",
ConfirmationTitle: "확인 패널",
LcPrevPage: "이전 페이지",
LcNextPage: "다음 페이지",
LcGotoTop: "맨 위로 스크롤 ",
LcGotoBottom: "맨 아래로 스크롤 ",
LcFilteringBy: "filtering by",
ResetInParentheses: "(reset)",
LcOpenFilteringMenu: "view filter-by-path options",
LcFilterBy: "filter by",
LcExitFilterMode: "stop filtering by path",
LcFilterPathOption: "enter path to filter by",
EnterFileName: "Enter path:",
FilteringMenuTitle: "Filtering",
MustExitFilterModeTitle: "Command not available",
MustExitFilterModePrompt: "Command not available in filtered mode. Exit filtered mode?",
LcDiff: "Diff",
LcEnterRefToDiff: "enter ref to diff",
LcEnteRefName: "ref 입력:",
LcExitDiffMode: "Diff 모드 종료",
DiffingMenuTitle: "Diff",
LcSwapDiff: "reverse diff direction",
LcOpenDiffingMenu: "Diff 메뉴 열기",
// the actual view is the extras view which I intend to give more tabs in future but for now we'll only mention the command log part
LcOpenExtrasMenu: "명령어 로그 메뉴 열기",
LcShowingGitDiff: "showing output for:",

View File

@ -212,7 +212,7 @@ func polishTranslationSet() TranslationSet {
LcStashOptions: "Opcje schowka",
NotARepository: "Błąd: nie jesteś w repozytorium",
LcJump: "przeskocz do panelu",
ExitLineByLineMode: `wyście z trybu "linia po linii"`,
ExitCustomPatchBuilder: `wyście z trybu "linia po linii"`,
EnterUpstream: `Podaj gałąź nadrzędną jako '<remote> <branchname>'`,
ReturnToRemotesList: `wróć do listy repozytoriów zdalnych`,
IgnoreTracked: "Ignoruj plik śledzony",

View File

@ -197,7 +197,7 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
if err := cmd.Wait(); err != nil {
// it's fine if we've killed this program ourselves
if !strings.Contains(err.Error(), "signal: killed") {
self.Log.Error(err)
self.Log.Errorf("Unexpected error when running cmd task: %v", err)
}
}

Some files were not shown because too many files have changed in this diff Show More