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. 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 ```yaml
os: 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 ## List Panel Navigation
<pre> <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>H</kbd>: scroll left
<kbd>L</kbd>: scroll right <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>: next tab
<kbd>[</kbd>: previous tab <kbd>[</kbd>: previous tab
</pre> </pre>
@ -163,36 +163,33 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Main Panel (Patch Building) ## Main Panel (Patch Building)
<pre> <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 previous hunk
<kbd></kbd>: select next 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>V</kbd>: toggle drag select <kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk <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> </pre>
## Main Panel (Staging) ## Main Panel (Staging)
<pre> <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 previous hunk
<kbd></kbd>: select next 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>V</kbd>: toggle drag select <kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk <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 <kbd>E</kbd>: edit hunk
</pre> </pre>

View File

@ -30,13 +30,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 一覧パネルの操作 ## 一覧パネルの操作
<pre> <pre>
<kbd>.</kbd>: 次のページ
<kbd>,</kbd>: 前のページ
<kbd><</kbd>: 最上部までスクロール
<kbd>></kbd>: 最下部までスクロール
<kbd>/</kbd>: 検索を開始
<kbd>H</kbd>: 左スクロール <kbd>H</kbd>: 左スクロール
<kbd>L</kbd>: 右スクロール <kbd>L</kbd>: 右スクロール
<kbd>,</kbd>: 前のページ
<kbd>.</kbd>: 次のページ
<kbd><</kbd>: 最上部までスクロール
<kbd>/</kbd>: 検索を開始
<kbd>></kbd>: 最下部までスクロール
<kbd>]</kbd>: 次のタブ <kbd>]</kbd>: 次のタブ
<kbd>[</kbd>: 前のタブ <kbd>[</kbd>: 前のタブ
</pre> </pre>
@ -222,36 +222,33 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## メインパネル (Patch Building) ## メインパネル (Patch Building)
<pre> <pre>
<kbd>esc</kbd>: line-by-lineモードを終了
<kbd>o</kbd>: ファイルを開く
<kbd></kbd>: 前の行を選択
<kbd></kbd>: 次の行を選択
<kbd></kbd>: 前のhunkを選択 <kbd></kbd>: 前のhunkを選択
<kbd></kbd>: 次のhunkを選択 <kbd></kbd>: 次のhunkを選択
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>space</kbd>: 行をパッチに追加/削除
<kbd>v</kbd>: 範囲選択を切り替え <kbd>v</kbd>: 範囲選択を切り替え
<kbd>V</kbd>: 範囲選択を切り替え <kbd>V</kbd>: 範囲選択を切り替え
<kbd>a</kbd>: hunk選択を切り替え <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> </pre>
## メインパネル (Staging) ## メインパネル (Staging)
<pre> <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></kbd>: 次のhunkを選択 <kbd></kbd>: 次のhunkを選択
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>e</kbd>: ファイルを編集
<kbd>v</kbd>: 範囲選択を切り替え <kbd>v</kbd>: 範囲選択を切り替え
<kbd>V</kbd>: 範囲選択を切り替え <kbd>V</kbd>: 範囲選択を切り替え
<kbd>a</kbd>: hunk選択を切り替え <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 <kbd>E</kbd>: edit hunk
</pre> </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 ## List Panel Navigation
<pre> <pre>
<kbd>.</kbd>: 다음 페이지
<kbd>,</kbd>: 이전 페이지
<kbd><</kbd>: 맨 위로 스크롤
<kbd>></kbd>: 맨 아래로 스크롤
<kbd>/</kbd>: 검색 시작
<kbd>H</kbd>: 우 스크롤 <kbd>H</kbd>: 우 스크롤
<kbd>L</kbd>: 좌 스크롤 <kbd>L</kbd>: 좌 스크롤
<kbd>,</kbd>: 이전 페이지
<kbd>.</kbd>: 다음 페이지
<kbd><</kbd>: 맨 위로 스크롤
<kbd>/</kbd>: 검색 시작
<kbd>></kbd>: 맨 아래로 스크롤
<kbd>]</kbd>: 이전 탭 <kbd>]</kbd>: 이전 탭
<kbd>[</kbd>: 다음 탭 <kbd>[</kbd>: 다음 탭
</pre> </pre>
@ -107,36 +107,33 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 메인 패널 (Patch Building) ## 메인 패널 (Patch Building)
<pre> <pre>
<kbd>esc</kbd>: line-by-line 모드 종료
<kbd>o</kbd>: 파일 닫기
<kbd></kbd>: 이전 줄 선택
<kbd></kbd>: 다음 줄 선택
<kbd></kbd>: 이전 hunk를 선택 <kbd></kbd>: 이전 hunk를 선택
<kbd></kbd>: 다음 hunk를 선택 <kbd></kbd>: 다음 hunk를 선택
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>space</kbd>: line(s)을 패치에 추가/삭제
<kbd>v</kbd>: 드래그 선택 전환 <kbd>v</kbd>: 드래그 선택 전환
<kbd>V</kbd>: 드래그 선택 전환 <kbd>V</kbd>: 드래그 선택 전환
<kbd>a</kbd>: toggle select hunk <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> </pre>
## 메인 패널 (Staging) ## 메인 패널 (Staging)
<pre> <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></kbd>: 다음 hunk를 선택 <kbd></kbd>: 다음 hunk를 선택
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>e</kbd>: 파일 편집
<kbd>v</kbd>: 드래그 선택 전환 <kbd>v</kbd>: 드래그 선택 전환
<kbd>V</kbd>: 드래그 선택 전환 <kbd>V</kbd>: 드래그 선택 전환
<kbd>a</kbd>: toggle select hunk <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 <kbd>E</kbd>: edit hunk
</pre> </pre>

View File

@ -30,13 +30,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Lijstpaneel Navigatie ## Lijstpaneel Navigatie
<pre> <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>H</kbd>: scroll left
<kbd>L</kbd>: scroll right <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>: volgende tabblad
<kbd>[</kbd>: vorige tabblad <kbd>[</kbd>: vorige tabblad
</pre> </pre>
@ -163,17 +163,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Patch Bouwen ## Patch Bouwen
<pre> <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 vorige hunk
<kbd></kbd>: selecteer de volgende 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>V</kbd>: toggle drag selecteer <kbd>V</kbd>: toggle drag selecteer
<kbd>a</kbd>: toggle selecteer hunk <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> </pre>
## Reflog ## Reflog
@ -217,20 +216,18 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Staging ## Staging
<pre> <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 vorige hunk
<kbd></kbd>: selecteer de volgende 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>V</kbd>: toggle drag selecteer <kbd>V</kbd>: toggle drag selecteer
<kbd>a</kbd>: toggle selecteer hunk <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 <kbd>E</kbd>: edit hunk
</pre> </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 ## List Panel Navigation
<pre> <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>H</kbd>: scroll left
<kbd>L</kbd>: scroll right <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>: next tab
<kbd>[</kbd>: previous tab <kbd>[</kbd>: previous tab
</pre> </pre>
@ -99,17 +99,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Main Panel (Patch Building) ## Main Panel (Patch Building)
<pre> <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>: poprzedni kawałek
<kbd></kbd>: następny 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>V</kbd>: toggle drag select <kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk <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> </pre>
## Pliki ## Pliki
@ -156,20 +155,18 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Poczekalnia ## Poczekalnia
<pre> <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>: poprzedni kawałek
<kbd></kbd>: następny 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>V</kbd>: toggle drag select <kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk <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 <kbd>E</kbd>: edit hunk
</pre> </pre>

View File

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

10
go.mod
View File

@ -17,7 +17,7 @@ require (
github.com/integrii/flaggy v1.4.0 github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4 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/kill v0.0.0-20220618033138-bfbe04675d10
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
github.com/jesseduffield/yaml v2.1.0+incompatible github.com/jesseduffield/yaml v2.1.0+incompatible
@ -42,7 +42,7 @@ require (
github.com/emirpasic/gods v1.12.0 // indirect github.com/emirpasic/gods v1.12.0 // indirect
github.com/fatih/color v1.9.0 // indirect github.com/fatih/color v1.9.0 // indirect
github.com/gdamore/encoding v1.0.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/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.0.0 // indirect github.com/go-git/go-billy/v5 v5.0.0 // indirect
github.com/go-logfmt/logfmt v0.5.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/mitchellh/go-homedir v1.1.0 // indirect
github.com/onsi/ginkgo v1.10.3 // indirect github.com/onsi/ginkgo v1.10.3 // indirect
github.com/onsi/gomega v1.7.1 // 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/sergi/go-diff v1.1.0 // indirect
github.com/xanzy/ssh-agent v0.2.1 // indirect github.com/xanzy/ssh-agent v0.2.1 // indirect
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 // indirect
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // 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 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= 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.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.2 h1:tKzG29kO9p2V++3oBY2W9zUjYu7IK1MENFeY/BzJSVY=
github.com/gdamore/tcell/v2 v2.5.1/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo= 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 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= 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/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 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/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.20220806032055-dfd3eb22e18a h1:k+lnojvZ6FoSzOIsSVVBlB9v3EZ+L5Qn/GS5PEzyTAA=
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/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 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo= 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= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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.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.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 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/samber/lo v1.10.1 h1:0D3h7i0U3hRAbaCeQ82DLe67n0A7Bbl0/cEoWqFGp+U= 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-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-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-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-20220804214406-8e32c043e418 h1:9vYwv7OjYaky/tlAeD7C4oC9EsPTlaFl1H2jS++V+ME=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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-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-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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.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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 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/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/integration" "github.com/jesseduffield/lazygit/pkg/integration"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
) )
@ -105,10 +104,9 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
"commits": tr.CommitsTitle, "commits": tr.CommitsTitle,
"confirmation": tr.ConfirmationTitle, "confirmation": tr.ConfirmationTitle,
"information": tr.InformationTitle, "information": tr.InformationTitle,
"main": tr.MainTitle, "main": tr.NormalTitle,
"patchBuilding": tr.PatchBuildingTitle, "patchBuilding": tr.PatchBuildingTitle,
"merging": tr.MergingTitle, "merging": tr.MergingTitle,
"normal": tr.NormalTitle,
"staging": tr.StagingTitle, "staging": tr.StagingTitle,
"menu": tr.MenuTitle, "menu": tr.MenuTitle,
"search": tr.SearchTitle, "search": tr.SearchTitle,
@ -127,12 +125,17 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
} }
func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*bindingSection { func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*bindingSection {
excludedViews := []string{"stagingSecondary", "patchBuildingSecondary"}
bindingsToDisplay := slices.Filter(bindings, func(binding *types.Binding) bool { 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 { bindingsByHeader := lo.GroupBy(bindingsToDisplay, func(binding *types.Binding) header {
return getHeaders(binding, tr) return getHeader(binding, tr)
}) })
bindingGroups := maps.MapToSlice( 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, func getHeader(binding *types.Binding, tr *i18n.TranslationSet) header {
// for example the copy-to-clipboard binding.
func getHeaders(binding *types.Binding, tr *i18n.TranslationSet) []header {
if binding.Tag == "navigation" { if binding.Tag == "navigation" {
return []header{{priority: 2, title: localisedTitle(tr, "navigation")}} return header{priority: 2, title: localisedTitle(tr, "navigation")}
} }
if binding.ViewName == "" { 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{priority: 1, title: localisedTitle(tr, binding.ViewName)}
return []header{}
}
return slices.Map(binding.Contexts, func(context string) header {
return header{priority: 1, title: localisedTitle(tr, context)}
})
} }
func formatSections(tr *i18n.TranslationSet, bindingSections []*bindingSection) string { func formatSections(tr *i18n.TranslationSet, bindingSections []*bindingSection) string {

View File

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

View File

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

View File

@ -190,7 +190,7 @@ func (p *PatchManager) RenderPatchForFile(filename string, plain bool, reverse b
parser := NewPatchParser(p.Log, patch) parser := NewPatchParser(p.Log, patch)
// not passing included lines because we don't want to see them in the secondary panel // 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 { 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 // 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 { contentToDisplay := slices.Some(p.PatchLines, func(line *PatchLine) bool {
return line.Content != "" 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 { 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) included := lo.Contains(incLineIndices, index)
return patchLine.render(selected, included) return patchLine.render(selected, included)
}) })
@ -202,12 +202,18 @@ func (p *PatchParser) Render(firstLineIndex int, lastLineIndex int, incLineIndic
return result return result
} }
// PlainRenderLines returns the non-coloured string of diff part from firstLineIndex to func (p *PatchParser) RenderPlain() string {
// lastLineIndex return renderLinesPlain(p.PatchLines)
func (p *PatchParser) PlainRenderLines(firstLineIndex, lastLineIndex int) string { }
linesToCopy := p.PatchLines[firstLineIndex : lastLineIndex+1]
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 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{ return []*boxlayout.Box{
{ {
Window: main, Window: "main",
Weight: 1, Weight: 1,
}, },
{ {
Window: secondary, Window: "secondary",
Weight: 1, Weight: 1,
}, },
} }

View File

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

View File

@ -12,10 +12,10 @@ import (
) )
// our UI command log looks like this: // our UI command log looks like this:
// Stage File // Stage File:
// git add -- 'filename' // git add -- 'filename'
// Unstage File // Unstage File:
// git reset HEAD 'filename' // git reset HEAD 'filename'
// //
// The 'Stage File' and 'Unstage File' lines are actions i.e they group up a set // 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). // 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 ( import (
"github.com/jesseduffield/lazygit/pkg/gui/controllers" "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 { func (gui *Gui) commitFilesRenderToMain() error {
node := gui.State.Contexts.CommitFiles.GetSelected() node := gui.State.Contexts.CommitFiles.GetSelected()
if node == nil { if node == nil {
@ -23,16 +18,16 @@ func (gui *Gui) commitFilesRenderToMain() error {
cmdObj := gui.git.WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false) cmdObj := gui.git.WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false)
task := NewRunPtyTask(cmdObj.GetCmd()) task := NewRunPtyTask(cmdObj.GetCmd())
mainContext := gui.State.Contexts.Normal pair := gui.normalMainContextPair()
if node.File != nil { if node.File != nil {
mainContext = gui.State.Contexts.PatchBuilding pair = gui.patchBuildingMainContextPair()
} }
return gui.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{
pair: pair,
main: &viewUpdateOpts{ main: &viewUpdateOpts{
title: "Patch", title: gui.Tr.Patch,
task: task, task: task,
context: mainContext,
}, },
secondary: gui.secondaryPatchPanelUpdateOpts(), 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.SetParentContext(opts.Context)
gui.State.Contexts.CommitFiles.SetWindowName(opts.Context.GetWindowName()) 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 err
} }
return gui.c.PushContext(gui.State.Contexts.CommitFiles) 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 return nil
} }
@ -41,6 +39,7 @@ func (gui *Gui) branchCommitsRenderToMain() error {
} }
return gui.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{ main: &viewUpdateOpts{
title: "Patch", title: "Patch",
task: task, 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 { func (gui *Gui) refForLog() string {
bisectInfo := gui.git.Bisect.GetInfo() bisectInfo := gui.git.Bisect.GetInfo()
gui.State.Model.BisectInfo = bisectInfo gui.State.Model.BisectInfo = bisectInfo

View File

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

View File

@ -1,8 +1,6 @@
package gui package gui
import ( import (
"errors"
"fmt"
"sort" "sort"
"strings" "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 // use replaceContext when you don't want to return to the original context upon
// hitting escape: you want to go that context's parent instead. // hitting escape: you want to go that context's parent instead.
func (gui *Gui) replaceContext(c types.Context) error { 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() 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 { 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")
}
if !c.IsFocusable() { if !c.IsFocusable() {
return nil 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() gui.State.ContextManager.Lock()
defer gui.State.ContextManager.Unlock()
if len(gui.State.ContextManager.ContextStack) == 0 { if len(gui.State.ContextManager.ContextStack) == 0 {
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c) gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
} else if c.GetKind() == types.SIDE_CONTEXT { } else if c.GetKind() == types.SIDE_CONTEXT {
// if we are switching to a side context, remove all other contexts in the stack // if we are switching to a side context, remove all other contexts in the stack
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 { for _, stackContext := range gui.State.ContextManager.ContextStack {
if stackContext.GetKey() != c.GetKey() { if stackContext.GetKind() == types.MAIN_CONTEXT {
if err := gui.deactivateContext(stackContext); err != nil { contextsToDeactivate = append(contextsToDeactivate, stackContext)
gui.State.ContextManager.Unlock()
return err
}
} }
} }
gui.State.ContextManager.ContextStack = []types.Context{c} 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 // 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. // 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. // 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 (topContext.GetKind() == types.TEMPORARY_POPUP && c.GetKey() != context.SEARCH_CONTEXT_KEY) ||
if err := gui.deactivateContext(topContext); err != nil { // we only ever want one main context on the stack at a time.
gui.State.ContextManager.Unlock() (topContext.GetKind() == types.MAIN_CONTEXT && c.GetKind() == types.MAIN_CONTEXT) {
return err
}
contextsToDeactivate = append(contextsToDeactivate, topContext)
_, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack) _, 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 contextsToDeactivate
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))
} }
func (gui *Gui) popContext() error { 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] newContext := gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
gui.g.SetCurrentContext(string(newContext.GetKey()))
gui.State.ContextManager.Unlock() 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 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()) view, _ := gui.g.View(c.GetViewName())
if view != nil && view.IsSearching() { if view != nil && view.IsSearching() {
@ -167,7 +147,7 @@ func (gui *Gui) deactivateContext(c types.Context) error {
view.Visible = false view.Visible = false
} }
if err := c.HandleFocusLost(); err != nil { if err := c.HandleFocusLost(opts); err != nil {
return err 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 set to another context we do nothing.
// if the context's view is the current view we trigger a focus; re-selecting the current item. // if the context's view is the current view we trigger a focus; re-selecting the current item.
func (gui *Gui) postRefreshUpdate(c types.Context) error { 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 { if err := c.HandleRender(); err != nil {
return err return err
} }
if gui.currentViewName() == c.GetViewName() { if gui.currentViewName() == c.GetViewName() {
if err := c.HandleFocus(); err != nil { if err := c.HandleFocus(types.OnFocusOpts{}); err != nil {
return err return err
} }
} }
@ -195,29 +171,16 @@ func (gui *Gui) postRefreshUpdate(c types.Context) error {
return nil 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() viewName := c.GetViewName()
v, err := gui.g.View(viewName) v, err := gui.g.View(viewName)
if err != nil { if err != nil {
return err return err
} }
originalViewContext := gui.State.ViewContextMap.Get(viewName)
var originalViewContextKey types.ContextKey = ""
if originalViewContext != nil {
originalViewContextKey = originalViewContext.GetKey()
}
gui.setWindowContext(c) gui.setWindowContext(c)
gui.setViewTabForContext(c)
if viewName == "main" { gui.moveToTopOfWindow(c)
gui.changeMainViewsContext(c)
} else {
gui.changeMainViewsContext(gui.State.Contexts.Normal)
}
gui.g.SetCurrentContext(string(c.GetKey()))
if _, err := gui.g.SetCurrentView(viewName); err != nil { if _, err := gui.g.SetCurrentView(viewName); err != nil {
return err return err
} }
@ -229,15 +192,6 @@ func (gui *Gui) activateContext(c types.Context, opts ...types.OnFocusOpts) erro
v.Visible = true 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 gui.g.Cursor = v.Editable
// render the options available for the current context at the bottom of the screen // 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) gui.renderOptionsMap(optionsMap)
if err := c.HandleFocus(opts...); err != nil { if err := c.HandleFocus(opts); err != nil {
return err return err
} }
@ -266,16 +220,6 @@ func (gui *Gui) renderOptionsMap(optionsMap map[string]string) {
_ = gui.renderString(gui.Views.Options, gui.optionsMapToString(optionsMap)) _ = 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 // // currently unused
// func (gui *Gui) renderContextStack() string { // func (gui *Gui) renderContextStack() string {
// result := "" // result := ""
@ -339,6 +283,10 @@ func (gui *Gui) currentStaticContext() types.Context {
gui.State.ContextManager.RLock() gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock() defer gui.State.ContextManager.RUnlock()
return gui.currentStaticContextWithoutLock()
}
func (gui *Gui) currentStaticContextWithoutLock() types.Context {
stack := gui.State.ContextManager.ContextStack stack := gui.State.ContextManager.ContextStack
if len(stack) == 0 { 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 { 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 { func (gui *Gui) getSideContextSelectedItemId() string {
@ -462,11 +367,6 @@ func (gui *Gui) getSideContextSelectedItemId() string {
return currentSideContext.GetSelectedItemId() 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 // currently unused
// func (gui *Gui) getCurrentSideView() *gocui.View { // func (gui *Gui) getCurrentSideView() *gocui.View {
// currentSideContext := gui.currentSideContext() // currentSideContext := gui.currentSideContext()

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,6 @@ type ListContextTrait struct {
c *types.HelperCommon c *types.HelperCommon
list types.IList list types.IList
viewTrait *ViewTrait
getDisplayStrings func(startIdx int, length int) [][]string getDisplayStrings func(startIdx int, length int) [][]string
} }
@ -20,43 +19,39 @@ func (self *ListContextTrait) GetList() types.IList {
return self.list return self.list
} }
func (self *ListContextTrait) GetViewTrait() types.IViewTrait {
return self.viewTrait
}
func (self *ListContextTrait) FocusLine() { func (self *ListContextTrait) FocusLine() {
// we need a way of knowing whether we've rendered to the view yet. // 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() self.setFooter()
} }
func (self *ListContextTrait) 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 { func formatListFooter(selectedLineIdx int, length int) string {
return fmt.Sprintf("%d of %d", selectedLineIdx+1, length) return fmt.Sprintf("%d of %d", selectedLineIdx+1, length)
} }
func (self *ListContextTrait) HandleFocus(opts ...types.OnFocusOpts) error { func (self *ListContextTrait) HandleFocus(opts types.OnFocusOpts) error {
self.FocusLine() 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 { func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) error {
self.viewTrait.SetOriginX(0) 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 // 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 { func (self *ListContextTrait) HandleRender() error {
self.list.RefreshSelectedIdx() self.list.RefreshSelectedIdx()
content := utils.RenderDisplayStrings(self.getDisplayStrings(0, self.list.Len())) content := utils.RenderDisplayStrings(self.getDisplayStrings(0, self.list.Len()))
self.viewTrait.SetContent(content) self.GetViewTrait().SetContent(content)
self.c.Render() self.c.Render()
self.setFooter() self.setFooter()
@ -65,5 +60,5 @@ func (self *ListContextTrait) HandleRender() error {
func (self *ListContextTrait) OnSearchSelect(selectedLineIdx int) error { func (self *ListContextTrait) OnSearchSelect(selectedLineIdx int) error {
self.GetList().SetSelectedLineIdx(selectedLineIdx) self.GetList().SetSelectedLineIdx(selectedLineIdx)
return self.HandleFocus() return self.HandleFocus(types.OnFocusOpts{})
} }

View File

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

View File

@ -24,7 +24,7 @@ func NewMenuContext(
) *MenuContext { ) *MenuContext {
viewModel := NewMenuViewModel() viewModel := NewMenuViewModel()
onFocus := func(...types.OnFocusOpts) error { onFocus := func(types.OnFocusOpts) error {
selectedMenuItem := viewModel.GetSelected() selectedMenuItem := viewModel.GetSelected()
renderToDescriptionView(selectedMenuItem.Tooltip) renderToDescriptionView(selectedMenuItem.Tooltip)
return nil return nil
@ -34,17 +34,18 @@ func NewMenuContext(
MenuViewModel: viewModel, MenuViewModel: viewModel,
ListContextTrait: &ListContextTrait{ ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "menu", View: view,
Key: "menu", WindowName: "menu",
Kind: types.TEMPORARY_POPUP, Key: "menu",
OnGetOptionsMap: getOptionsMap, Kind: types.TEMPORARY_POPUP,
Focusable: true, OnGetOptionsMap: getOptionsMap,
Focusable: true,
HasUncontrolledBounds: true,
}), ContextCallbackOpts{ }), ContextCallbackOpts{
OnFocus: onFocus, OnFocus: onFocus,
}), }),
getDisplayStrings: viewModel.GetDisplayStrings, getDisplayStrings: viewModel.GetDisplayStrings,
list: viewModel, list: viewModel,
viewTrait: NewViewTrait(view),
c: c, 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, view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string, getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error, onFocus func(types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error, onRenderToMain func() error,
onFocusLost func() error, onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon, c *types.HelperCommon,
) *ReflogCommitsContext { ) *ReflogCommitsContext {
@ -30,7 +30,7 @@ func NewReflogCommitsContext(
BasicViewModel: viewModel, BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{ ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "commits", View: view,
WindowName: "commits", WindowName: "commits",
Key: REFLOG_COMMITS_CONTEXT_KEY, Key: REFLOG_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT, Kind: types.SIDE_CONTEXT,
@ -41,7 +41,6 @@ func NewReflogCommitsContext(
OnRenderToMain: onRenderToMain, OnRenderToMain: onRenderToMain,
}), }),
list: viewModel, list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings, getDisplayStrings: getDisplayStrings,
c: c, c: c,
}, },

View File

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

View File

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

View File

@ -1,25 +1,25 @@
package context package context
import ( import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
) )
type SimpleContext struct { type SimpleContext struct {
OnFocus func(opts ...types.OnFocusOpts) error OnFocus func(opts types.OnFocusOpts) error
OnFocusLost func() error OnFocusLost func(opts types.OnFocusLostOpts) error
OnRender func() error OnRender func() error
// this is for pushing some content to the main view // this is for pushing some content to the main view
OnRenderToMain func(opts ...types.OnFocusOpts) error OnRenderToMain func() error
*BaseContext *BaseContext
} }
type ContextCallbackOpts struct { type ContextCallbackOpts struct {
OnFocus func(opts ...types.OnFocusOpts) error OnFocus func(opts types.OnFocusOpts) error
OnFocusLost func() error OnFocusLost func(opts types.OnFocusLostOpts) error
OnRender func() error OnRender func() error
// this is for pushing some content to the main view OnRenderToMain func() error
OnRenderToMain func(opts ...types.OnFocusOpts) error
} }
func NewSimpleContext(baseContext *BaseContext, opts ContextCallbackOpts) *SimpleContext { func NewSimpleContext(baseContext *BaseContext, opts ContextCallbackOpts) *SimpleContext {
@ -34,15 +34,30 @@ func NewSimpleContext(baseContext *BaseContext, opts ContextCallbackOpts) *Simpl
var _ types.Context = &SimpleContext{} 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 self.OnFocus != nil {
if err := self.OnFocus(opts...); err != nil { if err := self.OnFocus(opts); err != nil {
return err return err
} }
} }
if self.OnRenderToMain != nil { if self.OnRenderToMain != nil {
if err := self.OnRenderToMain(opts...); err != nil { if err := self.OnRenderToMain(); err != nil {
return err return err
} }
} }
@ -50,9 +65,9 @@ func (self *SimpleContext) HandleFocus(opts ...types.OnFocusOpts) error {
return nil return nil
} }
func (self *SimpleContext) HandleFocusLost() error { func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) error {
if self.OnFocusLost != nil { if self.OnFocusLost != nil {
return self.OnFocusLost() return self.OnFocusLost(opts)
} }
return nil return nil
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -61,12 +61,12 @@ func (self *ViewTrait) horizontalScrollAmount() int {
return self.view.InnerWidth() / HORIZONTAL_SCROLL_FACTOR return self.view.InnerWidth() / HORIZONTAL_SCROLL_FACTOR
} }
func (self *ViewTrait) ScrollUp() { func (self *ViewTrait) ScrollUp(value int) {
self.view.ScrollUp(1) self.view.ScrollUp(value)
} }
func (self *ViewTrait) ScrollDown() { func (self *ViewTrait) ScrollDown(value int) {
self.view.ScrollDown(1) self.view.ScrollDown(value)
} }
// this returns the amount we'll scroll if we want to scroll by a page. // 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, view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string, getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(...types.OnFocusOpts) error, onFocus func(types.OnFocusOpts) error,
onRenderToMain func(...types.OnFocusOpts) error, onRenderToMain func() error,
onFocusLost func() error, onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon, c *types.HelperCommon,
) *WorkingTreeContext { ) *WorkingTreeContext {
@ -31,7 +31,7 @@ func NewWorkingTreeContext(
FileTreeViewModel: viewModel, FileTreeViewModel: viewModel,
ListContextTrait: &ListContextTrait{ ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
ViewName: "files", View: view,
WindowName: "files", WindowName: "files",
Key: FILES_CONTEXT_KEY, Key: FILES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT, Kind: types.SIDE_CONTEXT,
@ -42,7 +42,6 @@ func NewWorkingTreeContext(
OnRenderToMain: onRenderToMain, OnRenderToMain: onRenderToMain,
}), }),
list: viewModel, list: viewModel,
viewTrait: NewViewTrait(view),
getDisplayStrings: getDisplayStrings, getDisplayStrings: getDisplayStrings,
c: c, c: c,
}, },

View File

@ -9,26 +9,27 @@ func (gui *Gui) contextTree() *context.ContextTree {
return &context.ContextTree{ return &context.ContextTree{
Global: context.NewSimpleContext( Global: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{ context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.GLOBAL_CONTEXT, Kind: types.GLOBAL_CONTEXT,
ViewName: "", View: nil,
WindowName: "", WindowName: "",
Key: context.GLOBAL_CONTEXT_KEY, Key: context.GLOBAL_CONTEXT_KEY,
Focusable: false, Focusable: false,
HasUncontrolledBounds: true, // setting to true because the global context doesn't even have a view
}), }),
context.ContextCallbackOpts{ context.ContextCallbackOpts{
OnRenderToMain: OnFocusWrapper(gui.statusRenderToMain), OnRenderToMain: gui.statusRenderToMain,
}, },
), ),
Status: context.NewSimpleContext( Status: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{ context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.SIDE_CONTEXT, Kind: types.SIDE_CONTEXT,
ViewName: "status", View: gui.Views.Status,
WindowName: "status", WindowName: "status",
Key: context.STATUS_CONTEXT_KEY, Key: context.STATUS_CONTEXT_KEY,
Focusable: true, Focusable: true,
}), }),
context.ContextCallbackOpts{ context.ContextCallbackOpts{
OnRenderToMain: OnFocusWrapper(gui.statusRenderToMain), OnRenderToMain: gui.statusRenderToMain,
}, },
), ),
Files: gui.filesListContext(), Files: gui.filesListContext(),
@ -47,84 +48,150 @@ func (gui *Gui) contextTree() *context.ContextTree {
Normal: context.NewSimpleContext( Normal: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{ context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.MAIN_CONTEXT, Kind: types.MAIN_CONTEXT,
ViewName: "main", View: gui.Views.Main,
WindowName: "main", WindowName: "main",
Key: context.MAIN_NORMAL_CONTEXT_KEY, Key: context.NORMAL_MAIN_CONTEXT_KEY,
Focusable: false, Focusable: false,
}), }),
context.ContextCallbackOpts{ 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 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{ context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.MAIN_CONTEXT, Kind: types.MAIN_CONTEXT,
ViewName: "main", View: gui.Views.Secondary,
WindowName: "main", WindowName: "secondary",
Key: context.MAIN_STAGING_CONTEXT_KEY, Key: context.NORMAL_SECONDARY_CONTEXT_KEY,
Focusable: true, Focusable: false,
}), }),
context.ContextCallbackOpts{ 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)
},
},
), ),
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{ context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.MAIN_CONTEXT, Kind: types.MAIN_CONTEXT,
ViewName: "main", View: gui.Views.PatchBuildingSecondary,
WindowName: "main", WindowName: "secondary",
Key: context.MAIN_PATCH_BUILDING_CONTEXT_KEY, Key: context.PATCH_BUILDING_SECONDARY_CONTEXT_KEY,
Focusable: true, Focusable: false,
}), }),
context.ContextCallbackOpts{ 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)
},
},
), ),
Merging: context.NewSimpleContext( Merging: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{ context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.MAIN_CONTEXT, Kind: types.MAIN_CONTEXT,
ViewName: "main", View: gui.Views.Merging,
WindowName: "main", WindowName: "main",
Key: context.MAIN_MERGING_CONTEXT_KEY, Key: context.MERGING_MAIN_CONTEXT_KEY,
OnGetOptionsMap: gui.getMergingOptions, OnGetOptionsMap: gui.getMergingOptions,
Focusable: true, Focusable: true,
}), }),
context.ContextCallbackOpts{ 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( Confirmation: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{ context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.TEMPORARY_POPUP, Kind: types.TEMPORARY_POPUP,
ViewName: "confirmation", View: gui.Views.Confirmation,
WindowName: "confirmation", WindowName: "confirmation",
Key: context.CONFIRMATION_CONTEXT_KEY, Key: context.CONFIRMATION_CONTEXT_KEY,
Focusable: true, Focusable: true,
HasUncontrolledBounds: true,
}), }),
context.ContextCallbackOpts{ context.ContextCallbackOpts{
OnFocus: OnFocusWrapper(gui.handleAskFocused), OnFocus: OnFocusWrapper(gui.handleAskFocused),
OnFocusLost: func() error { OnFocusLost: func(types.OnFocusLostOpts) error {
gui.deactivateConfirmationPrompt() gui.deactivateConfirmationPrompt()
return nil return nil
}, },
@ -132,11 +199,12 @@ func (gui *Gui) contextTree() *context.ContextTree {
), ),
CommitMessage: context.NewSimpleContext( CommitMessage: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{ context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP, Kind: types.PERSISTENT_POPUP,
ViewName: "commitMessage", View: gui.Views.CommitMessage,
WindowName: "commitMessage", WindowName: "commitMessage",
Key: context.COMMIT_MESSAGE_CONTEXT_KEY, Key: context.COMMIT_MESSAGE_CONTEXT_KEY,
Focusable: true, Focusable: true,
HasUncontrolledBounds: true,
}), }),
context.ContextCallbackOpts{ context.ContextCallbackOpts{
OnFocus: OnFocusWrapper(gui.handleCommitMessageFocused), OnFocus: OnFocusWrapper(gui.handleCommitMessageFocused),
@ -145,7 +213,7 @@ func (gui *Gui) contextTree() *context.ContextTree {
Search: context.NewSimpleContext( Search: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{ context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP, Kind: types.PERSISTENT_POPUP,
ViewName: "search", View: gui.Views.Search,
WindowName: "search", WindowName: "search",
Key: context.SEARCH_CONTEXT_KEY, Key: context.SEARCH_CONTEXT_KEY,
Focusable: true, Focusable: true,
@ -155,26 +223,39 @@ func (gui *Gui) contextTree() *context.ContextTree {
CommandLog: context.NewSimpleContext( CommandLog: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{ context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.EXTRAS_CONTEXT, Kind: types.EXTRAS_CONTEXT,
ViewName: "extras", View: gui.Views.Extras,
WindowName: "extras", WindowName: "extras",
Key: context.COMMAND_LOG_CONTEXT_KEY, Key: context.COMMAND_LOG_CONTEXT_KEY,
OnGetOptionsMap: gui.getMergingOptions, OnGetOptionsMap: gui.getMergingOptions,
Focusable: true, Focusable: true,
}), }),
context.ContextCallbackOpts{ context.ContextCallbackOpts{
OnFocusLost: func() error { OnFocusLost: func(opts types.OnFocusLostOpts) error {
gui.Views.Extras.Autoscroll = true gui.Views.Extras.Autoscroll = true
return nil 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 // using this wrapper for when an onFocus function doesn't care about any potential
// props that could be passed // props that could be passed
func OnFocusWrapper(f func() error) func(opts ...types.OnFocusOpts) error { func OnFocusWrapper(f func() error) func(opts types.OnFocusOpts) error {
return func(opts ...types.OnFocusOpts) error { return func(opts types.OnFocusOpts) error {
return f() 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{ gui.helpers = &helpers.Helpers{
Refs: refsHelper, Refs: refsHelper,
Host: helpers.NewHostHelper(helperCommon, gui.git), 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), Bisect: helpers.NewBisectHelper(helperCommon, gui.git),
Suggestions: suggestionsHelper, Suggestions: suggestionsHelper,
Files: helpers.NewFilesHelper(helperCommon, gui.git, osCommand), Files: helpers.NewFilesHelper(helperCommon, gui.git, osCommand),
@ -120,11 +120,18 @@ func (gui *Gui) resetControllers() {
) )
undoController := controllers.NewUndoController(common) undoController := controllers.NewUndoController(common)
globalController := controllers.NewGlobalController(common) globalController := controllers.NewGlobalController(common)
contextLinesController := controllers.NewContextLinesController(common)
verticalScrollControllerFactory := controllers.NewVerticalScrollControllerFactory(common)
branchesController := controllers.NewBranchesController(common) branchesController := controllers.NewBranchesController(common)
gitFlowController := controllers.NewGitFlowController(common) gitFlowController := controllers.NewGitFlowController(common)
filesRemoveController := controllers.NewFilesRemoveController(common) filesRemoveController := controllers.NewFilesRemoveController(common)
stashController := controllers.NewStashController(common) stashController := controllers.NewStashController(common)
commitFilesController := controllers.NewCommitFilesController(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 } 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(context, controllers.NewBasicCommitsController(common, context))
} }
controllers.AttachControllers(gui.State.Contexts.Files, filesController, filesRemoveController) // 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.Tags, tagsController) controllers.AttachControllers(gui.State.Contexts.Staging,
controllers.AttachControllers(gui.State.Contexts.Submodules, submodulesController) stagingController,
controllers.AttachControllers(gui.State.Contexts.LocalCommits, localCommitsController, bisectController) patchExplorerControllerFactory.Create(gui.State.Contexts.Staging),
controllers.AttachControllers(gui.State.Contexts.Branches, branchesController, gitFlowController) verticalScrollControllerFactory.Create(gui.State.Contexts.Staging),
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.StagingSecondary,
controllers.AttachControllers(gui.State.Contexts.Stash, stashController) stagingSecondaryController,
controllers.AttachControllers(gui.State.Contexts.Menu, menuController) patchExplorerControllerFactory.Create(gui.State.Contexts.StagingSecondary),
controllers.AttachControllers(gui.State.Contexts.CommitMessage, commitMessageController) verticalScrollControllerFactory.Create(gui.State.Contexts.StagingSecondary),
controllers.AttachControllers(gui.State.Contexts.RemoteBranches, remoteBranchesController) )
controllers.AttachControllers(gui.State.Contexts.Global, syncController, undoController, globalController)
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 // this must come last so that we've got our click handlers defined against the context
listControllerFactory := controllers.NewListControllerFactory(gui.c) listControllerFactory := controllers.NewListControllerFactory(gui.c)

View File

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

View File

@ -75,10 +75,10 @@ func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []
func (self *CommitFilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding { func (self *CommitFilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{ return []*gocui.ViewMouseBinding{
{ {
ViewName: "main", ViewName: "patchBuilding",
Key: gocui.MouseLeft, Key: gocui.MouseLeft,
Handler: self.onClickMain, 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 { if node == nil {
return 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 { 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 { 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 { 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() { 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", ViewName: "main",
Key: gocui.MouseLeft, Key: gocui.MouseLeft,
Handler: self.onClickMain, 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", ViewName: "secondary",
Key: gocui.MouseLeft, Key: gocui.MouseLeft,
Handler: self.onClickSecondary, 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 err
} }
return self.context().HandleFocus() return self.context().HandleFocus(types.OnFocusOpts{})
} }
func (self *FilesController) checkSelectedFileNode(callback func(*filetree.FileNode) error) func() error { 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 { 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 { func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
@ -389,7 +407,7 @@ func (self *FilesController) toggleStagedAll() error {
return err return err
} }
return self.context().HandleFocus() return self.context().HandleFocus(types.OnFocusOpts{})
} }
func (self *FilesController) toggleStagedAllWithLock() error { 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 { 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 { 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 { func (self *FilesController) fetch() error {

View File

@ -3,6 +3,7 @@ package helpers
import ( import (
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
) )
@ -11,17 +12,20 @@ type IPatchBuildingHelper interface {
} }
type PatchBuildingHelper struct { type PatchBuildingHelper struct {
c *types.HelperCommon c *types.HelperCommon
git *commands.GitCommand git *commands.GitCommand
contexts *context.ContextTree
} }
func NewPatchBuildingHelper( func NewPatchBuildingHelper(
c *types.HelperCommon, c *types.HelperCommon,
git *commands.GitCommand, git *commands.GitCommand,
contexts *context.ContextTree,
) *PatchBuildingHelper { ) *PatchBuildingHelper {
return &PatchBuildingHelper{ return &PatchBuildingHelper{
c: c, c: c,
git: git, git: git,
contexts: contexts,
} }
} }
@ -31,3 +35,28 @@ func (self *PatchBuildingHelper) ValidateNormalWorkingTreeState() (bool, error)
} }
return true, nil 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 { 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 // 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 // at the moment much logic depends on the selected line always being visible
if !self.isSelectedLineInViewPort() { if !self.isSelectedLineInViewPort() {
return self.handleLineChange(-1) return self.handleLineChange(-scrollHeight)
} }
return nil return nil
} }
func (self *ListController) HandleScrollDown() error { func (self *ListController) HandleScrollDown() error {
self.context.GetViewTrait().ScrollDown() scrollHeight := self.c.UserConfig.Gui.ScrollHeight
self.context.GetViewTrait().ScrollDown(scrollHeight)
if !self.isSelectedLineInViewPort() { if !self.isSelectedLineInViewPort() {
return self.handleLineChange(1) return self.handleLineChange(scrollHeight)
} }
return nil return nil
@ -81,7 +83,7 @@ func (self *ListController) isSelectedLineInViewPort() bool {
func (self *ListController) scrollHorizontal(scrollFunc func()) error { func (self *ListController) scrollHorizontal(scrollFunc func()) error {
scrollFunc() scrollFunc()
return self.context.HandleFocus() return self.context.HandleFocus(types.OnFocusOpts{})
} }
func (self *ListController) handleLineChange(change int) error { 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 // 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. // we're not constantly re-rendering the main view.
if before != after { if before != after {
return self.context.HandleFocus() return self.context.HandleFocus(types.OnFocusOpts{})
} }
return nil return nil
@ -136,7 +138,7 @@ func (self *ListController) HandleClick(opts gocui.ViewMouseBindingOpts) error {
if prevSelectedLineIdx == newSelectedLineIdx && alreadyFocused && self.context.GetOnClick() != nil { if prevSelectedLineIdx == newSelectedLineIdx && alreadyFocused && self.context.GetOnClick() != nil {
return self.context.GetOnClick()() return self.context.GetOnClick()()
} }
return self.context.HandleFocus() return self.context.HandleFocus(types.OnFocusOpts{})
} }
func (self *ListController) pushContextIfNotFocused() error { 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 { func (self *ListController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{ return []*gocui.ViewMouseBinding{
{ {
ViewName: self.context.GetViewName(), ViewName: self.context.GetViewName(),
ToContext: string(self.context.GetKey()), Key: gocui.MouseWheelUp,
Key: gocui.MouseWheelUp, Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollUp() },
Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollUp() },
}, },
{ {
ViewName: self.context.GetViewName(), ViewName: self.context.GetViewName(),
ToContext: string(self.context.GetKey()), Key: gocui.MouseLeft,
Key: gocui.MouseLeft, Handler: func(opts gocui.ViewMouseBindingOpts) error { return self.HandleClick(opts) },
Handler: func(opts gocui.ViewMouseBindingOpts) error { return self.HandleClick(opts) },
}, },
{ {
ViewName: self.context.GetViewName(), ViewName: self.context.GetViewName(),
ToContext: string(self.context.GetKey()), Key: gocui.MouseWheelDown,
Key: gocui.MouseWheelDown, Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollDown() },
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" "fmt"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
) )
@ -16,7 +15,7 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error {
menuItems := []*types.MenuItem{ menuItems := []*types.MenuItem{
{ {
Label: "reset patch", Label: "reset patch",
OnPress: gui.handleResetPatch, OnPress: gui.helpers.PatchBuilding.Reset,
Key: 'c', Key: 'c',
}, },
{ {
@ -89,9 +88,9 @@ func (gui *Gui) validateNormalWorkingTreeState() (bool, error) {
return true, nil return true, nil
} }
func (gui *Gui) returnFocusFromLineByLinePanelIfNecessary() error { func (gui *Gui) returnFocusFromPatchExplorerIfNecessary() error {
if gui.State.MainContext == context.MAIN_PATCH_BUILDING_CONTEXT_KEY { if gui.currentContext().GetKey() == gui.State.Contexts.CustomPatchBuilder.GetKey() {
return gui.handleEscapePatchBuildingPanel() return gui.helpers.PatchBuilding.Escape()
} }
return nil return nil
} }
@ -101,7 +100,7 @@ func (gui *Gui) handleDeletePatchFromCommit() error {
return err return err
} }
if err := gui.returnFocusFromLineByLinePanelIfNecessary(); err != nil { if err := gui.returnFocusFromPatchExplorerIfNecessary(); err != nil {
return err return err
} }
@ -118,7 +117,7 @@ func (gui *Gui) handleMovePatchToSelectedCommit() error {
return err return err
} }
if err := gui.returnFocusFromLineByLinePanelIfNecessary(); err != nil { if err := gui.returnFocusFromPatchExplorerIfNecessary(); err != nil {
return err return err
} }
@ -135,7 +134,7 @@ func (gui *Gui) handleMovePatchIntoWorkingTree() error {
return err return err
} }
if err := gui.returnFocusFromLineByLinePanelIfNecessary(); err != nil { if err := gui.returnFocusFromPatchExplorerIfNecessary(); err != nil {
return err return err
} }
@ -166,7 +165,7 @@ func (gui *Gui) handlePullPatchIntoNewCommit() error {
return err return err
} }
if err := gui.returnFocusFromLineByLinePanelIfNecessary(); err != nil { if err := gui.returnFocusFromPatchExplorerIfNecessary(); err != nil {
return err return err
} }
@ -179,7 +178,7 @@ func (gui *Gui) handlePullPatchIntoNewCommit() error {
} }
func (gui *Gui) handleApplyPatch(reverse bool) error { func (gui *Gui) handleApplyPatch(reverse bool) error {
if err := gui.returnFocusFromLineByLinePanelIfNecessary(); err != nil { if err := gui.returnFocusFromPatchExplorerIfNecessary(); err != nil {
return err return err
} }
@ -193,18 +192,3 @@ func (gui *Gui) handleApplyPatch(reverse bool) error {
} }
return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) 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()) task := NewRunPtyTask(cmdObj.GetCmd())
return gui.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{ main: &viewUpdateOpts{
title: "Diff", title: "Diff",
task: task, task: task,

View File

@ -34,8 +34,9 @@ func (gui *Gui) filesRenderToMain() error {
if node == nil { if node == nil {
return gui.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{ main: &viewUpdateOpts{
title: "", title: gui.c.Tr.DiffTitle,
task: NewRenderStringTask(gui.c.Tr.NoChangedFiles), task: NewRenderStringTask(gui.c.Tr.NoChangedFiles),
}, },
}) })
@ -53,31 +54,38 @@ func (gui *Gui) filesRenderToMain() error {
gui.resetMergeStateWithLock() gui.resetMergeStateWithLock()
mainContext := gui.State.Contexts.Normal pair := gui.normalMainContextPair()
if node.File != nil { if node.File != nil {
mainContext = gui.State.Contexts.Staging pair = gui.stagingMainContextPair()
} }
split := gui.c.UserConfig.Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges()) split := gui.c.UserConfig.Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges())
mainShowsStaged := !split && node.GetHasStagedChanges() mainShowsStaged := !split && node.GetHasStagedChanges()
cmdObj := gui.git.WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged, gui.IgnoreWhitespaceInDiffView) cmdObj := gui.git.WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged, gui.IgnoreWhitespaceInDiffView)
refreshOpts := refreshMainOpts{main: &viewUpdateOpts{ title := gui.c.Tr.UnstagedChanges
title: gui.c.Tr.UnstagedChanges,
task: NewRunPtyTask(cmdObj.GetCmd()),
context: mainContext,
}}
if mainShowsStaged { 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 { if split {
cmdObj := gui.git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.IgnoreWhitespaceInDiffView) 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{ refreshOpts.secondary = &viewUpdateOpts{
title: gui.c.Tr.StagedChanges, title: title,
task: NewRunPtyTask(cmdObj.GetCmd()), task: NewRunPtyTask(cmdObj.GetCmd()),
context: mainContext,
} }
} }
@ -89,6 +97,8 @@ func (gui *Gui) onFocusFile() error {
return nil return nil
} }
// test
func (gui *Gui) getSetTextareaTextFn(getView func() *gocui.View) func(string) { func (gui *Gui) getSetTextareaTextFn(getView func() *gocui.View) func(string) {
return func(text string) { return func(text string) {
// using a getView function so that we don't need to worry about when the view is created // 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() 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, // 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. // for example, will show authorship information in half and full screen mode.
func (gui *Gui) rerenderViewsWithScreenModeDependentContent() error { func (gui *Gui) rerenderViewsWithScreenModeDependentContent() error {
for _, view := range []*gocui.View{gui.Views.Branches, gui.Views.Commits} { // for now we re-render all list views.
if err := gui.rerenderView(view); err != nil { for _, context := range gui.getListContexts() {
if err := gui.rerenderView(context.GetView()); err != nil {
return err return err
} }
} }
@ -24,7 +25,6 @@ func (gui *Gui) rerenderViewsWithScreenModeDependentContent() error {
return nil return nil
} }
// TODO: GENERICS
func nextIntInCycle(sl []WindowMaximisation, current WindowMaximisation) WindowMaximisation { func nextIntInCycle(sl []WindowMaximisation, current WindowMaximisation) WindowMaximisation {
for i, val := range sl { for i, val := range sl {
if val == current { if val == current {
@ -37,7 +37,6 @@ func nextIntInCycle(sl []WindowMaximisation, current WindowMaximisation) WindowM
return sl[0] return sl[0]
} }
// TODO: GENERICS
func prevIntInCycle(sl []WindowMaximisation, current WindowMaximisation) WindowMaximisation { func prevIntInCycle(sl []WindowMaximisation, current WindowMaximisation) WindowMaximisation {
for i, val := range sl { for i, val := range sl {
if val == current { if val == current {
@ -80,7 +79,14 @@ func (gui *Gui) scrollUpMain() error {
gui.State.Panels.Merging.UserVerticalScrolling = true 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 return nil
} }
@ -90,19 +96,38 @@ func (gui *Gui) scrollDownMain() error {
gui.State.Panels.Merging.UserVerticalScrolling = true 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 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 { func (gui *Gui) scrollLeftMain() error {
gui.scrollLeft(gui.Views.Main) gui.scrollLeft(gui.mainView())
return nil return nil
} }
func (gui *Gui) scrollRightMain() error { func (gui *Gui) scrollRightMain() error {
gui.scrollRight(gui.Views.Main) gui.scrollRight(gui.mainView())
return nil return nil
} }
@ -117,13 +142,15 @@ func (gui *Gui) scrollRight(view *gocui.View) {
} }
func (gui *Gui) scrollUpSecondary() error { func (gui *Gui) scrollUpSecondary() error {
gui.scrollUpView(gui.Views.Secondary) gui.scrollUpView(gui.secondaryView())
return nil return nil
} }
func (gui *Gui) scrollDownSecondary() error { func (gui *Gui) scrollDownSecondary() error {
gui.scrollDownView(gui.Views.Secondary) secondaryView := gui.secondaryView()
gui.scrollDownView(secondaryView)
return nil return nil
} }

View File

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

View File

@ -1,6 +1,8 @@
package gui package gui
import ( import (
"errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/types" "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 { 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 { func (self *guiCommon) PopContext() error {
@ -50,6 +62,14 @@ func (self *guiCommon) CurrentContext() types.Context {
return self.gui.currentContext() 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 { func (self *guiCommon) GetAppState() *config.AppState {
return self.gui.Config.GetAppState() return self.gui.Config.GetAppState()
} }

View File

@ -7,7 +7,6 @@ import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/constants" "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/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings" "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types" "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. // only to be called from the cheatsheet generate script. This mutates the Gui struct.
func (self *Gui) GetCheatsheetKeybindings() []*types.Binding { 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.helpers = helpers.NewStubHelpers()
self.State = &GuiRepoState{} self.State = &GuiRepoState{}
self.State.Contexts = self.contextTree() self.State.Contexts = self.contextTree()
@ -173,7 +177,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
}, },
{ {
ViewName: "status", ViewName: "status",
Contexts: []string{string(context.STATUS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.Edit), Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.handleEditConfig, Handler: self.handleEditConfig,
Description: self.c.Tr.EditConfig, Description: self.c.Tr.EditConfig,
@ -192,70 +195,60 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
}, },
{ {
ViewName: "status", ViewName: "status",
Contexts: []string{string(context.STATUS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.OpenFile), Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.handleOpenConfig, Handler: self.handleOpenConfig,
Description: self.c.Tr.OpenConfig, Description: self.c.Tr.OpenConfig,
}, },
{ {
ViewName: "status", ViewName: "status",
Contexts: []string{string(context.STATUS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Status.CheckForUpdate), Key: opts.GetKey(opts.Config.Status.CheckForUpdate),
Handler: self.handleCheckForUpdate, Handler: self.handleCheckForUpdate,
Description: self.c.Tr.LcCheckForUpdate, Description: self.c.Tr.LcCheckForUpdate,
}, },
{ {
ViewName: "status", ViewName: "status",
Contexts: []string{string(context.STATUS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Status.RecentRepos), Key: opts.GetKey(opts.Config.Status.RecentRepos),
Handler: self.handleCreateRecentReposMenu, Handler: self.handleCreateRecentReposMenu,
Description: self.c.Tr.SwitchRepo, Description: self.c.Tr.SwitchRepo,
}, },
{ {
ViewName: "status", ViewName: "status",
Contexts: []string{string(context.STATUS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Status.AllBranchesLogGraph), Key: opts.GetKey(opts.Config.Status.AllBranchesLogGraph),
Handler: self.handleShowAllBranchLogs, Handler: self.handleShowAllBranchLogs,
Description: self.c.Tr.LcAllBranchesLogGraph, Description: self.c.Tr.LcAllBranchesLogGraph,
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{string(context.FILES_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard, Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.LcCopyFileNameToClipboard, Description: self.c.Tr.LcCopyFileNameToClipboard,
}, },
{ {
ViewName: "branches", ViewName: "localBranches",
Contexts: []string{string(context.LOCAL_BRANCHES_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard, Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.LcCopyBranchNameToClipboard, Description: self.c.Tr.LcCopyBranchNameToClipboard,
}, },
{ {
ViewName: "commits", ViewName: "commits",
Contexts: []string{string(context.LOCAL_COMMITS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard, Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.LcCopyCommitShaToClipboard, Description: self.c.Tr.LcCopyCommitShaToClipboard,
}, },
{ {
ViewName: "commits", ViewName: "commits",
Contexts: []string{string(context.LOCAL_COMMITS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Commits.ResetCherryPick), Key: opts.GetKey(opts.Config.Commits.ResetCherryPick),
Handler: self.helpers.CherryPick.Reset, Handler: self.helpers.CherryPick.Reset,
Description: self.c.Tr.LcResetCherryPick, Description: self.c.Tr.LcResetCherryPick,
}, },
{ {
ViewName: "commits", ViewName: "reflogCommits",
Contexts: []string{string(context.REFLOG_COMMITS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard, Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.LcCopyCommitShaToClipboard, Description: self.c.Tr.LcCopyCommitShaToClipboard,
}, },
{ {
ViewName: "subCommits", ViewName: "subCommits",
Contexts: []string{string(context.SUB_COMMITS_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard, Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.LcCopyCommitShaToClipboard, Description: self.c.Tr.LcCopyCommitShaToClipboard,
@ -268,7 +261,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
}, },
{ {
ViewName: "commitFiles", ViewName: "commitFiles",
Contexts: []string{string(context.COMMIT_FILES_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard, Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.LcCopyCommitFileNameToClipboard, Description: self.c.Tr.LcCopyCommitFileNameToClipboard,
@ -315,7 +307,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
}, },
{ {
ViewName: "main", ViewName: "main",
Contexts: []string{string(context.MAIN_NORMAL_CONTEXT_KEY)},
Key: gocui.MouseWheelDown, Key: gocui.MouseWheelDown,
Handler: self.scrollDownMain, Handler: self.scrollDownMain,
Description: self.c.Tr.ScrollDown, Description: self.c.Tr.ScrollDown,
@ -323,7 +314,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
}, },
{ {
ViewName: "main", ViewName: "main",
Contexts: []string{string(context.MAIN_NORMAL_CONTEXT_KEY)},
Key: gocui.MouseWheelUp, Key: gocui.MouseWheelUp,
Handler: self.scrollUpMain, Handler: self.scrollUpMain,
Description: self.c.Tr.ScrollUp, Description: self.c.Tr.ScrollUp,
@ -331,370 +321,110 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
}, },
{ {
ViewName: "secondary", 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, Key: gocui.MouseWheelUp,
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: self.scrollUpMain, Handler: self.scrollUpSecondary,
}, },
{ {
ViewName: "main", ViewName: "merging",
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)},
Key: opts.GetKey(opts.Config.Universal.ScrollLeft), Key: opts.GetKey(opts.Config.Universal.ScrollLeft),
Handler: self.scrollLeftMain, Handler: self.scrollLeftMain,
Description: self.c.Tr.LcScrollLeft, Description: self.c.Tr.LcScrollLeft,
Tag: "navigation", Tag: "navigation",
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_PATCH_BUILDING_CONTEXT_KEY), string(context.MAIN_STAGING_CONTEXT_KEY), string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.ScrollRight), Key: opts.GetKey(opts.Config.Universal.ScrollRight),
Handler: self.scrollRightMain, Handler: self.scrollRightMain,
Description: self.c.Tr.LcScrollRight, Description: self.c.Tr.LcScrollRight,
Tag: "navigation", Tag: "navigation",
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.Return), Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.handleEscapeMerge, Handler: self.handleEscapeMerge,
Description: self.c.Tr.ReturnToFilesPanel, Description: self.c.Tr.ReturnToFilesPanel,
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Files.OpenMergeTool), Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
Handler: self.helpers.WorkingTree.OpenMergeTool, Handler: self.helpers.WorkingTree.OpenMergeTool,
Description: self.c.Tr.LcOpenMergeTool, Description: self.c.Tr.LcOpenMergeTool,
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.Select), Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.handlePickHunk, Handler: self.handlePickHunk,
Description: self.c.Tr.PickHunk, Description: self.c.Tr.PickHunk,
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Main.PickBothHunks), Key: opts.GetKey(opts.Config.Main.PickBothHunks),
Handler: self.handlePickAllHunks, Handler: self.handlePickAllHunks,
Description: self.c.Tr.PickAllHunks, Description: self.c.Tr.PickAllHunks,
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.PrevBlock), Key: opts.GetKey(opts.Config.Universal.PrevBlock),
Handler: self.handleSelectPrevConflict, Handler: self.handleSelectPrevConflict,
Description: self.c.Tr.PrevConflict, Description: self.c.Tr.PrevConflict,
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.NextBlock), Key: opts.GetKey(opts.Config.Universal.NextBlock),
Handler: self.handleSelectNextConflict, Handler: self.handleSelectNextConflict,
Description: self.c.Tr.NextConflict, Description: self.c.Tr.NextConflict,
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.PrevItem), Key: opts.GetKey(opts.Config.Universal.PrevItem),
Handler: self.handleSelectPrevConflictHunk, Handler: self.handleSelectPrevConflictHunk,
Description: self.c.Tr.SelectPrevHunk, Description: self.c.Tr.SelectPrevHunk,
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.NextItem), Key: opts.GetKey(opts.Config.Universal.NextItem),
Handler: self.handleSelectNextConflictHunk, Handler: self.handleSelectNextConflictHunk,
Description: self.c.Tr.SelectNextHunk, Description: self.c.Tr.SelectNextHunk,
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt), Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt),
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: self.handleSelectPrevConflict, Handler: self.handleSelectPrevConflict,
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.NextBlockAlt), Key: opts.GetKey(opts.Config.Universal.NextBlockAlt),
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: self.handleSelectNextConflict, Handler: self.handleSelectNextConflict,
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.PrevItemAlt), Key: opts.GetKey(opts.Config.Universal.PrevItemAlt),
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: self.handleSelectPrevConflictHunk, Handler: self.handleSelectPrevConflictHunk,
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.NextItemAlt), Key: opts.GetKey(opts.Config.Universal.NextItemAlt),
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: self.handleSelectNextConflictHunk, Handler: self.handleSelectNextConflictHunk,
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.Edit), Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.handleMergeConflictEditFileAtLine, Handler: self.handleMergeConflictEditFileAtLine,
Description: self.c.Tr.LcEditFile, Description: self.c.Tr.LcEditFile,
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.OpenFile), Key: opts.GetKey(opts.Config.Universal.OpenFile),
Handler: self.handleMergeConflictOpenFileAtLine, Handler: self.handleMergeConflictOpenFileAtLine,
Description: self.c.Tr.LcOpenFile, Description: self.c.Tr.LcOpenFile,
}, },
{ {
ViewName: "main", ViewName: "merging",
Contexts: []string{string(context.MAIN_MERGING_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.Undo), Key: opts.GetKey(opts.Config.Universal.Undo),
Handler: self.handleMergeConflictUndo, Handler: self.handleMergeConflictUndo,
Description: self.c.Tr.LcUndo, Description: self.c.Tr.LcUndo,
@ -742,31 +472,17 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
Handler: self.scrollDownConfirmationPanel, Handler: self.scrollDownConfirmationPanel,
}, },
{ {
ViewName: "files", ViewName: "submodules",
Contexts: []string{string(context.SUBMODULES_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard), Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopySelectedSideContextItemToClipboard, Handler: self.handleCopySelectedSideContextItemToClipboard,
Description: self.c.Tr.LcCopySubmoduleNameToClipboard, Description: self.c.Tr.LcCopySubmoduleNameToClipboard,
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{string(context.FILES_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.ToggleWhitespaceInDiffView), Key: opts.GetKey(opts.Config.Universal.ToggleWhitespaceInDiffView),
Handler: self.toggleWhitespaceInDiffView, Handler: self.toggleWhitespaceInDiffView,
Description: self.c.Tr.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", ViewName: "extras",
Key: gocui.MouseWheelUp, Key: gocui.MouseWheelUp,
@ -777,17 +493,9 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
Key: gocui.MouseWheelDown, Key: gocui.MouseWheelDown,
Handler: self.scrollDownExtra, Handler: self.scrollDownExtra,
}, },
{
ViewName: "extras",
Key: opts.GetKey(opts.Config.Universal.ExtrasMenu),
Handler: self.handleCreateExtrasMenuPanel,
Description: self.c.Tr.LcOpenExtrasMenu,
OpensMenu: true,
},
{ {
ViewName: "extras", ViewName: "extras",
Tag: "navigation", Tag: "navigation",
Contexts: []string{string(context.COMMAND_LOG_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.PrevItemAlt), Key: opts.GetKey(opts.Config.Universal.PrevItemAlt),
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: self.scrollUpExtra, Handler: self.scrollUpExtra,
@ -795,7 +503,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
{ {
ViewName: "extras", ViewName: "extras",
Tag: "navigation", Tag: "navigation",
Contexts: []string{string(context.COMMAND_LOG_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.PrevItem), Key: opts.GetKey(opts.Config.Universal.PrevItem),
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: self.scrollUpExtra, Handler: self.scrollUpExtra,
@ -803,7 +510,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
{ {
ViewName: "extras", ViewName: "extras",
Tag: "navigation", Tag: "navigation",
Contexts: []string{string(context.COMMAND_LOG_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.NextItem), Key: opts.GetKey(opts.Config.Universal.NextItem),
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: self.scrollDownExtra, Handler: self.scrollDownExtra,
@ -811,7 +517,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
{ {
ViewName: "extras", ViewName: "extras",
Tag: "navigation", Tag: "navigation",
Contexts: []string{string(context.COMMAND_LOG_CONTEXT_KEY)},
Key: opts.GetKey(opts.Config.Universal.NextItemAlt), Key: opts.GetKey(opts.Config.Universal.NextItemAlt),
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: self.scrollDownExtra, Handler: self.scrollDownExtra,
@ -828,12 +533,8 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
mouseKeybindings := []*gocui.ViewMouseBinding{} mouseKeybindings := []*gocui.ViewMouseBinding{}
for _, c := range self.State.Contexts.Flatten() { for _, c := range self.State.Contexts.Flatten() {
viewName := c.GetViewName() viewName := c.GetViewName()
contextKey := c.GetKey()
for _, binding := range c.GetKeybindings(opts) { for _, binding := range c.GetKeybindings(opts) {
// TODO: move all mouse keybindings into the mouse keybindings approach below // 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 binding.ViewName = viewName
bindings = append(bindings, binding) bindings = append(bindings, binding)
} }
@ -841,7 +542,7 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
mouseKeybindings = append(mouseKeybindings, c.GetMouseKeybindings(opts)...) 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{ 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.PrevBlock), Modifier: gocui.ModNone, Handler: self.previousSideWindow},
{ViewName: viewName, Key: opts.GetKey(opts.Config.Universal.NextBlock), Modifier: gocui.ModNone, Handler: self.nextSideWindow}, {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{
bindings = append(bindings, []*types.Binding{ {
{ ViewName: "",
ViewName: viewName, Key: opts.GetKey(opts.Config.Universal.NextTab),
Key: opts.GetKey(opts.Config.Universal.NextTab), Handler: self.handleNextTab,
Handler: self.handleNextTab, Description: self.c.Tr.LcNextTab,
Description: self.c.Tr.LcNextTab, Tag: "navigation",
Tag: "navigation", },
}, {
{ ViewName: "",
ViewName: viewName, Key: opts.GetKey(opts.Config.Universal.PrevTab),
Key: opts.GetKey(opts.Config.Universal.PrevTab), Handler: self.handlePrevTab,
Handler: self.handlePrevTab, Description: self.c.Tr.LcPrevTab,
Description: self.c.Tr.LcPrevTab, Tag: "navigation",
Tag: "navigation", },
}, }...)
}...)
}
return bindings, mouseKeybindings return bindings, mouseKeybindings
} }
@ -914,12 +613,14 @@ func (gui *Gui) resetKeybindings() error {
} }
} }
for viewName := range gui.initialViewTabContextMap(gui.State.Contexts) { for _, values := range gui.viewTabMap() {
viewName := viewName for _, value := range values {
tabClickCallback := func(tabIndex int) error { return gui.onViewTabClick(viewName, tabIndex) } viewName := value.ViewName
tabClickCallback := func(tabIndex int) error { return gui.onViewTabClick(gui.windowForView(viewName), tabIndex) }
if err := gui.g.SetTabClickBinding(viewName, tabClickCallback); err != nil { if err := gui.g.SetTabClickBinding(viewName, tabClickCallback); err != nil {
return err 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 // warning: mutates the binding

View File

@ -1,7 +1,9 @@
package gui package gui
import ( import (
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/theme" "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] dimensionsObj, ok := viewDimensions[windowName]
view, err := g.View(viewName)
if err != nil {
return nil, err
}
if !ok { if !ok {
// view not specified in dimensions object: so create the view and hide it // 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 // 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) // to render content as soon as it appears, because lazyloaded content (via a pty task)
// cares about the size of the view. // cares about the size of the view.
view, err := g.SetView(viewName, 0, 0, width, height, 0) _, err := g.SetView(viewName, 0, 0, width, height, 0)
if view != nil { view.Visible = false
view.Visible = false
}
return view, err return view, err
} }
frameOffset := 1 frameOffset := 1
if frame { if view.Frame {
frameOffset = 0 frameOffset = 0
} }
view, err := g.SetView( _, err = g.SetView(
viewName, viewName,
dimensionsObj.X0-frameOffset, dimensionsObj.X0-frameOffset,
dimensionsObj.Y0-frameOffset, dimensionsObj.Y0-frameOffset,
@ -68,16 +74,17 @@ func (gui *Gui) layout(g *gocui.Gui) error {
dimensionsObj.Y1+frameOffset, dimensionsObj.Y1+frameOffset,
0, 0,
) )
if view != nil { view.Visible = true
view.Frame = frame
view.Visible = true
}
return view, err return view, err
} }
for _, arg := range gui.controlledViews() { for _, context := range gui.State.Contexts.Flatten() {
_, err := setViewFromDimensions(arg.viewName, arg.windowName, arg.frame) if !context.HasControlledBounds() {
continue
}
_, err := setViewFromDimensions(context.GetViewName(), context.GetWindowName())
if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG { if err != nil && err.Error() != UNKNOWN_VIEW_ERROR_MSG {
return err return err
} }
@ -124,10 +131,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
continue continue
} }
if !gui.isContextVisible(listContext) {
continue
}
listContext.FocusLine() listContext.FocusLine()
view.SelBgColor = theme.GocuiSelectedLineBgColor view.SelBgColor = theme.GocuiSelectedLineBgColor
@ -136,7 +139,16 @@ func (gui *Gui) layout(g *gocui.Gui) error {
view.SetOnSelectItem(gui.onSelectItemWrapper(listContext.OnSearchSelect)) 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() mainViewWidth, mainViewHeight := gui.Views.Main.Size()
if mainViewWidth != gui.PrevLayout.MainWidth || mainViewHeight != gui.PrevLayout.MainHeight { if mainViewWidth != gui.PrevLayout.MainWidth || mainViewHeight != gui.PrevLayout.MainHeight {
@ -188,11 +200,19 @@ func (gui *Gui) onInitialViewsCreation() error {
gui.g.Mutexes.ViewsMutex.Lock() gui.g.Mutexes.ViewsMutex.Lock()
// add tabs to views // add tabs to views
for _, view := range gui.g.Views() { for _, view := range gui.g.Views() {
tabs := gui.viewTabNames(view.Name()) // if the view is in our mapping, we'll set the tabs and the tab index
if len(tabs) == 0 { for _, values := range gui.viewTabMap() {
continue 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() 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.onFocusFile),
OnFocusWrapper(gui.withDiffModeCheck(gui.filesRenderToMain)), gui.withDiffModeCheck(gui.filesRenderToMain),
nil, nil,
gui.c, 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) return presentation.GetBranchListDisplayStrings(gui.State.Model.Branches, gui.State.ScreenMode != SCREEN_NORMAL, gui.State.Modes.Diffing.Ref, gui.Tr)
}, },
nil, nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.branchesRenderToMain)), gui.withDiffModeCheck(gui.branchesRenderToMain),
nil, nil,
gui.c, gui.c,
) )
@ -57,12 +57,12 @@ func (gui *Gui) branchesListContext() *context.BranchesContext {
func (gui *Gui) remotesListContext() *context.RemotesContext { func (gui *Gui) remotesListContext() *context.RemotesContext {
return context.NewRemotesContext( return context.NewRemotesContext(
func() []*models.Remote { return gui.State.Model.Remotes }, func() []*models.Remote { return gui.State.Model.Remotes },
gui.Views.Branches, gui.Views.Remotes,
func(startIdx int, length int) [][]string { func(startIdx int, length int) [][]string {
return presentation.GetRemoteListDisplayStrings(gui.State.Model.Remotes, gui.State.Modes.Diffing.Ref) return presentation.GetRemoteListDisplayStrings(gui.State.Model.Remotes, gui.State.Modes.Diffing.Ref)
}, },
nil, nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.remotesRenderToMain)), gui.withDiffModeCheck(gui.remotesRenderToMain),
nil, nil,
gui.c, gui.c,
) )
@ -76,7 +76,7 @@ func (gui *Gui) remoteBranchesListContext() *context.RemoteBranchesContext {
return presentation.GetRemoteBranchListDisplayStrings(gui.State.Model.RemoteBranches, gui.State.Modes.Diffing.Ref) return presentation.GetRemoteBranchListDisplayStrings(gui.State.Model.RemoteBranches, gui.State.Modes.Diffing.Ref)
}, },
nil, nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.remoteBranchesRenderToMain)), gui.withDiffModeCheck(gui.remoteBranchesRenderToMain),
nil, nil,
gui.c, gui.c,
) )
@ -95,12 +95,12 @@ func (gui *Gui) withDiffModeCheck(f func() error) func() error {
func (gui *Gui) tagsListContext() *context.TagsContext { func (gui *Gui) tagsListContext() *context.TagsContext {
return context.NewTagsContext( return context.NewTagsContext(
func() []*models.Tag { return gui.State.Model.Tags }, func() []*models.Tag { return gui.State.Model.Tags },
gui.Views.Branches, gui.Views.Tags,
func(startIdx int, length int) [][]string { func(startIdx int, length int) [][]string {
return presentation.GetTagListDisplayStrings(gui.State.Model.Tags, gui.State.Modes.Diffing.Ref) return presentation.GetTagListDisplayStrings(gui.State.Model.Tags, gui.State.Modes.Diffing.Ref)
}, },
nil, nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.tagsRenderToMain)), gui.withDiffModeCheck(gui.tagsRenderToMain),
nil, nil,
gui.c, gui.c,
) )
@ -133,7 +133,7 @@ func (gui *Gui) branchCommitsListContext() *context.LocalCommitsContext {
) )
}, },
OnFocusWrapper(gui.onCommitFocus), OnFocusWrapper(gui.onCommitFocus),
OnFocusWrapper(gui.withDiffModeCheck(gui.branchCommitsRenderToMain)), gui.withDiffModeCheck(gui.branchCommitsRenderToMain),
nil, nil,
gui.c, gui.c,
) )
@ -166,7 +166,7 @@ func (gui *Gui) subCommitsListContext() *context.SubCommitsContext {
) )
}, },
nil, nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.subCommitsRenderToMain)), gui.withDiffModeCheck(gui.subCommitsRenderToMain),
nil, nil,
gui.c, gui.c,
) )
@ -194,7 +194,7 @@ func (gui *Gui) shouldShowGraph() bool {
func (gui *Gui) reflogCommitsListContext() *context.ReflogCommitsContext { func (gui *Gui) reflogCommitsListContext() *context.ReflogCommitsContext {
return context.NewReflogCommitsContext( return context.NewReflogCommitsContext(
func() []*models.Commit { return gui.State.Model.FilteredReflogCommits }, func() []*models.Commit { return gui.State.Model.FilteredReflogCommits },
gui.Views.Commits, gui.Views.ReflogCommits,
func(startIdx int, length int) [][]string { func(startIdx int, length int) [][]string {
return presentation.GetReflogCommitListDisplayStrings( return presentation.GetReflogCommitListDisplayStrings(
gui.State.Model.FilteredReflogCommits, gui.State.Model.FilteredReflogCommits,
@ -206,7 +206,7 @@ func (gui *Gui) reflogCommitsListContext() *context.ReflogCommitsContext {
) )
}, },
nil, nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.reflogCommitsRenderToMain)), gui.withDiffModeCheck(gui.reflogCommitsRenderToMain),
nil, nil,
gui.c, gui.c,
) )
@ -220,7 +220,7 @@ func (gui *Gui) stashListContext() *context.StashContext {
return presentation.GetStashEntryListDisplayStrings(gui.State.Model.StashEntries, gui.State.Modes.Diffing.Ref) return presentation.GetStashEntryListDisplayStrings(gui.State.Model.StashEntries, gui.State.Modes.Diffing.Ref)
}, },
nil, nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.stashRenderToMain)), gui.withDiffModeCheck(gui.stashRenderToMain),
nil, nil,
gui.c, gui.c,
) )
@ -240,8 +240,8 @@ func (gui *Gui) commitFilesListContext() *context.CommitFilesContext {
return []string{line} return []string{line}
}) })
}, },
OnFocusWrapper(gui.onCommitFileFocus), nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.commitFilesRenderToMain)), gui.withDiffModeCheck(gui.commitFilesRenderToMain),
nil, nil,
gui.c, gui.c,
) )
@ -250,12 +250,12 @@ func (gui *Gui) commitFilesListContext() *context.CommitFilesContext {
func (gui *Gui) submodulesListContext() *context.SubmodulesContext { func (gui *Gui) submodulesListContext() *context.SubmodulesContext {
return context.NewSubmodulesContext( return context.NewSubmodulesContext(
func() []*models.SubmoduleConfig { return gui.State.Model.Submodules }, func() []*models.SubmoduleConfig { return gui.State.Model.Submodules },
gui.Views.Files, gui.Views.Submodules,
func(startIdx int, length int) [][]string { func(startIdx int, length int) [][]string {
return presentation.GetSubmoduleListDisplayStrings(gui.State.Model.Submodules) return presentation.GetSubmoduleListDisplayStrings(gui.State.Model.Submodules)
}, },
nil, nil,
OnFocusWrapper(gui.withDiffModeCheck(gui.submodulesRenderToMain)), gui.withDiffModeCheck(gui.submodulesRenderToMain),
nil, nil,
gui.c, gui.c,
) )

View File

@ -10,18 +10,11 @@ import (
type viewUpdateOpts struct { type viewUpdateOpts struct {
title string 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 task updateTask
context types.Context
} }
type refreshMainOpts struct { type refreshMainOpts struct {
pair MainContextPair
main *viewUpdateOpts main *viewUpdateOpts
secondary *viewUpdateOpts secondary *viewUpdateOpts
} }
@ -99,15 +92,21 @@ func (gui *Gui) runTaskForView(view *gocui.View, task updateTask) error {
return nil return nil
} }
func (gui *Gui) refreshMainView(opts *viewUpdateOpts, view *gocui.View) error { func (gui *Gui) moveMainContextPairToTop(pair MainContextPair) {
view.Title = opts.title gui.setWindowContext(pair.main)
view.Wrap = !opts.noWrap gui.moveToTopOfWindow(pair.main)
view.Highlight = opts.highlight if pair.secondary != nil {
context := opts.context gui.setWindowContext(pair.secondary)
if context == nil { gui.moveToTopOfWindow(pair.secondary)
context = gui.State.Contexts.Normal }
}
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 { if err := gui.runTaskForView(view, opts.task); err != nil {
gui.c.Log.Error(err) gui.c.Log.Error(err)
@ -117,19 +116,75 @@ func (gui *Gui) refreshMainView(opts *viewUpdateOpts, view *gocui.View) error {
return nil 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 { 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 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 return err
} }
} }
if opts.secondary != nil { 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 return err
} }
} else if opts.pair.secondary != nil {
opts.pair.secondary.GetView().Clear()
} }
gui.moveMainContextPairToTop(opts.pair)
gui.splitMainPanel(opts.secondary != nil) gui.splitMainPanel(opts.secondary != nil)
return nil return nil

View File

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

View File

@ -34,7 +34,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
description: func() string { description: func() string {
return gui.withResetButton(gui.c.Tr.LcBuildingPatch, style.FgYellow.SetBold()) return gui.withResetButton(gui.c.Tr.LcBuildingPatch, style.FgYellow.SetBold())
}, },
reset: gui.handleResetPatch, reset: gui.helpers.PatchBuilding.Reset,
}, },
{ {
isActive: gui.State.Modes.Filtering.Active, 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 { for _, binding := range bindings {
if keybindings.GetKeyDisplay(binding.Key) != "" && binding.Description != "" { if keybindings.GetKeyDisplay(binding.Key) != "" && binding.Description != "" {
if len(binding.Contexts) == 0 && binding.ViewName == "" { if binding.ViewName == "" {
bindingsGlobal = append(bindingsGlobal, binding) bindingsGlobal = append(bindingsGlobal, binding)
} else if binding.Tag == "navigation" { } else if binding.Tag == "navigation" {
bindingsNavigation = append(bindingsNavigation, binding) bindingsNavigation = append(bindingsNavigation, binding)
} else if lo.Contains(binding.Contexts, string(context.GetKey())) { } else if binding.ViewName == context.GetViewName() {
bindingsPanel = append(bindingsPanel, binding) 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" import "github.com/jesseduffield/lazygit/pkg/utils"

View File

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

View File

@ -1,12 +1,12 @@
package lbl package patch_exploring
import ( import (
"github.com/jesseduffield/lazygit/pkg/commands/patch" "github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// State represents the current state of the line-by-line context i.e. when // State represents the current state of the patch explorer context i.e. when
// you're staging a file line-by-line or you're building a patch from an existing commit // 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. // this struct holds the info about the diff you're interacting with and what's currently selected.
type State struct { type State struct {
selectedLineIdx int selectedLineIdx int
@ -26,6 +26,13 @@ const (
) )
func NewState(diff string, selectedLineIdx int, oldState *State, log *logrus.Entry) *State { 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) patchParser := patch.NewPatchParser(log, diff)
if len(patchParser.StageableLines) == 0 { if len(patchParser.StageableLines) == 0 {
@ -178,14 +185,14 @@ func (s *State) AdjustSelectedLineIdx(change int) {
s.SelectLine(s.selectedLineIdx + change) 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() firstLineIdx, lastLineIdx := s.SelectedRange()
return s.patchParser.Render(firstLineIdx, lastLineIdx, includedLineIndices) return s.patchParser.Render(isFocused, firstLineIdx, lastLineIdx, includedLineIndices)
} }
func (s *State) PlainRenderSelected() string { func (s *State) PlainRenderSelected() string {
firstLineIdx, lastLineIdx := s.SelectedRange() firstLineIdx, lastLineIdx := s.SelectedRange()
return s.patchParser.PlainRenderLines(firstLineIdx, lastLineIdx) return s.patchParser.RenderLinesPlain(firstLineIdx, lastLineIdx)
} }
func (s *State) SelectBottom() { func (s *State) SelectBottom() {

View File

@ -5,6 +5,7 @@ package gui
import ( import (
"io" "io"
"os"
"os/exec" "os/exec"
"strings" "strings"
@ -19,14 +20,20 @@ func (gui *Gui) desiredPtySize() *pty.Winsize {
} }
func (gui *Gui) onResize() error { func (gui *Gui) onResize() error {
gui.Mutexes.PtyMutex.Lock()
defer gui.Mutexes.PtyMutex.Unlock()
if gui.State.Ptmx == nil { if gui.State.Ptmx == nil {
return nil return nil
} }
gui.Log.Warn("resizing")
// TODO: handle resizing properly: we need to actually clear the main view // 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 // and re-read the output from our pty. Or we could just re-run the original
// command from scratch // command from scratch
if err := pty.Setsize(gui.State.Ptmx, gui.desiredPtySize()); err != nil { if err := pty.Setsize(gui.State.Ptmx, gui.desiredPtySize()); err != nil {
panic(err)
return err return err
} }
@ -57,20 +64,27 @@ func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error
manager := gui.getManager(view) manager := gui.getManager(view)
var ptmx *os.File
start := func() (*exec.Cmd, io.Reader) { 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 { if err != nil {
gui.c.Log.Error(err) gui.c.Log.Error(err)
} }
gui.Mutexes.PtyMutex.Lock()
gui.State.Ptmx = ptmx gui.State.Ptmx = ptmx
gui.Mutexes.PtyMutex.Unlock()
return cmd, ptmx return cmd, ptmx
} }
onClose := func() { onClose := func() {
gui.State.Ptmx.Close() gui.Mutexes.PtyMutex.Lock()
ptmx.Close()
ptmx = nil
gui.State.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 { 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()) task := NewRunPtyTask(cmdObj.GetCmd())
return gui.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{ main: &viewUpdateOpts{
title: gui.c.Tr.LogTitle, title: gui.c.Tr.LogTitle,
task: task, task: task,

View File

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

View File

@ -13,6 +13,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/filetree" "github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts" "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
"github.com/jesseduffield/lazygit/pkg/gui/presentation" "github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
@ -31,6 +32,7 @@ func getScopeNames(scopes []types.RefreshableView) []string {
types.REMOTES: "remotes", types.REMOTES: "remotes",
types.STATUS: "status", types.STATUS: "status",
types.BISECT_INFO: "bisect", types.BISECT_INFO: "bisect",
types.STAGING: "staging",
} }
return slices.Map(scopes, func(scope types.RefreshableView) string { return slices.Map(scopes, func(scope types.RefreshableView) string {
@ -70,6 +72,8 @@ func (gui *Gui) Refresh(options types.RefreshOptions) error {
f := func() { f := func() {
var scopeSet *set.Set[types.RefreshableView] var scopeSet *set.Set[types.RefreshableView]
if len(options.Scope) == 0 { 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{ scopeSet = set.NewFromSlice([]types.RefreshableView{
types.COMMITS, types.COMMITS,
types.BRANCHES, types.BRANCHES,
@ -105,6 +109,11 @@ func (gui *Gui) Refresh(options types.RefreshOptions) error {
refresh(func() { _ = gui.refreshRebaseCommits() }) 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) { if scopeSet.Includes(types.FILES) || scopeSet.Includes(types.SUBMODULES) {
refresh(func() { _ = gui.refreshFilesAndSubmodules() }) refresh(func() { _ = gui.refreshFilesAndSubmodules() })
} }
@ -121,6 +130,14 @@ func (gui *Gui) Refresh(options types.RefreshOptions) error {
refresh(func() { _ = gui.refreshRemotes() }) 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() wg.Wait()
gui.refreshStatus() gui.refreshStatus()
@ -218,6 +235,21 @@ func (gui *Gui) refreshCommitsWithLimit() error {
return gui.c.PostRefreshUpdate(gui.State.Contexts.LocalCommits) 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 { func (gui *Gui) refreshRebaseCommits() error {
gui.Mutexes.LocalCommitsMutex.Lock() gui.Mutexes.LocalCommitsMutex.Lock()
defer gui.Mutexes.LocalCommitsMutex.Unlock() defer gui.Mutexes.LocalCommitsMutex.Unlock()
@ -332,7 +364,7 @@ func (gui *Gui) refreshMergeState() error {
gui.State.Panels.Merging.Lock() gui.State.Panels.Merging.Lock()
defer gui.State.Panels.Merging.Unlock() 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 return nil
} }
@ -535,3 +567,137 @@ func (gui *Gui) refreshStatus() {
gui.setViewContent(gui.Views.Status, status) 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{ return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{ main: &viewUpdateOpts{
title: "Remote Branch", title: "Remote Branch",
task: task, task: task,

View File

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

View File

@ -29,7 +29,7 @@ func (self *KeybindingCreator) call(customCommand config.CustomCommand, handler
return nil, formatContextNotProvidedError(customCommand) return nil, formatContextNotProvidedError(customCommand)
} }
viewName, contexts, err := self.getViewNameAndContexts(customCommand) viewName, err := self.getViewNameAndContexts(customCommand)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -41,7 +41,6 @@ func (self *KeybindingCreator) call(customCommand config.CustomCommand, handler
return &types.Binding{ return &types.Binding{
ViewName: viewName, ViewName: viewName,
Contexts: contexts,
Key: self.getKey(customCommand.Key), Key: self.getKey(customCommand.Key),
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: handler, Handler: handler,
@ -49,22 +48,18 @@ func (self *KeybindingCreator) call(customCommand config.CustomCommand, handler
}, nil }, nil
} }
func (self *KeybindingCreator) getViewNameAndContexts(customCommand config.CustomCommand) (string, []string, error) { func (self *KeybindingCreator) getViewNameAndContexts(customCommand config.CustomCommand) (string, error) {
if customCommand.Context == "global" { if customCommand.Context == "global" {
return "", nil, nil return "", nil
} }
ctx, ok := self.contextForContextKey(types.ContextKey(customCommand.Context)) ctx, ok := self.contextForContextKey(types.ContextKey(customCommand.Context))
if !ok { 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() viewName := ctx.GetViewName()
contexts := []string{customCommand.Context} return viewName, nil
return viewName, contexts, nil
} }
func (self *KeybindingCreator) contextForContextKey(contextKey types.ContextKey) (types.Context, bool) { func (self *KeybindingCreator) contextForContextKey(contextKey types.ContextKey) (types.Context, bool) {

View File

@ -21,9 +21,9 @@ func (gui *Gui) nextSideWindow() error {
return err return err
} }
viewName := gui.getViewNameForWindow(newWindow) context := gui.getContextForWindow(newWindow)
return gui.pushContextWithView(viewName) return gui.c.PushContext(context)
} }
func (gui *Gui) previousSideWindow() error { func (gui *Gui) previousSideWindow() error {
@ -47,13 +47,15 @@ func (gui *Gui) previousSideWindow() error {
return err 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 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{ return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{ main: &viewUpdateOpts{
title: "Stash", title: "Stash",
task: task, task: task,

View File

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

View File

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

View File

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

View File

@ -1,16 +1,17 @@
package gui package gui
func (self *Gui) tagsRenderToMain() error { func (gui *Gui) tagsRenderToMain() error {
var task updateTask var task updateTask
tag := self.State.Contexts.Tags.GetSelected() tag := gui.State.Contexts.Tags.GetSelected()
if tag == nil { if tag == nil {
task = NewRenderStringTask("No tags") task = NewRenderStringTask("No tags")
} else { } else {
cmdObj := self.git.Branch.GetGraphCmdObj(tag.FullRefName()) cmdObj := gui.git.Branch.GetGraphCmdObj(tag.FullRefName())
task = NewRunCommandTask(cmdObj.GetCmd()) task = NewRunCommandTask(cmdObj.GetCmd())
} }
return self.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{
pair: gui.normalMainContextPair(),
main: &viewUpdateOpts{ main: &viewUpdateOpts{
title: "Tag", title: "Tag",
task: task, 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) { start := func() (*exec.Cmd, io.Reader) {
r, err := cmd.StdoutPipe() r, err := cmd.StdoutPipe()
if err != nil { if err != nil {
gui.c.Log.Warn(err) gui.c.Log.Error(err)
} }
cmd.Stderr = cmd.Stdout cmd.Stderr = cmd.Stdout
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
gui.c.Log.Warn(err) gui.c.Log.Error(err)
} }
return cmd, r return cmd, r
} }
if err := manager.NewTask(manager.NewCmdTask(start, prefix, height+oy+10, nil), cmdStr); err != nil { if err := manager.NewTask(manager.NewCmdTask(start, prefix, height+oy+10, nil), cmdStr); err != nil {
gui.c.Log.Warn(err) gui.c.Log.Error(err)
} }
return nil return nil

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/spkg/bom" "github.com/spkg/bom"
) )
@ -116,52 +117,71 @@ func (gui *Gui) popupPanelFocused() bool {
return gui.isPopupPanel(gui.currentViewName()) 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) onViewTabClick(windowName string, tabIndex int) error {
func (gui *Gui) secondaryViewFocused() bool { tabs := gui.viewTabMap()[windowName]
state := gui.State.Panels.LineByLine if len(tabs) == 0 {
return state != nil && state.SecondaryFocused return nil
} }
func (gui *Gui) onViewTabClick(viewName string, tabIndex int) error { viewName := tabs[tabIndex].ViewName
context := gui.State.ViewTabContextMap[viewName][tabIndex].Context
context, ok := gui.contextForView(viewName)
if !ok {
return nil
}
return gui.c.PushContext(context) 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 { func (gui *Gui) handleNextTab() error {
v := getTabbedView(gui) view := getTabbedView(gui)
if v == nil { if view == nil {
return nil return nil
} }
return gui.onViewTabClick( for _, context := range gui.State.Contexts.Flatten() {
v.Name(), if context.GetViewName() == view.Name() {
utils.ModuloWithWrap(v.TabIndex+1, len(v.Tabs)), return gui.onViewTabClick(
) context.GetWindowName(),
utils.ModuloWithWrap(view.TabIndex+1, len(view.Tabs)),
)
}
}
return nil
} }
func (gui *Gui) handlePrevTab() error { func (gui *Gui) handlePrevTab() error {
v := getTabbedView(gui) view := getTabbedView(gui)
if v == nil { if view == nil {
return nil return nil
} }
return gui.onViewTabClick( for _, context := range gui.State.Contexts.Flatten() {
v.Name(), if context.GetViewName() == view.Name() {
utils.ModuloWithWrap(v.TabIndex-1, len(v.Tabs)), return gui.onViewTabClick(
) context.GetWindowName(),
} utils.ModuloWithWrap(view.TabIndex-1, len(view.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
} }
return delta return nil
} }
func getTabbedView(gui *Gui) *gocui.View { func getTabbedView(gui *Gui) *gocui.View {

View File

@ -3,34 +3,43 @@ package gui
import ( import (
"github.com/jesseduffield/generics/slices" "github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme" "github.com/jesseduffield/lazygit/pkg/theme"
) )
type Views struct { type Views struct {
Status *gocui.View Status *gocui.View
Submodules *gocui.View
Files *gocui.View Files *gocui.View
Branches *gocui.View Branches *gocui.View
Remotes *gocui.View
Tags *gocui.View
RemoteBranches *gocui.View RemoteBranches *gocui.View
ReflogCommits *gocui.View
Commits *gocui.View Commits *gocui.View
Stash *gocui.View Stash *gocui.View
Main *gocui.View
Secondary *gocui.View Main *gocui.View
Options *gocui.View Secondary *gocui.View
Confirmation *gocui.View Staging *gocui.View
Menu *gocui.View StagingSecondary *gocui.View
CommitMessage *gocui.View PatchBuilding *gocui.View
CommitFiles *gocui.View PatchBuildingSecondary *gocui.View
SubCommits *gocui.View Merging *gocui.View
Information *gocui.View
AppStatus *gocui.View Options *gocui.View
Search *gocui.View Confirmation *gocui.View
SearchPrefix *gocui.View Menu *gocui.View
Limit *gocui.View CommitMessage *gocui.View
Suggestions *gocui.View CommitFiles *gocui.View
Tooltip *gocui.View SubCommits *gocui.View
Extras *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 { type viewNameMapping struct {
@ -49,15 +58,26 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping {
// first layer. Ordering within this layer does not matter because there are // first layer. Ordering within this layer does not matter because there are
// no overlapping views // no overlapping views
{viewPtr: &gui.Views.Status, name: "status"}, {viewPtr: &gui.Views.Status, name: "status"},
{viewPtr: &gui.Views.Submodules, name: "submodules"},
{viewPtr: &gui.Views.Files, name: "files"}, {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.RemoteBranches, name: "remoteBranches"},
{viewPtr: &gui.Views.ReflogCommits, name: "reflogCommits"},
{viewPtr: &gui.Views.Commits, name: "commits"}, {viewPtr: &gui.Views.Commits, name: "commits"},
{viewPtr: &gui.Views.Stash, name: "stash"}, {viewPtr: &gui.Views.Stash, name: "stash"},
{viewPtr: &gui.Views.SubCommits, name: "subCommits"}, {viewPtr: &gui.Views.SubCommits, name: "subCommits"},
{viewPtr: &gui.Views.CommitFiles, name: "commitFiles"}, {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.Secondary, name: "secondary"},
{viewPtr: &gui.Views.Main, name: "main"},
{viewPtr: &gui.Views.Extras, name: "extras"}, {viewPtr: &gui.Views.Extras, name: "extras"},
// bottom line // bottom line
@ -80,35 +100,13 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping {
} }
} }
type controlledView struct { func (gui *Gui) windowForView(viewName string) string {
viewName string context, ok := gui.contextForView(viewName)
windowName string if !ok {
frame bool panic("todo: deal with this")
}
// 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},
} }
return context.GetWindowName()
} }
func (gui *Gui) createAllViews() error { func (gui *Gui) createAllViews() error {
@ -121,9 +119,11 @@ func (gui *Gui) createAllViews() error {
} }
gui.Views.Options.FgColor = theme.OptionsColor gui.Views.Options.FgColor = theme.OptionsColor
gui.Views.Options.Frame = false
gui.Views.SearchPrefix.BgColor = gocui.ColorDefault gui.Views.SearchPrefix.BgColor = gocui.ColorDefault
gui.Views.SearchPrefix.FgColor = gocui.ColorGreen gui.Views.SearchPrefix.FgColor = gocui.ColorGreen
gui.Views.SearchPrefix.Frame = false
gui.setViewContent(gui.Views.SearchPrefix, SEARCH_PREFIX) gui.setViewContent(gui.Views.SearchPrefix, SEARCH_PREFIX)
gui.Views.Stash.Title = gui.c.Tr.StashTitle 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.Title = gui.c.Tr.BranchesTitle
gui.Views.Branches.FgColor = theme.GocuiDefaultTextColor 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.RemoteBranches.FgColor = theme.GocuiDefaultTextColor
gui.Views.Files.Title = gui.c.Tr.FilesTitle gui.Views.Files.Title = gui.c.Tr.FilesTitle
gui.Views.Files.FgColor = theme.GocuiDefaultTextColor gui.Views.Files.FgColor = theme.GocuiDefaultTextColor
gui.Views.Secondary.Title = gui.c.Tr.DiffTitle 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} {
gui.Views.Secondary.Wrap = true view.Title = gui.c.Tr.DiffTitle
gui.Views.Secondary.FgColor = theme.GocuiDefaultTextColor view.Wrap = true
gui.Views.Secondary.IgnoreCarriageReturns = true view.FgColor = theme.GocuiDefaultTextColor
gui.Views.Secondary.CanScrollPastBottom = gui.c.UserConfig.Gui.ScrollPastBottom view.IgnoreCarriageReturns = true
view.CanScrollPastBottom = gui.c.UserConfig.Gui.ScrollPastBottom
}
gui.Views.Main.Title = gui.c.Tr.DiffTitle gui.Views.Staging.Title = gui.c.Tr.UnstagedChanges
gui.Views.Main.Wrap = true gui.Views.Staging.Highlight = true
gui.Views.Main.FgColor = theme.GocuiDefaultTextColor gui.Views.Staging.Wrap = true
gui.Views.Main.IgnoreCarriageReturns = true
gui.Views.Main.CanScrollPastBottom = gui.c.UserConfig.Gui.ScrollPastBottom 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.Title = gui.c.Tr.NotEnoughSpace
gui.Views.Limit.Wrap = true gui.Views.Limit.Wrap = true
@ -166,10 +188,12 @@ func (gui *Gui) createAllViews() error {
gui.Views.Search.BgColor = gocui.ColorDefault gui.Views.Search.BgColor = gocui.ColorDefault
gui.Views.Search.FgColor = gocui.ColorGreen gui.Views.Search.FgColor = gocui.ColorGreen
gui.Views.Search.Editable = true gui.Views.Search.Editable = true
gui.Views.Search.Frame = false
gui.Views.AppStatus.BgColor = gocui.ColorDefault gui.Views.AppStatus.BgColor = gocui.ColorDefault
gui.Views.AppStatus.FgColor = gocui.ColorCyan gui.Views.AppStatus.FgColor = gocui.ColorCyan
gui.Views.AppStatus.Visible = false gui.Views.AppStatus.Visible = false
gui.Views.AppStatus.Frame = false
gui.Views.CommitMessage.Visible = false gui.Views.CommitMessage.Visible = false
gui.Views.CommitMessage.Title = gui.c.Tr.CommitMessage 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.BgColor = gocui.ColorDefault
gui.Views.Information.FgColor = gocui.ColorGreen gui.Views.Information.FgColor = gocui.ColorGreen
gui.Views.Information.Frame = false
gui.Views.Extras.Title = gui.c.Tr.CommandLog gui.Views.Extras.Title = gui.c.Tr.CommandLog
gui.Views.Extras.FgColor = theme.GocuiDefaultTextColor gui.Views.Extras.FgColor = theme.GocuiDefaultTextColor
@ -197,22 +222,3 @@ func (gui *Gui) createAllViews() error {
return nil 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 package gui
import ( import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types" "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. // 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 // space. Right now most windows are 1:1 with views, except for commitFiles which
// is a view that moves between windows // 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 { func (gui *Gui) getViewNameForWindow(window string) string {
viewName, ok := gui.State.WindowViewNameMap[window] viewName, ok := gui.State.WindowViewNameMap[window]
if !ok { if !ok {
return window panic(fmt.Sprintf("Viewname not found for window: %s", window))
} }
return viewName return viewName
} }
// for now all we actually care about is the context's view so we're storing that func (gui *Gui) getContextForWindow(window string) types.Context {
func (gui *Gui) setWindowContext(c types.Context) { viewName := gui.getViewNameForWindow(window)
if gui.State.WindowViewNameMap == nil {
gui.State.WindowViewNameMap = map[string]string{} 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() { if c.IsTransient() {
gui.resetWindowContext(c) gui.resetWindowContext(c)
} }
@ -40,8 +61,46 @@ func (gui *Gui) currentWindow() string {
func (gui *Gui) resetWindowContext(c types.Context) { func (gui *Gui) resetWindowContext(c types.Context) {
for windowName, viewName := range gui.State.WindowViewNameMap { for windowName, viewName := range gui.State.WindowViewNameMap {
if viewName == c.GetViewName() && windowName != c.GetWindowName() { 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 for _, context := range gui.State.Contexts.Flatten() {
gui.State.WindowViewNameMap[windowName] = windowName 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: `切换拖动选择`, ToggleDragSelect: `切换拖动选择`,
ToggleSelectHunk: `切换选择区块`, ToggleSelectHunk: `切换选择区块`,
ToggleSelectionForPatch: `添加/移除 行到补丁`, ToggleSelectionForPatch: `添加/移除 行到补丁`,
TogglePanel: `切换到其他面板`, ToggleStagingPanel: `切换到其他面板`,
ReturnToFilesPanel: `返回文件面板`, ReturnToFilesPanel: `返回文件面板`,
FastForward: `从上游快进此分支`, FastForward: `从上游快进此分支`,
Fetching: "抓取并快进 {{.from}} -> {{.to}} ...", Fetching: "抓取并快进 {{.from}} -> {{.to}} ...",
@ -298,7 +298,7 @@ func chineseTranslationSet() TranslationSet {
PatchOptionsTitle: "补丁选项", PatchOptionsTitle: "补丁选项",
NoPatchError: "尚未创建补丁。你可以在提交中的文件上按下“空格”或使用“回车”添加其中的特定行以开始构建补丁", NoPatchError: "尚未创建补丁。你可以在提交中的文件上按下“空格”或使用“回车”添加其中的特定行以开始构建补丁",
LcEnterFile: "输入文件以将所选行添加到补丁中(或切换目录折叠)", LcEnterFile: "输入文件以将所选行添加到补丁中(或切换目录折叠)",
ExitLineByLineMode: `退出逐行模式`, ExitCustomPatchBuilder: `退出逐行模式`,
EnterUpstream: `以这种格式输入上游:'<远程仓库> <分支名称>'`, EnterUpstream: `以这种格式输入上游:'<远程仓库> <分支名称>'`,
InvalidUpstream: "上游格式无效,格式应当为:'<remote> <branchname>'", InvalidUpstream: "上游格式无效,格式应当为:'<remote> <branchname>'",
ReturnToRemotesList: `返回远程仓库列表`, ReturnToRemotesList: `返回远程仓库列表`,

View File

@ -141,7 +141,7 @@ func dutchTranslationSet() TranslationSet {
ToggleDragSelect: `toggle drag selecteer`, ToggleDragSelect: `toggle drag selecteer`,
ToggleSelectHunk: `toggle selecteer hunk`, ToggleSelectHunk: `toggle selecteer hunk`,
ToggleSelectionForPatch: `voeg toe/verwijder lijn(en) in patch`, 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`, ReturnToFilesPanel: `ga terug naar het bestanden paneel`,
FastForward: `fast-forward deze branch vanaf zijn upstream`, FastForward: `fast-forward deze branch vanaf zijn upstream`,
Fetching: "fetching en fast-forwarding {{.from}} -> {{.to}} ...", Fetching: "fetching en fast-forwarding {{.from}} -> {{.to}} ...",
@ -258,7 +258,7 @@ func dutchTranslationSet() TranslationSet {
PatchOptionsTitle: "Patch Opties", 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", 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", 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>'`, EnterUpstream: `Enter upstream als '<remote> <branchnaam>'`,
ReturnToRemotesList: `ga terug naar remotes lijst`, ReturnToRemotesList: `ga terug naar remotes lijst`,
LcAddNewRemote: `voeg een nieuwe remote toe`, LcAddNewRemote: `voeg een nieuwe remote toe`,

View File

@ -176,7 +176,7 @@ type TranslationSet struct {
ToggleSelectHunk string ToggleSelectHunk string
ToggleSelectionForPatch string ToggleSelectionForPatch string
EditHunk string EditHunk string
TogglePanel string ToggleStagingPanel string
ReturnToFilesPanel string ReturnToFilesPanel string
FastForward string FastForward string
Fetching string Fetching string
@ -307,7 +307,7 @@ type TranslationSet struct {
PatchOptionsTitle string PatchOptionsTitle string
NoPatchError string NoPatchError string
LcEnterFile string LcEnterFile string
ExitLineByLineMode string ExitCustomPatchBuilder string
EnterUpstream string EnterUpstream string
InvalidUpstream string InvalidUpstream string
ReturnToRemotesList string ReturnToRemotesList string
@ -503,6 +503,8 @@ type TranslationSet struct {
NukeDescription string NukeDescription string
DiscardStagedChangesDescription string DiscardStagedChangesDescription string
EmptyOutput string EmptyOutput string
Patch string
CustomPatch string
Actions Actions Actions Actions
Bisect Bisect Bisect Bisect
} }
@ -812,7 +814,7 @@ func EnglishTranslationSet() TranslationSet {
ToggleSelectHunk: `toggle select hunk`, ToggleSelectHunk: `toggle select hunk`,
ToggleSelectionForPatch: `add/remove line(s) to patch`, ToggleSelectionForPatch: `add/remove line(s) to patch`,
EditHunk: `edit hunk`, EditHunk: `edit hunk`,
TogglePanel: `switch to other panel`, ToggleStagingPanel: `switch to other panel (staged/unstaged changes)`,
ReturnToFilesPanel: `return to files panel`, ReturnToFilesPanel: `return to files panel`,
FastForward: `fast-forward this branch from its upstream`, FastForward: `fast-forward this branch from its upstream`,
Fetching: "fetching and fast-forwarding {{.from}} -> {{.to}} ...", Fetching: "fetching and fast-forwarding {{.from}} -> {{.to}} ...",
@ -944,7 +946,7 @@ func EnglishTranslationSet() TranslationSet {
PatchOptionsTitle: "Patch Options", 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", 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)", 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>'`, EnterUpstream: `Enter upstream as '<remote> <branchname>'`,
InvalidUpstream: "Invalid upstream. Must be in the format '<remote> <branchname>'", InvalidUpstream: "Invalid upstream. Must be in the format '<remote> <branchname>'",
ReturnToRemotesList: `Return to remotes list`, 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).", 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", 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>", EmptyOutput: "<empty output>",
Patch: "Patch",
CustomPatch: "Custom patch",
Actions: Actions{ Actions: Actions{
// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm) // TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
CheckoutCommit: "Checkout commit", CheckoutCommit: "Checkout commit",

View File

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

View File

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

View File

@ -212,7 +212,7 @@ func polishTranslationSet() TranslationSet {
LcStashOptions: "Opcje schowka", LcStashOptions: "Opcje schowka",
NotARepository: "Błąd: nie jesteś w repozytorium", NotARepository: "Błąd: nie jesteś w repozytorium",
LcJump: "przeskocz do panelu", 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>'`, EnterUpstream: `Podaj gałąź nadrzędną jako '<remote> <branchname>'`,
ReturnToRemotesList: `wróć do listy repozytoriów zdalnych`, ReturnToRemotesList: `wróć do listy repozytoriów zdalnych`,
IgnoreTracked: "Ignoruj plik śledzony", 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 { if err := cmd.Wait(); err != nil {
// it's fine if we've killed this program ourselves // it's fine if we've killed this program ourselves
if !strings.Contains(err.Error(), "signal: killed") { 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