mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-03-19 21:28:28 +02:00
Merge branch 'refactor-better-encapsulation' into test-refactor
This commit is contained in:
commit
8d68ab41b6
@ -8,20 +8,20 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>ctrl+r</kbd>: switch to a recent repo
|
||||
<kbd>pgup</kbd>: scroll up main panel (fn+up/shift+k)
|
||||
<kbd>pgdown</kbd>: scroll down main panel (fn+down/shift+j)
|
||||
<kbd>m</kbd>: view merge/rebase options
|
||||
<kbd>ctrl+p</kbd>: view custom patch options
|
||||
<kbd>R</kbd>: refresh
|
||||
<kbd>?</kbd>: open menu
|
||||
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: prev screen mode
|
||||
<kbd>ctrl+s</kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: open diff menu
|
||||
<kbd>ctrl+e</kbd>: open diff menu
|
||||
<kbd>@</kbd>: open command log menu
|
||||
<kbd>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||
<kbd>}</kbd>: Increase the size of the context shown around changes in the diff view
|
||||
<kbd>{</kbd>: Decrease the size of the context shown around changes in the diff view
|
||||
<kbd>:</kbd>: execute custom command
|
||||
<kbd>ctrl+p</kbd>: view custom patch options
|
||||
<kbd>m</kbd>: view merge/rebase options
|
||||
<kbd>R</kbd>: refresh
|
||||
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: prev screen mode
|
||||
<kbd>?</kbd>: open menu
|
||||
<kbd>ctrl+s</kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: open diff menu
|
||||
<kbd>ctrl+e</kbd>: open diff menu
|
||||
<kbd>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||
<kbd>z</kbd>: undo (via reflog) (experimental)
|
||||
<kbd>ctrl+z</kbd>: redo (via reflog) (experimental)
|
||||
<kbd>P</kbd>: push
|
||||
@ -56,6 +56,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>`</kbd>: toggle file tree view
|
||||
</pre>
|
||||
|
||||
## Commit Message
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: confirm
|
||||
<kbd>esc</kbd>: close
|
||||
</pre>
|
||||
|
||||
## Commits
|
||||
|
||||
<pre>
|
||||
@ -89,6 +96,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
## Confirmation Panel
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: confirm
|
||||
<kbd>esc</kbd>: close/cancel
|
||||
</pre>
|
||||
|
||||
## Files
|
||||
|
||||
<pre>
|
||||
@ -197,6 +211,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>C</kbd>: commit changes using git editor
|
||||
</pre>
|
||||
|
||||
## Menu
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: execute
|
||||
<kbd>esc</kbd>: close
|
||||
</pre>
|
||||
|
||||
## Reflog
|
||||
|
||||
<pre>
|
||||
@ -250,8 +271,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edit config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>e</kbd>: edit config file
|
||||
<kbd>u</kbd>: check for update
|
||||
<kbd>enter</kbd>: switch to a recent repo
|
||||
<kbd>a</kbd>: show all branch logs
|
||||
|
@ -8,20 +8,20 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>ctrl+r</kbd>: 最近使用したリポジトリに切り替え
|
||||
<kbd>pgup</kbd>: メインパネルを上にスクロール (fn+up/shift+k)
|
||||
<kbd>pgdown</kbd>: メインパネルを下にスクロール (fn+down/shift+j)
|
||||
<kbd>m</kbd>: view merge/rebase options
|
||||
<kbd>ctrl+p</kbd>: view custom patch options
|
||||
<kbd>R</kbd>: リフレッシュ
|
||||
<kbd>?</kbd>: メニューを開く
|
||||
<kbd>+</kbd>: 次のスクリーンモード (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: 前のスクリーンモード
|
||||
<kbd>ctrl+s</kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: 差分メニューを開く
|
||||
<kbd>ctrl+e</kbd>: 差分メニューを開く
|
||||
<kbd>@</kbd>: コマンドログメニューを開く
|
||||
<kbd>ctrl+w</kbd>: 空白文字の差分の表示有無を切り替え
|
||||
<kbd>}</kbd>: Increase the size of the context shown around changes in the diff view
|
||||
<kbd>{</kbd>: Decrease the size of the context shown around changes in the diff view
|
||||
<kbd>:</kbd>: カスタムコマンドを実行
|
||||
<kbd>ctrl+p</kbd>: view custom patch options
|
||||
<kbd>m</kbd>: view merge/rebase options
|
||||
<kbd>R</kbd>: リフレッシュ
|
||||
<kbd>+</kbd>: 次のスクリーンモード (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: 前のスクリーンモード
|
||||
<kbd>?</kbd>: メニューを開く
|
||||
<kbd>ctrl+s</kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: 差分メニューを開く
|
||||
<kbd>ctrl+e</kbd>: 差分メニューを開く
|
||||
<kbd>ctrl+w</kbd>: 空白文字の差分の表示有無を切り替え
|
||||
<kbd>z</kbd>: アンドゥ (via reflog) (experimental)
|
||||
<kbd>ctrl+z</kbd>: リドゥ (via reflog) (experimental)
|
||||
<kbd>P</kbd>: push
|
||||
@ -115,6 +115,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>`</kbd>: ファイルツリーの表示を切り替え
|
||||
</pre>
|
||||
|
||||
## コミットメッセージ
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 確認
|
||||
<kbd>esc</kbd>: 閉じる
|
||||
</pre>
|
||||
|
||||
## サブモジュール
|
||||
|
||||
<pre>
|
||||
@ -131,8 +138,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## ステータス
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: 設定ファイルを編集
|
||||
<kbd>o</kbd>: 設定ファイルを開く
|
||||
<kbd>e</kbd>: 設定ファイルを編集
|
||||
<kbd>u</kbd>: 更新を確認
|
||||
<kbd>enter</kbd>: 最近使用したリポジトリに切り替え
|
||||
<kbd>a</kbd>: すべてのブランチログを表示
|
||||
@ -257,6 +264,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>C</kbd>: gitエディタを使用して変更をコミット
|
||||
</pre>
|
||||
|
||||
## メニュー
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 実行
|
||||
<kbd>esc</kbd>: 閉じる
|
||||
</pre>
|
||||
|
||||
## リモート
|
||||
|
||||
<pre>
|
||||
@ -295,3 +309,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>enter</kbd>: コミットを閲覧
|
||||
</pre>
|
||||
|
||||
## 確認パネル
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 確認
|
||||
<kbd>esc</kbd>: 閉じる/キャンセル
|
||||
</pre>
|
||||
|
@ -8,20 +8,20 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>ctrl+r</kbd>: 최근에 사용한 저장소로 전환
|
||||
<kbd>pgup</kbd>: 메인 패널을 위로 스크롤 (fn+up/shift+k)
|
||||
<kbd>pgdown</kbd>: 메인 패널을 아래로로 스크롤 (fn+down/shift+j)
|
||||
<kbd>m</kbd>: view merge/rebase options
|
||||
<kbd>ctrl+p</kbd>: 커스텀 Patch 옵션 보기
|
||||
<kbd>R</kbd>: 새로고침
|
||||
<kbd>?</kbd>: 매뉴 열기
|
||||
<kbd>+</kbd>: 다음 스크린 모드 (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: 이전 스크린 모드
|
||||
<kbd>ctrl+s</kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: Diff 메뉴 열기
|
||||
<kbd>ctrl+e</kbd>: Diff 메뉴 열기
|
||||
<kbd>@</kbd>: 명령어 로그 메뉴 열기
|
||||
<kbd>ctrl+w</kbd>: 공백문자를 Diff 뷰에서 표시 여부 전환
|
||||
<kbd>}</kbd>: diff 보기의 변경 사항 주위에 표시되는 컨텍스트의 크기를 늘리기
|
||||
<kbd>{</kbd>: diff 보기의 변경 사항 주위에 표시되는 컨텍스트 크기 줄이기
|
||||
<kbd>:</kbd>: execute custom command
|
||||
<kbd>ctrl+p</kbd>: 커스텀 Patch 옵션 보기
|
||||
<kbd>m</kbd>: view merge/rebase options
|
||||
<kbd>R</kbd>: 새로고침
|
||||
<kbd>+</kbd>: 다음 스크린 모드 (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: 이전 스크린 모드
|
||||
<kbd>?</kbd>: 매뉴 열기
|
||||
<kbd>ctrl+s</kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: Diff 메뉴 열기
|
||||
<kbd>ctrl+e</kbd>: Diff 메뉴 열기
|
||||
<kbd>ctrl+w</kbd>: 공백문자를 Diff 뷰에서 표시 여부 전환
|
||||
<kbd>z</kbd>: 되돌리기 (reflog) (실험적)
|
||||
<kbd>ctrl+z</kbd>: 다시 실행 (reflog) (실험적)
|
||||
<kbd>P</kbd>: 푸시
|
||||
@ -83,6 +83,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
## 메뉴
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 실행
|
||||
<kbd>esc</kbd>: 닫기
|
||||
</pre>
|
||||
|
||||
## 메인 패널 (Merging)
|
||||
|
||||
<pre>
|
||||
@ -168,8 +175,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## 상태
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: 설정 파일 수정
|
||||
<kbd>o</kbd>: 설정 파일 열기
|
||||
<kbd>e</kbd>: 설정 파일 수정
|
||||
<kbd>u</kbd>: 업데이트 확인
|
||||
<kbd>enter</kbd>: 최근에 사용한 저장소로 전환
|
||||
<kbd>a</kbd>: 모든 브랜치 로그 표시
|
||||
@ -259,6 +266,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>`</kbd>: 파일 트리뷰로 전환
|
||||
</pre>
|
||||
|
||||
## 커밋메시지
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 확인
|
||||
<kbd>esc</kbd>: 닫기
|
||||
</pre>
|
||||
|
||||
## 태그
|
||||
|
||||
<pre>
|
||||
@ -295,3 +309,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>M</kbd>: git mergetool를 열기
|
||||
<kbd>f</kbd>: fetch
|
||||
</pre>
|
||||
|
||||
## 확인 패널
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 확인
|
||||
<kbd>esc</kbd>: 닫기/취소
|
||||
</pre>
|
||||
|
@ -8,20 +8,20 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>ctrl+r</kbd>: wissel naar een recente repo
|
||||
<kbd>pgup</kbd>: scroll naar beneden vanaf hoofdpaneel (fn+up/shift+k)
|
||||
<kbd>pgdown</kbd>: scroll naar beneden vanaf hoofdpaneel (fn+down/shift+j)
|
||||
<kbd>m</kbd>: bekijk merge/rebase opties
|
||||
<kbd>ctrl+p</kbd>: bekijk aangepaste patch opties
|
||||
<kbd>R</kbd>: verversen
|
||||
<kbd>?</kbd>: open menu
|
||||
<kbd>+</kbd>: volgende scherm modus (normaal/half/groot)
|
||||
<kbd>_</kbd>: vorige scherm modus
|
||||
<kbd>ctrl+s</kbd>: bekijk scoping opties
|
||||
<kbd>W</kbd>: open diff menu
|
||||
<kbd>ctrl+e</kbd>: open diff menu
|
||||
<kbd>@</kbd>: open command log menu
|
||||
<kbd>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||
<kbd>}</kbd>: Increase the size of the context shown around changes in the diff view
|
||||
<kbd>{</kbd>: Decrease the size of the context shown around changes in the diff view
|
||||
<kbd>:</kbd>: voer aangepaste commando uit
|
||||
<kbd>ctrl+p</kbd>: bekijk aangepaste patch opties
|
||||
<kbd>m</kbd>: bekijk merge/rebase opties
|
||||
<kbd>R</kbd>: verversen
|
||||
<kbd>+</kbd>: volgende scherm modus (normaal/half/groot)
|
||||
<kbd>_</kbd>: vorige scherm modus
|
||||
<kbd>?</kbd>: open menu
|
||||
<kbd>ctrl+s</kbd>: bekijk scoping opties
|
||||
<kbd>W</kbd>: open diff menu
|
||||
<kbd>ctrl+e</kbd>: open diff menu
|
||||
<kbd>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||
<kbd>z</kbd>: ongedaan maken (via reflog) (experimenteel)
|
||||
<kbd>ctrl+z</kbd>: redo (via reflog) (experimenteel)
|
||||
<kbd>P</kbd>: push
|
||||
@ -68,6 +68,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>f</kbd>: fetch
|
||||
</pre>
|
||||
|
||||
## Bevestigingspaneel
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: bevestig
|
||||
<kbd>esc</kbd>: sluiten
|
||||
</pre>
|
||||
|
||||
## Branches
|
||||
|
||||
<pre>
|
||||
@ -91,6 +98,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>enter</kbd>: bekijk commits
|
||||
</pre>
|
||||
|
||||
## Commit Bericht
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: bevestig
|
||||
<kbd>esc</kbd>: sluiten
|
||||
</pre>
|
||||
|
||||
## Commit bestanden
|
||||
|
||||
<pre>
|
||||
@ -138,6 +152,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>enter</kbd>: bekijk gecommite bestanden
|
||||
</pre>
|
||||
|
||||
## Menu
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: uitvoeren
|
||||
<kbd>esc</kbd>: sluiten
|
||||
</pre>
|
||||
|
||||
## Mergen
|
||||
|
||||
<pre>
|
||||
@ -250,8 +271,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: verander config bestand
|
||||
<kbd>o</kbd>: open config bestand
|
||||
<kbd>e</kbd>: verander config bestand
|
||||
<kbd>u</kbd>: check voor updates
|
||||
<kbd>enter</kbd>: wissel naar een recente repo
|
||||
<kbd>a</kbd>: alle logs van de branch laten zien
|
||||
|
@ -8,20 +8,20 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>ctrl+r</kbd>: switch to a recent repo
|
||||
<kbd>pgup</kbd>: scroll up main panel (fn+up/shift+k)
|
||||
<kbd>pgdown</kbd>: scroll down main panel (fn+down/shift+j)
|
||||
<kbd>m</kbd>: widok scalenia/opcje zmiany bazy
|
||||
<kbd>ctrl+p</kbd>: view custom patch options
|
||||
<kbd>R</kbd>: odśwież
|
||||
<kbd>?</kbd>: open menu
|
||||
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: prev screen mode
|
||||
<kbd>ctrl+s</kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: open diff menu
|
||||
<kbd>ctrl+e</kbd>: open diff menu
|
||||
<kbd>@</kbd>: open command log menu
|
||||
<kbd>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||
<kbd>}</kbd>: Increase the size of the context shown around changes in the diff view
|
||||
<kbd>{</kbd>: Decrease the size of the context shown around changes in the diff view
|
||||
<kbd>:</kbd>: wykonaj własną komendę
|
||||
<kbd>ctrl+p</kbd>: view custom patch options
|
||||
<kbd>m</kbd>: widok scalenia/opcje zmiany bazy
|
||||
<kbd>R</kbd>: odśwież
|
||||
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: prev screen mode
|
||||
<kbd>?</kbd>: open menu
|
||||
<kbd>ctrl+s</kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: open diff menu
|
||||
<kbd>ctrl+e</kbd>: open diff menu
|
||||
<kbd>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||
<kbd>z</kbd>: undo (via reflog) (experimental)
|
||||
<kbd>ctrl+z</kbd>: redo (via reflog) (experimental)
|
||||
<kbd>P</kbd>: push
|
||||
@ -42,6 +42,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>[</kbd>: previous tab
|
||||
</pre>
|
||||
|
||||
## Commit Message
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: potwierdź
|
||||
<kbd>esc</kbd>: zamknij
|
||||
</pre>
|
||||
|
||||
## Commity
|
||||
|
||||
<pre>
|
||||
@ -75,6 +82,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>enter</kbd>: przeglądaj pliki commita
|
||||
</pre>
|
||||
|
||||
## Confirmation Panel
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: potwierdź
|
||||
<kbd>esc</kbd>: zamknij
|
||||
</pre>
|
||||
|
||||
## Local Branches
|
||||
|
||||
<pre>
|
||||
@ -113,6 +127,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>esc</kbd>: wyście z trybu "linia po linii"
|
||||
</pre>
|
||||
|
||||
## Menu
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: wykonaj
|
||||
<kbd>esc</kbd>: zamknij
|
||||
</pre>
|
||||
|
||||
## Pliki
|
||||
|
||||
<pre>
|
||||
@ -243,8 +264,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edytuj konfigurację
|
||||
<kbd>o</kbd>: otwórz konfigurację
|
||||
<kbd>e</kbd>: edytuj konfigurację
|
||||
<kbd>u</kbd>: sprawdź aktualizacje
|
||||
<kbd>enter</kbd>: switch to a recent repo
|
||||
<kbd>a</kbd>: pokaż wszystkie logi gałęzi
|
||||
|
@ -8,20 +8,20 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>ctrl+r</kbd>: 切换到最近的仓库
|
||||
<kbd>pgup</kbd>: 向上滚动主面板 (fn+up/shift+k)
|
||||
<kbd>pgdown</kbd>: 向下滚动主面板 (fn+down/shift+j)
|
||||
<kbd>m</kbd>: 查看 合并/变基 选项
|
||||
<kbd>ctrl+p</kbd>: 查看自定义补丁选项
|
||||
<kbd>R</kbd>: 刷新
|
||||
<kbd>?</kbd>: 打开菜单
|
||||
<kbd>+</kbd>: 下一屏模式(正常/半屏/全屏)
|
||||
<kbd>_</kbd>: 上一屏模式
|
||||
<kbd>ctrl+s</kbd>: 查看按路径过滤选项
|
||||
<kbd>W</kbd>: 打开 diff 菜单
|
||||
<kbd>ctrl+e</kbd>: 打开 diff 菜单
|
||||
<kbd>@</kbd>: 打开命令日志菜单
|
||||
<kbd>ctrl+w</kbd>: 切换是否在差异视图中显示空白字符差异
|
||||
<kbd>}</kbd>: 扩大差异视图中显示的上下文范围
|
||||
<kbd>{</kbd>: 缩小差异视图中显示的上下文范围
|
||||
<kbd>:</kbd>: 执行自定义命令
|
||||
<kbd>ctrl+p</kbd>: 查看自定义补丁选项
|
||||
<kbd>m</kbd>: 查看 合并/变基 选项
|
||||
<kbd>R</kbd>: 刷新
|
||||
<kbd>+</kbd>: 下一屏模式(正常/半屏/全屏)
|
||||
<kbd>_</kbd>: 上一屏模式
|
||||
<kbd>?</kbd>: 打开菜单
|
||||
<kbd>ctrl+s</kbd>: 查看按路径过滤选项
|
||||
<kbd>W</kbd>: 打开 diff 菜单
|
||||
<kbd>ctrl+e</kbd>: 打开 diff 菜单
|
||||
<kbd>ctrl+w</kbd>: 切换是否在差异视图中显示空白字符差异
|
||||
<kbd>z</kbd>: (通过 reflog)撤销「实验功能」
|
||||
<kbd>ctrl+z</kbd>: (通过 reflog)重做「实验功能」
|
||||
<kbd>P</kbd>: 推送
|
||||
@ -155,6 +155,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>`</kbd>: 切换文件树视图
|
||||
</pre>
|
||||
|
||||
## 提交讯息
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 确认
|
||||
<kbd>esc</kbd>: 关闭
|
||||
</pre>
|
||||
|
||||
## 文件
|
||||
|
||||
<pre>
|
||||
@ -254,13 +261,27 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## 状态
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: 编辑配置文件
|
||||
<kbd>o</kbd>: 打开配置文件
|
||||
<kbd>e</kbd>: 编辑配置文件
|
||||
<kbd>u</kbd>: 检查更新
|
||||
<kbd>enter</kbd>: 切换到最近的仓库
|
||||
<kbd>a</kbd>: 显示所有分支的日志
|
||||
</pre>
|
||||
|
||||
## 确认面板
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 确认
|
||||
<kbd>esc</kbd>: 关闭
|
||||
</pre>
|
||||
|
||||
## 菜单
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 执行
|
||||
<kbd>esc</kbd>: 关闭
|
||||
</pre>
|
||||
|
||||
## 贮藏
|
||||
|
||||
<pre>
|
||||
|
@ -33,7 +33,6 @@ type App struct {
|
||||
Config config.AppConfigurer
|
||||
OSCommand *oscommands.OSCommand
|
||||
Gui *gui.Gui
|
||||
Updater *updates.Updater // may only need this on the Gui
|
||||
}
|
||||
|
||||
func Run(
|
||||
@ -87,8 +86,7 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
|
||||
|
||||
app.OSCommand = oscommands.NewOSCommand(common, config, oscommands.GetPlatform(), oscommands.NewNullGuiIO(app.Log))
|
||||
|
||||
var err error
|
||||
app.Updater, err = updates.NewUpdater(common, config, app.OSCommand)
|
||||
updater, err := updates.NewUpdater(common, config, app.OSCommand)
|
||||
if err != nil {
|
||||
return app, err
|
||||
}
|
||||
@ -108,7 +106,7 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
|
||||
return app, err
|
||||
}
|
||||
|
||||
app.Gui, err = gui.NewGui(common, config, gitVersion, app.Updater, showRecentRepos, dirName)
|
||||
app.Gui, err = gui.NewGui(common, config, gitVersion, updater, showRecentRepos, dirName)
|
||||
if err != nil {
|
||||
return app, err
|
||||
}
|
||||
|
@ -1,131 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
// statusManager's job is to handle rendering of loading states and toast notifications
|
||||
// that you see at the bottom left of the screen.
|
||||
type statusManager struct {
|
||||
statuses []appStatus
|
||||
nextId int
|
||||
mutex deadlock.Mutex
|
||||
}
|
||||
|
||||
type appStatus struct {
|
||||
message string
|
||||
statusType string
|
||||
id int
|
||||
}
|
||||
|
||||
func (m *statusManager) removeStatus(id int) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
m.statuses = slices.Filter(m.statuses, func(status appStatus) bool {
|
||||
return status.id != id
|
||||
})
|
||||
}
|
||||
|
||||
func (m *statusManager) addWaitingStatus(message string) int {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
m.nextId += 1
|
||||
id := m.nextId
|
||||
|
||||
newStatus := appStatus{
|
||||
message: message,
|
||||
statusType: "waiting",
|
||||
id: id,
|
||||
}
|
||||
m.statuses = append([]appStatus{newStatus}, m.statuses...)
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func (m *statusManager) addToastStatus(message string) int {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
m.nextId++
|
||||
id := m.nextId
|
||||
|
||||
newStatus := appStatus{
|
||||
message: message,
|
||||
statusType: "toast",
|
||||
id: id,
|
||||
}
|
||||
m.statuses = append([]appStatus{newStatus}, m.statuses...)
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
m.removeStatus(id)
|
||||
}()
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func (m *statusManager) getStatusString() string {
|
||||
if len(m.statuses) == 0 {
|
||||
return ""
|
||||
}
|
||||
topStatus := m.statuses[0]
|
||||
if topStatus.statusType == "waiting" {
|
||||
return topStatus.message + " " + utils.Loader()
|
||||
}
|
||||
return topStatus.message
|
||||
}
|
||||
|
||||
func (m *statusManager) showStatus() bool {
|
||||
return len(m.statuses) > 0
|
||||
}
|
||||
|
||||
func (gui *Gui) toast(message string) {
|
||||
gui.statusManager.addToastStatus(message)
|
||||
|
||||
gui.renderAppStatus()
|
||||
}
|
||||
|
||||
func (gui *Gui) renderAppStatus() {
|
||||
go utils.Safe(func() {
|
||||
ticker := time.NewTicker(time.Millisecond * 50)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
appStatus := gui.statusManager.getStatusString()
|
||||
gui.c.OnUIThread(func() error {
|
||||
return gui.renderString(gui.Views.AppStatus, appStatus)
|
||||
})
|
||||
|
||||
if appStatus == "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// withWaitingStatus wraps a function and shows a waiting status while the function is still executing
|
||||
func (gui *Gui) withWaitingStatus(message string, f func() error) error {
|
||||
go utils.Safe(func() {
|
||||
id := gui.statusManager.addWaitingStatus(message)
|
||||
|
||||
defer func() {
|
||||
gui.statusManager.removeStatus(id)
|
||||
}()
|
||||
|
||||
gui.renderAppStatus()
|
||||
|
||||
if err := f(); err != nil {
|
||||
gui.c.OnUIThread(func() error {
|
||||
return gui.c.Error(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
@ -4,18 +4,33 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) startBackgroundRoutines() {
|
||||
userConfig := gui.UserConfig
|
||||
type BackgroundRoutineMgr struct {
|
||||
gui *Gui
|
||||
|
||||
// if we've suspended the gui (e.g. because we've switched to a subprocess)
|
||||
// we typically want to pause some things that are running like background
|
||||
// file refreshes
|
||||
pauseBackgroundThreads bool
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) PauseBackgroundThreads(pause bool) {
|
||||
self.pauseBackgroundThreads = pause
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
||||
userConfig := self.gui.UserConfig
|
||||
|
||||
if userConfig.Git.AutoFetch {
|
||||
fetchInterval := userConfig.Refresher.FetchInterval
|
||||
if fetchInterval > 0 {
|
||||
go utils.Safe(gui.startBackgroundFetch)
|
||||
go utils.Safe(self.startBackgroundFetch)
|
||||
} else {
|
||||
gui.c.Log.Errorf(
|
||||
self.gui.c.Log.Errorf(
|
||||
"Value of config option 'refresher.fetchInterval' (%d) is invalid, disabling auto-fetch",
|
||||
fetchInterval)
|
||||
}
|
||||
@ -24,42 +39,44 @@ func (gui *Gui) startBackgroundRoutines() {
|
||||
if userConfig.Git.AutoRefresh {
|
||||
refreshInterval := userConfig.Refresher.RefreshInterval
|
||||
if refreshInterval > 0 {
|
||||
gui.goEvery(time.Second*time.Duration(refreshInterval), gui.stopChan, gui.refreshFilesAndSubmodules)
|
||||
self.goEvery(time.Second*time.Duration(refreshInterval), self.gui.stopChan, func() error {
|
||||
return self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
})
|
||||
} else {
|
||||
gui.c.Log.Errorf(
|
||||
self.gui.c.Log.Errorf(
|
||||
"Value of config option 'refresher.refreshInterval' (%d) is invalid, disabling auto-refresh",
|
||||
refreshInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) startBackgroundFetch() {
|
||||
gui.waitForIntro.Wait()
|
||||
isNew := gui.IsNewRepo
|
||||
userConfig := gui.UserConfig
|
||||
func (self *BackgroundRoutineMgr) startBackgroundFetch() {
|
||||
self.gui.waitForIntro.Wait()
|
||||
isNew := self.gui.IsNewRepo
|
||||
userConfig := self.gui.UserConfig
|
||||
if !isNew {
|
||||
time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second)
|
||||
}
|
||||
err := gui.backgroundFetch()
|
||||
err := self.backgroundFetch()
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
|
||||
_ = gui.c.Alert(gui.c.Tr.NoAutomaticGitFetchTitle, gui.c.Tr.NoAutomaticGitFetchBody)
|
||||
_ = self.gui.c.Alert(self.gui.c.Tr.NoAutomaticGitFetchTitle, self.gui.c.Tr.NoAutomaticGitFetchBody)
|
||||
} else {
|
||||
gui.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), gui.stopChan, func() error {
|
||||
err := gui.backgroundFetch()
|
||||
gui.render()
|
||||
self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, func() error {
|
||||
err := self.backgroundFetch()
|
||||
self.gui.c.Render()
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
|
||||
func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
|
||||
go utils.Safe(func() {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if gui.PauseBackgroundThreads {
|
||||
if self.pauseBackgroundThreads {
|
||||
continue
|
||||
}
|
||||
_ = function()
|
||||
@ -69,3 +86,11 @@ func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function fun
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) backgroundFetch() (err error) {
|
||||
err = self.gui.git.Sync.Fetch(git_commands.FetchOptions{Background: true})
|
||||
|
||||
_ = self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
|
||||
|
||||
return err
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
package gui
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
func (gui *Gui) branchesRenderToMain() error {
|
||||
var task types.UpdateTask
|
||||
branch := gui.State.Contexts.Branches.GetSelected()
|
||||
if branch == nil {
|
||||
task = types.NewRenderStringTask(gui.c.Tr.NoBranchesThisRepo)
|
||||
} else {
|
||||
cmdObj := gui.git.Branch.GetGraphCmdObj(branch.FullRefName())
|
||||
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.c.Tr.LogTitle,
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
func (gui *Gui) commitFilesRenderToMain() error {
|
||||
node := gui.State.Contexts.CommitFiles.GetSelected()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ref := gui.State.Contexts.CommitFiles.GetRef()
|
||||
to := ref.RefName()
|
||||
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
|
||||
cmdObj := gui.git.WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false,
|
||||
gui.IgnoreWhitespaceInDiffView)
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
pair := gui.c.MainViewPairs().Normal
|
||||
if node.File != nil {
|
||||
pair = gui.c.MainViewPairs().PatchBuilding
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: pair,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.Tr.Patch,
|
||||
Task: task,
|
||||
},
|
||||
Secondary: gui.secondaryPatchPanelUpdateOpts(),
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) SwitchToCommitFilesContext(opts controllers.SwitchToCommitFilesContextOpts) error {
|
||||
gui.State.Contexts.CommitFiles.SetSelectedLineIdx(0)
|
||||
gui.State.Contexts.CommitFiles.SetRef(opts.Ref)
|
||||
gui.State.Contexts.CommitFiles.SetTitleRef(opts.Ref.Description())
|
||||
gui.State.Contexts.CommitFiles.SetCanRebase(opts.CanRebase)
|
||||
gui.State.Contexts.CommitFiles.SetParentContext(opts.Context)
|
||||
gui.State.Contexts.CommitFiles.SetWindowName(opts.Context.GetWindowName())
|
||||
|
||||
if err := gui.c.Refresh(types.RefreshOptions{
|
||||
Scope: []types.RefreshableView{types.COMMIT_FILES},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.c.PushContext(gui.State.Contexts.CommitFiles)
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) handleCommitMessageFocused() error {
|
||||
message := utils.ResolvePlaceholderString(
|
||||
gui.c.Tr.CommitMessageConfirm,
|
||||
map[string]string{
|
||||
"keyBindClose": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Return),
|
||||
"keyBindConfirm": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Confirm),
|
||||
},
|
||||
)
|
||||
|
||||
gui.RenderCommitLength()
|
||||
|
||||
return gui.renderString(gui.Views.Options, message)
|
||||
}
|
||||
|
||||
func (gui *Gui) RenderCommitLength() {
|
||||
if !gui.c.UserConfig.Gui.CommitLength.Show {
|
||||
return
|
||||
}
|
||||
|
||||
gui.Views.CommitMessage.Subtitle = getBufferLength(gui.Views.CommitMessage)
|
||||
}
|
||||
|
||||
func getBufferLength(view *gocui.View) string {
|
||||
return " " + strconv.Itoa(strings.Count(view.TextArea.GetContent(), "")-1) + " "
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// after selecting the 200th commit, we'll load in all the rest
|
||||
const COMMIT_THRESHOLD = 200
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) getSelectedLocalCommit() *models.Commit {
|
||||
return gui.State.Contexts.LocalCommits.GetSelected()
|
||||
}
|
||||
|
||||
func (gui *Gui) onCommitFocus() error {
|
||||
context := gui.State.Contexts.LocalCommits
|
||||
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
|
||||
context.SetLimitCommits(false)
|
||||
go utils.Safe(func() {
|
||||
if err := gui.refreshCommitsWithLimit(); err != nil {
|
||||
_ = gui.c.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) branchCommitsRenderToMain() error {
|
||||
var task types.UpdateTask
|
||||
commit := gui.State.Contexts.LocalCommits.GetSelected()
|
||||
if commit == nil {
|
||||
task = types.NewRenderStringTask(gui.c.Tr.NoCommitsThisBranch)
|
||||
} else if commit.Action == todo.UpdateRef {
|
||||
task = types.NewRenderStringTask(
|
||||
utils.ResolvePlaceholderString(
|
||||
gui.c.Tr.UpdateRefHere,
|
||||
map[string]string{
|
||||
"ref": commit.Name,
|
||||
}))
|
||||
} else {
|
||||
cmdObj := gui.git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath(),
|
||||
gui.IgnoreWhitespaceInDiffView)
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Patch",
|
||||
Task: task,
|
||||
},
|
||||
Secondary: gui.secondaryPatchPanelUpdateOpts(),
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) secondaryPatchPanelUpdateOpts() *types.ViewUpdateOpts {
|
||||
if gui.git.Patch.PatchBuilder.Active() {
|
||||
patch := gui.git.Patch.PatchBuilder.RenderAggregatedPatch(false)
|
||||
|
||||
return &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(patch),
|
||||
Title: gui.Tr.CustomPatch,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) refForLog() string {
|
||||
bisectInfo := gui.git.Bisect.GetInfo()
|
||||
gui.State.Model.BisectInfo = bisectInfo
|
||||
|
||||
if !bisectInfo.Started() {
|
||||
return "HEAD"
|
||||
}
|
||||
|
||||
// need to see if our bisect's current commit is reachable from our 'new' ref.
|
||||
if bisectInfo.Bisecting() && !gui.git.Bisect.ReachableFromStart(bisectInfo) {
|
||||
return bisectInfo.GetNewSha()
|
||||
}
|
||||
|
||||
return bisectInfo.GetStartSha()
|
||||
}
|
@ -1,311 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// This file is for the rendering of confirmation panels along with setting and handling associated
|
||||
// keybindings.
|
||||
|
||||
func (gui *Gui) wrappedConfirmationFunction(cancel context.CancelFunc, function func() error) func() error {
|
||||
return func() error {
|
||||
cancel()
|
||||
|
||||
if err := gui.c.PopContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if function != nil {
|
||||
if err := function(); err != nil {
|
||||
return gui.c.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) wrappedPromptConfirmationFunction(cancel context.CancelFunc, function func(string) error, getResponse func() string) func() error {
|
||||
return func() error {
|
||||
cancel()
|
||||
|
||||
if err := gui.c.PopContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if function != nil {
|
||||
if err := function(getResponse()); err != nil {
|
||||
return gui.c.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) deactivateConfirmationPrompt() {
|
||||
gui.Mutexes.PopupMutex.Lock()
|
||||
gui.State.CurrentPopupOpts = nil
|
||||
gui.Mutexes.PopupMutex.Unlock()
|
||||
|
||||
gui.Views.Confirmation.Visible = false
|
||||
gui.Views.Suggestions.Visible = false
|
||||
|
||||
gui.clearConfirmationViewKeyBindings()
|
||||
}
|
||||
|
||||
func (gui *Gui) getMessageHeight(wrap bool, message string, width int) int {
|
||||
lines := strings.Split(message, "\n")
|
||||
lineCount := 0
|
||||
// if we need to wrap, calculate height to fit content within view's width
|
||||
if wrap {
|
||||
for _, line := range lines {
|
||||
lineCount += runewidth.StringWidth(line)/width + 1
|
||||
}
|
||||
} else {
|
||||
lineCount = len(lines)
|
||||
}
|
||||
return lineCount
|
||||
}
|
||||
|
||||
func (gui *Gui) getPopupPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) {
|
||||
return gui.getPopupPanelDimensionsAux(panelWidth, contentHeight)
|
||||
}
|
||||
|
||||
func (gui *Gui) getPopupPanelDimensionsAux(panelWidth int, panelHeight int) (int, int, int, int) {
|
||||
width, height := gui.g.Size()
|
||||
if panelHeight > height*3/4 {
|
||||
panelHeight = height * 3 / 4
|
||||
}
|
||||
return width/2 - panelWidth/2,
|
||||
height/2 - panelHeight/2 - panelHeight%2 - 1,
|
||||
width/2 + panelWidth/2,
|
||||
height/2 + panelHeight/2
|
||||
}
|
||||
|
||||
func (gui *Gui) getConfirmationPanelWidth() int {
|
||||
width, _ := gui.g.Size()
|
||||
// we want a minimum width up to a point, then we do it based on ratio.
|
||||
panelWidth := 4 * width / 7
|
||||
minWidth := 80
|
||||
if panelWidth < minWidth {
|
||||
if width-2 < minWidth {
|
||||
panelWidth = width - 2
|
||||
} else {
|
||||
panelWidth = minWidth
|
||||
}
|
||||
}
|
||||
|
||||
return panelWidth
|
||||
}
|
||||
|
||||
func (gui *Gui) prepareConfirmationPanel(
|
||||
ctx context.Context,
|
||||
opts types.ConfirmOpts,
|
||||
) error {
|
||||
gui.Views.Confirmation.HasLoader = opts.HasLoader
|
||||
if opts.HasLoader {
|
||||
gui.g.StartTicking(ctx)
|
||||
}
|
||||
gui.Views.Confirmation.Title = opts.Title
|
||||
// for now we do not support wrapping in our editor
|
||||
gui.Views.Confirmation.Wrap = !opts.Editable
|
||||
gui.Views.Confirmation.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Confirmation.Mask = runeForMask(opts.Mask)
|
||||
_ = gui.Views.Confirmation.SetOrigin(0, 0)
|
||||
|
||||
gui.findSuggestions = opts.FindSuggestionsFunc
|
||||
if opts.FindSuggestionsFunc != nil {
|
||||
suggestionsView := gui.Views.Suggestions
|
||||
suggestionsView.Wrap = false
|
||||
suggestionsView.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.setSuggestions(opts.FindSuggestionsFunc(""))
|
||||
suggestionsView.Visible = true
|
||||
suggestionsView.Title = fmt.Sprintf(gui.c.Tr.SuggestionsTitle, gui.c.UserConfig.Keybinding.Universal.TogglePanel)
|
||||
}
|
||||
|
||||
gui.resizeConfirmationPanel()
|
||||
return nil
|
||||
}
|
||||
|
||||
func runeForMask(mask bool) rune {
|
||||
if mask {
|
||||
return '*'
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (gui *Gui) createPopupPanel(ctx context.Context, opts types.CreatePopupPanelOpts) error {
|
||||
gui.Mutexes.PopupMutex.Lock()
|
||||
defer gui.Mutexes.PopupMutex.Unlock()
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
// we don't allow interruptions of non-loader popups in case we get stuck somehow
|
||||
// e.g. a credentials popup never gets its required user input so a process hangs
|
||||
// forever.
|
||||
// The proper solution is to have a queue of popup options
|
||||
if gui.State.CurrentPopupOpts != nil && !gui.State.CurrentPopupOpts.HasLoader {
|
||||
gui.Log.Error("ignoring create popup panel because a popup panel is already open")
|
||||
cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
// remove any previous keybindings
|
||||
gui.clearConfirmationViewKeyBindings()
|
||||
|
||||
err := gui.prepareConfirmationPanel(
|
||||
ctx,
|
||||
types.ConfirmOpts{
|
||||
Title: opts.Title,
|
||||
Prompt: opts.Prompt,
|
||||
HasLoader: opts.HasLoader,
|
||||
FindSuggestionsFunc: opts.FindSuggestionsFunc,
|
||||
Editable: opts.Editable,
|
||||
Mask: opts.Mask,
|
||||
})
|
||||
if err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
confirmationView := gui.Views.Confirmation
|
||||
confirmationView.Editable = opts.Editable
|
||||
confirmationView.Editor = gocui.EditorFunc(gui.promptEditor)
|
||||
|
||||
if opts.Editable {
|
||||
textArea := confirmationView.TextArea
|
||||
textArea.Clear()
|
||||
textArea.TypeString(opts.Prompt)
|
||||
gui.resizeConfirmationPanel()
|
||||
confirmationView.RenderTextArea()
|
||||
} else {
|
||||
if err := gui.renderString(confirmationView, style.AttrBold.Sprint(opts.Prompt)); err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := gui.setKeyBindings(cancel, opts); err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
gui.State.CurrentPopupOpts = &opts
|
||||
|
||||
return gui.c.PushContext(gui.State.Contexts.Confirmation)
|
||||
}
|
||||
|
||||
func (gui *Gui) setKeyBindings(cancel context.CancelFunc, opts types.CreatePopupPanelOpts) error {
|
||||
actions := utils.ResolvePlaceholderString(
|
||||
gui.c.Tr.CloseConfirm,
|
||||
map[string]string{
|
||||
"keyBindClose": "esc",
|
||||
"keyBindConfirm": "enter",
|
||||
},
|
||||
)
|
||||
|
||||
_ = gui.renderString(gui.Views.Options, actions)
|
||||
var onConfirm func() error
|
||||
if opts.HandleConfirmPrompt != nil {
|
||||
onConfirm = gui.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
|
||||
} else {
|
||||
onConfirm = gui.wrappedConfirmationFunction(cancel, opts.HandleConfirm)
|
||||
}
|
||||
|
||||
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||
onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(
|
||||
cancel,
|
||||
opts.HandleConfirmPrompt,
|
||||
gui.getSelectedSuggestionValue,
|
||||
)
|
||||
|
||||
bindings := []*types.Binding{
|
||||
{
|
||||
ViewName: "confirmation",
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
|
||||
Handler: onConfirm,
|
||||
},
|
||||
{
|
||||
ViewName: "confirmation",
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
|
||||
Handler: gui.wrappedConfirmationFunction(cancel, opts.HandleClose),
|
||||
},
|
||||
{
|
||||
ViewName: "confirmation",
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
|
||||
Handler: func() error {
|
||||
if len(gui.State.Suggestions) > 0 {
|
||||
return gui.replaceContext(gui.State.Contexts.Suggestions)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
ViewName: "suggestions",
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
|
||||
Handler: onSuggestionConfirm,
|
||||
},
|
||||
{
|
||||
ViewName: "suggestions",
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
|
||||
Handler: gui.wrappedConfirmationFunction(cancel, opts.HandleClose),
|
||||
},
|
||||
{
|
||||
ViewName: "suggestions",
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
|
||||
Handler: func() error { return gui.replaceContext(gui.State.Contexts.Confirmation) },
|
||||
},
|
||||
}
|
||||
|
||||
for _, binding := range bindings {
|
||||
if err := gui.SetKeybinding(binding); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) clearConfirmationViewKeyBindings() {
|
||||
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshSuggestions() {
|
||||
gui.suggestionsAsyncHandler.Do(func() func() {
|
||||
findSuggestionsFn := gui.findSuggestions
|
||||
if findSuggestionsFn != nil {
|
||||
suggestions := gui.findSuggestions(gui.c.GetPromptInput())
|
||||
return func() { gui.setSuggestions(suggestions) }
|
||||
} else {
|
||||
return func() {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleAskFocused() error {
|
||||
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||
|
||||
message := utils.ResolvePlaceholderString(
|
||||
gui.c.Tr.CloseConfirm,
|
||||
map[string]string{
|
||||
"keyBindClose": keybindings.Label(keybindingConfig.Universal.Return),
|
||||
"keyBindConfirm": keybindings.Label(keybindingConfig.Universal.Confirm),
|
||||
},
|
||||
)
|
||||
|
||||
return gui.renderString(gui.Views.Options, message)
|
||||
}
|
3
pkg/gui/constants/constants.go
Normal file
3
pkg/gui/constants/constants.go
Normal file
@ -0,0 +1,3 @@
|
||||
package constants
|
||||
|
||||
const SEARCH_PREFIX = "search: "
|
@ -1,12 +1,10 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/generics/maps"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
@ -17,46 +15,66 @@ import (
|
||||
// you in the menu context. When contexts are activated/deactivated certain things need
|
||||
// to happen like showing/hiding views and rendering content.
|
||||
|
||||
func (gui *Gui) popupViewNames() []string {
|
||||
popups := slices.Filter(gui.State.Contexts.Flatten(), func(c types.Context) bool {
|
||||
return c.GetKind() == types.PERSISTENT_POPUP || c.GetKind() == types.TEMPORARY_POPUP
|
||||
})
|
||||
type ContextMgr struct {
|
||||
ContextStack []types.Context
|
||||
sync.RWMutex
|
||||
gui *Gui
|
||||
|
||||
return slices.Map(popups, func(c types.Context) string {
|
||||
return c.GetViewName()
|
||||
})
|
||||
allContexts *context.ContextTree
|
||||
}
|
||||
|
||||
// use replaceContext when you don't want to return to the original context upon
|
||||
func NewContextMgr(
|
||||
gui *Gui,
|
||||
allContexts *context.ContextTree,
|
||||
) *ContextMgr {
|
||||
return &ContextMgr{
|
||||
ContextStack: []types.Context{},
|
||||
RWMutex: sync.RWMutex{},
|
||||
gui: gui,
|
||||
allContexts: allContexts,
|
||||
}
|
||||
}
|
||||
|
||||
// use when you don't want to return to the original context upon
|
||||
// hitting escape: you want to go that context's parent instead.
|
||||
func (gui *Gui) replaceContext(c types.Context) error {
|
||||
func (self *ContextMgr) Replace(c types.Context) error {
|
||||
if !c.IsFocusable() {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.State.ContextManager.Lock()
|
||||
self.Lock()
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||
gui.State.ContextManager.ContextStack = []types.Context{c}
|
||||
if len(self.ContextStack) == 0 {
|
||||
self.ContextStack = []types.Context{c}
|
||||
} else {
|
||||
// replace the last item with the given item
|
||||
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack[0:len(gui.State.ContextManager.ContextStack)-1], c)
|
||||
self.ContextStack = append(self.ContextStack[0:len(self.ContextStack)-1], c)
|
||||
}
|
||||
|
||||
defer gui.State.ContextManager.Unlock()
|
||||
defer self.Unlock()
|
||||
|
||||
return gui.activateContext(c, types.OnFocusOpts{})
|
||||
return self.activateContext(c, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (gui *Gui) pushContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
func (self *ContextMgr) Push(c types.Context, opts ...types.OnFocusOpts) error {
|
||||
if len(opts) > 1 {
|
||||
return errors.New("cannot pass multiple opts to Push")
|
||||
}
|
||||
|
||||
singleOpts := types.OnFocusOpts{}
|
||||
if len(opts) > 0 {
|
||||
// using triple dot but you should only ever pass one of these opt structs
|
||||
singleOpts = opts[0]
|
||||
}
|
||||
|
||||
if !c.IsFocusable() {
|
||||
return nil
|
||||
}
|
||||
|
||||
contextsToDeactivate, contextToActivate := gui.pushToContextStack(c)
|
||||
contextsToDeactivate, contextToActivate := self.pushToContextStack(c)
|
||||
|
||||
for _, contextToDeactivate := range contextsToDeactivate {
|
||||
if err := gui.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
|
||||
if err := self.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -65,43 +83,43 @@ func (gui *Gui) pushContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.activateContext(contextToActivate, opts)
|
||||
return self.activateContext(contextToActivate, singleOpts)
|
||||
}
|
||||
|
||||
// Adjusts the context stack based on the context that's being pushed and
|
||||
// returns (contexts to deactivate, context to activate)
|
||||
func (gui *Gui) pushToContextStack(c types.Context) ([]types.Context, types.Context) {
|
||||
func (self *ContextMgr) pushToContextStack(c types.Context) ([]types.Context, types.Context) {
|
||||
contextsToDeactivate := []types.Context{}
|
||||
|
||||
gui.State.ContextManager.Lock()
|
||||
defer gui.State.ContextManager.Unlock()
|
||||
self.Lock()
|
||||
defer self.Unlock()
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) > 0 &&
|
||||
c == gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1] {
|
||||
if len(self.ContextStack) > 0 &&
|
||||
c == self.ContextStack[len(self.ContextStack)-1] {
|
||||
// Context being pushed is already on top of the stack: nothing to
|
||||
// deactivate or activate
|
||||
return contextsToDeactivate, nil
|
||||
}
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
|
||||
if len(self.ContextStack) == 0 {
|
||||
self.ContextStack = append(self.ContextStack, c)
|
||||
} else if c.GetKind() == types.SIDE_CONTEXT {
|
||||
// if we are switching to a side context, remove all other contexts in the stack
|
||||
contextsToDeactivate = gui.State.ContextManager.ContextStack
|
||||
gui.State.ContextManager.ContextStack = []types.Context{c}
|
||||
contextsToDeactivate = self.ContextStack
|
||||
self.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
|
||||
contextsToKeep := []types.Context{}
|
||||
for _, stackContext := range gui.State.ContextManager.ContextStack {
|
||||
for _, stackContext := range self.ContextStack {
|
||||
if stackContext.GetKind() == types.MAIN_CONTEXT {
|
||||
contextsToDeactivate = append(contextsToDeactivate, stackContext)
|
||||
} else {
|
||||
contextsToKeep = append(contextsToKeep, stackContext)
|
||||
}
|
||||
}
|
||||
gui.State.ContextManager.ContextStack = append(contextsToKeep, c)
|
||||
self.ContextStack = append(contextsToKeep, c)
|
||||
} else {
|
||||
topContext := gui.currentContextWithoutLock()
|
||||
topContext := self.currentContextWithoutLock()
|
||||
|
||||
// if we're pushing the same context on, we do nothing.
|
||||
if topContext.GetKey() != c.GetKey() {
|
||||
@ -114,48 +132,48 @@ func (gui *Gui) pushToContextStack(c types.Context) ([]types.Context, types.Cont
|
||||
(topContext.GetKind() == types.MAIN_CONTEXT && c.GetKind() == types.MAIN_CONTEXT) {
|
||||
|
||||
contextsToDeactivate = append(contextsToDeactivate, topContext)
|
||||
_, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
|
||||
_, self.ContextStack = slices.Pop(self.ContextStack)
|
||||
}
|
||||
|
||||
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
|
||||
self.ContextStack = append(self.ContextStack, c)
|
||||
}
|
||||
}
|
||||
|
||||
return contextsToDeactivate, c
|
||||
}
|
||||
|
||||
func (gui *Gui) popContext() error {
|
||||
gui.State.ContextManager.Lock()
|
||||
func (self *ContextMgr) Pop() error {
|
||||
self.Lock()
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) == 1 {
|
||||
if len(self.ContextStack) == 1 {
|
||||
// cannot escape from bottommost context
|
||||
gui.State.ContextManager.Unlock()
|
||||
self.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
var currentContext types.Context
|
||||
currentContext, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
|
||||
currentContext, self.ContextStack = slices.Pop(self.ContextStack)
|
||||
|
||||
newContext := gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
|
||||
newContext := self.ContextStack[len(self.ContextStack)-1]
|
||||
|
||||
gui.State.ContextManager.Unlock()
|
||||
self.Unlock()
|
||||
|
||||
if err := gui.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
|
||||
if err := self.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.activateContext(newContext, types.OnFocusOpts{})
|
||||
return self.activateContext(newContext, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (gui *Gui) removeContexts(contextsToRemove []types.Context) error {
|
||||
gui.State.ContextManager.Lock()
|
||||
func (self *ContextMgr) RemoveContexts(contextsToRemove []types.Context) error {
|
||||
self.Lock()
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) == 1 {
|
||||
gui.State.ContextManager.Unlock()
|
||||
if len(self.ContextStack) == 1 {
|
||||
self.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
rest := lo.Filter(gui.State.ContextManager.ContextStack, func(context types.Context, _ int) bool {
|
||||
rest := lo.Filter(self.ContextStack, func(context types.Context, _ int) bool {
|
||||
for _, contextToRemove := range contextsToRemove {
|
||||
if context.GetKey() == contextToRemove.GetKey() {
|
||||
return false
|
||||
@ -163,25 +181,25 @@ func (gui *Gui) removeContexts(contextsToRemove []types.Context) error {
|
||||
}
|
||||
return true
|
||||
})
|
||||
gui.State.ContextManager.ContextStack = rest
|
||||
self.ContextStack = rest
|
||||
contextToActivate := rest[len(rest)-1]
|
||||
gui.State.ContextManager.Unlock()
|
||||
self.Unlock()
|
||||
|
||||
for _, context := range contextsToRemove {
|
||||
if err := gui.deactivateContext(context, types.OnFocusLostOpts{NewContextKey: contextToActivate.GetKey()}); err != nil {
|
||||
if err := self.deactivateContext(context, types.OnFocusLostOpts{NewContextKey: contextToActivate.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// activate the item at the top of the stack
|
||||
return gui.activateContext(contextToActivate, types.OnFocusOpts{})
|
||||
return self.activateContext(contextToActivate, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (gui *Gui) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
|
||||
view, _ := gui.g.View(c.GetViewName())
|
||||
func (self *ContextMgr) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
|
||||
view, _ := self.gui.c.GocuiGui().View(c.GetViewName())
|
||||
|
||||
if view != nil && view.IsSearching() {
|
||||
if err := gui.onSearchEscape(); err != nil {
|
||||
if err := self.gui.onSearchEscape(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -200,34 +218,17 @@ func (gui *Gui) deactivateContext(c types.Context, opts types.OnFocusLostOpts) e
|
||||
return nil
|
||||
}
|
||||
|
||||
// postRefreshUpdate is to be called on a context after the state that it depends on has been refreshed
|
||||
// if the context's view is set to another context we do nothing.
|
||||
// if the context's view is the current view we trigger a focus; re-selecting the current item.
|
||||
func (gui *Gui) postRefreshUpdate(c types.Context) error {
|
||||
if err := c.HandleRender(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gui.currentViewName() == c.GetViewName() {
|
||||
if err := c.HandleFocus(types.OnFocusOpts{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
func (self *ContextMgr) activateContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
viewName := c.GetViewName()
|
||||
v, err := gui.g.View(viewName)
|
||||
v, err := self.gui.c.GocuiGui().View(viewName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.setWindowContext(c)
|
||||
self.gui.helpers.Window.SetWindowContext(c)
|
||||
|
||||
gui.moveToTopOfWindow(c)
|
||||
if _, err := gui.g.SetCurrentView(viewName); err != nil {
|
||||
self.gui.helpers.Window.MoveToTopOfWindow(c)
|
||||
if _, err := self.gui.c.GocuiGui().SetCurrentView(viewName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -238,14 +239,9 @@ func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
|
||||
v.Visible = true
|
||||
|
||||
gui.g.Cursor = v.Editable
|
||||
self.gui.c.GocuiGui().Cursor = v.Editable
|
||||
|
||||
// render the options available for the current context at the bottom of the screen
|
||||
optionsMap := c.GetOptionsMap()
|
||||
if optionsMap == nil {
|
||||
optionsMap = gui.globalOptionsMap()
|
||||
}
|
||||
gui.renderOptionsMap(optionsMap)
|
||||
self.gui.renderContextOptionsMap(c)
|
||||
|
||||
if err := c.HandleFocus(opts); err != nil {
|
||||
return err
|
||||
@ -254,63 +250,27 @@ func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
|
||||
options := maps.MapToSlice(optionsMap, func(key string, description string) string {
|
||||
return key + ": " + description
|
||||
})
|
||||
sort.Strings(options)
|
||||
return strings.Join(options, ", ")
|
||||
func (self *ContextMgr) Current() types.Context {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
return self.currentContextWithoutLock()
|
||||
}
|
||||
|
||||
func (gui *Gui) renderOptionsMap(optionsMap map[string]string) {
|
||||
_ = gui.renderString(gui.Views.Options, gui.optionsMapToString(optionsMap))
|
||||
}
|
||||
|
||||
// // currently unused
|
||||
// func (gui *Gui) renderContextStack() string {
|
||||
// result := ""
|
||||
// for _, context := range gui.State.ContextManager.ContextStack {
|
||||
// result += string(context.GetKey()) + "\n"
|
||||
// }
|
||||
// return result
|
||||
// }
|
||||
|
||||
func (gui *Gui) currentContext() types.Context {
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
|
||||
return gui.currentContextWithoutLock()
|
||||
}
|
||||
|
||||
func (gui *Gui) currentContextWithoutLock() types.Context {
|
||||
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||
return gui.defaultSideContext()
|
||||
func (self *ContextMgr) currentContextWithoutLock() types.Context {
|
||||
if len(self.ContextStack) == 0 {
|
||||
return self.gui.defaultSideContext()
|
||||
}
|
||||
|
||||
return gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
|
||||
return self.ContextStack[len(self.ContextStack)-1]
|
||||
}
|
||||
|
||||
// the status panel is not yet a list context (and may never be), so this method is not
|
||||
// quite the same as currentSideContext()
|
||||
func (gui *Gui) currentSideListContext() types.IListContext {
|
||||
context := gui.currentSideContext()
|
||||
listContext, ok := context.(types.IListContext)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return listContext
|
||||
}
|
||||
// Note that this could return the 'status' context which is not itself a list context.
|
||||
func (self *ContextMgr) CurrentSide() types.Context {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
func (gui *Gui) currentSideContext() types.Context {
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
|
||||
stack := gui.State.ContextManager.ContextStack
|
||||
|
||||
// on startup the stack can be empty so we'll return an empty string in that case
|
||||
if len(stack) == 0 {
|
||||
return gui.defaultSideContext()
|
||||
}
|
||||
stack := self.ContextStack
|
||||
|
||||
// find the first context in the stack with the type of types.SIDE_CONTEXT
|
||||
for i := range stack {
|
||||
@ -321,22 +281,22 @@ func (gui *Gui) currentSideContext() types.Context {
|
||||
}
|
||||
}
|
||||
|
||||
return gui.defaultSideContext()
|
||||
return self.gui.defaultSideContext()
|
||||
}
|
||||
|
||||
// static as opposed to popup
|
||||
func (gui *Gui) currentStaticContext() types.Context {
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
func (self *ContextMgr) CurrentStatic() types.Context {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
return gui.currentStaticContextWithoutLock()
|
||||
return self.currentStaticContextWithoutLock()
|
||||
}
|
||||
|
||||
func (gui *Gui) currentStaticContextWithoutLock() types.Context {
|
||||
stack := gui.State.ContextManager.ContextStack
|
||||
func (self *ContextMgr) currentStaticContextWithoutLock() types.Context {
|
||||
stack := self.ContextStack
|
||||
|
||||
if len(stack) == 0 {
|
||||
return gui.defaultSideContext()
|
||||
return self.gui.defaultSideContext()
|
||||
}
|
||||
|
||||
// find the first context in the stack without a popup type
|
||||
@ -348,88 +308,43 @@ func (gui *Gui) currentStaticContextWithoutLock() types.Context {
|
||||
}
|
||||
}
|
||||
|
||||
return gui.defaultSideContext()
|
||||
return self.gui.defaultSideContext()
|
||||
}
|
||||
|
||||
func (gui *Gui) defaultSideContext() types.Context {
|
||||
if gui.State.Modes.Filtering.Active() {
|
||||
return gui.State.Contexts.LocalCommits
|
||||
} else {
|
||||
return gui.State.Contexts.Files
|
||||
func (self *ContextMgr) ForEach(f func(types.Context)) {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
for _, context := range self.gui.State.ContextMgr.ContextStack {
|
||||
f(context)
|
||||
}
|
||||
}
|
||||
|
||||
// getFocusLayout returns a manager function for when view gain and lose focus
|
||||
func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
|
||||
var previousView *gocui.View
|
||||
return func(g *gocui.Gui) error {
|
||||
newView := gui.g.CurrentView()
|
||||
// for now we don't consider losing focus to a popup panel as actually losing focus
|
||||
if newView != previousView && !gui.isPopupPanel(newView.Name()) {
|
||||
if err := gui.onViewFocusLost(previousView); err != nil {
|
||||
return err
|
||||
}
|
||||
func (self *ContextMgr) IsCurrent(c types.Context) bool {
|
||||
return self.Current().GetKey() == c.GetKey()
|
||||
}
|
||||
|
||||
previousView = newView
|
||||
// all list contexts
|
||||
func (self *ContextMgr) AllList() []types.IListContext {
|
||||
var listContexts []types.IListContext
|
||||
|
||||
for _, context := range self.allContexts.Flatten() {
|
||||
if listContext, ok := context.(types.IListContext); ok {
|
||||
listContexts = append(listContexts, listContext)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) onViewFocusLost(oldView *gocui.View) error {
|
||||
if oldView == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
oldView.Highlight = false
|
||||
|
||||
_ = oldView.SetOriginX(0)
|
||||
|
||||
return nil
|
||||
return listContexts
|
||||
}
|
||||
|
||||
func (gui *Gui) TransientContexts() []types.Context {
|
||||
return slices.Filter(gui.State.Contexts.Flatten(), func(context types.Context) bool {
|
||||
return context.IsTransient()
|
||||
})
|
||||
}
|
||||
func (self *ContextMgr) AllPatchExplorer() []types.IPatchExplorerContext {
|
||||
var listContexts []types.IPatchExplorerContext
|
||||
|
||||
func (gui *Gui) rerenderView(view *gocui.View) error {
|
||||
context, ok := gui.contextForView(view.Name())
|
||||
if !ok {
|
||||
gui.Log.Errorf("no context found for view %s", view.Name())
|
||||
return nil
|
||||
for _, context := range self.allContexts.Flatten() {
|
||||
if listContext, ok := context.(types.IPatchExplorerContext); ok {
|
||||
listContexts = append(listContexts, listContext)
|
||||
}
|
||||
}
|
||||
|
||||
return context.HandleRender()
|
||||
return listContexts
|
||||
}
|
||||
|
||||
func (gui *Gui) getSideContextSelectedItemId() string {
|
||||
currentSideContext := gui.currentSideListContext()
|
||||
if currentSideContext == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return currentSideContext.GetSelectedItemId()
|
||||
}
|
||||
|
||||
// currently unused
|
||||
// func (gui *Gui) getCurrentSideView() *gocui.View {
|
||||
// currentSideContext := gui.currentSideContext()
|
||||
// if currentSideContext == nil {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// view, _ := gui.g.View(currentSideContext.GetViewName())
|
||||
|
||||
// return view
|
||||
// }
|
||||
|
||||
// currently unused
|
||||
// func (gui *Gui) renderContextStack() string {
|
||||
// result := ""
|
||||
// for _, context := range gui.State.ContextManager.ContextStack {
|
||||
// result += context.GetViewName() + "\n"
|
||||
// }
|
||||
// return result
|
||||
// }
|
||||
|
@ -16,6 +16,9 @@ type BaseContext struct {
|
||||
keybindingsFns []types.KeybindingsFn
|
||||
mouseKeybindingsFns []types.MouseKeybindingsFn
|
||||
onClickFn func() error
|
||||
onRenderToMainFn func() error
|
||||
onFocusFn onFocusFn
|
||||
onFocusLostFn onFocusLostFn
|
||||
|
||||
focusable bool
|
||||
transient bool
|
||||
@ -25,6 +28,11 @@ type BaseContext struct {
|
||||
*ParentContextMgr
|
||||
}
|
||||
|
||||
type (
|
||||
onFocusFn = func(types.OnFocusOpts) error
|
||||
onFocusLostFn = func(types.OnFocusLostOpts) error
|
||||
)
|
||||
|
||||
var _ types.IBaseContext = &BaseContext{}
|
||||
|
||||
type NewBaseContextOpts struct {
|
||||
@ -129,6 +137,36 @@ func (self *BaseContext) GetOnClick() func() error {
|
||||
return self.onClickFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) AddOnRenderToMainFn(fn func() error) {
|
||||
if fn != nil {
|
||||
self.onRenderToMainFn = fn
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetOnRenderToMain() func() error {
|
||||
return self.onRenderToMainFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) AddOnFocusFn(fn onFocusFn) {
|
||||
if fn != nil {
|
||||
self.onFocusFn = fn
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetOnFocus() onFocusFn {
|
||||
return self.onFocusFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) AddOnFocusLostFn(fn onFocusLostFn) {
|
||||
if fn != nil {
|
||||
self.onFocusLostFn = fn
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetOnFocusLost() onFocusLostFn {
|
||||
return self.onFocusLostFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
|
||||
bindings := []*gocui.ViewMouseBinding{}
|
||||
for i := range self.mouseKeybindingsFns {
|
||||
|
@ -1,8 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -11,40 +11,40 @@ type BranchesContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*BranchesContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*BranchesContext)(nil)
|
||||
_ types.DiffableContext = (*BranchesContext)(nil)
|
||||
)
|
||||
|
||||
func NewBranchesContext(
|
||||
getModel func() []*models.Branch,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Branch { return c.Model().Branches })
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetBranchListDisplayStrings(
|
||||
c.Model().Branches,
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().Diffing.Ref,
|
||||
c.Tr,
|
||||
)
|
||||
}
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *BranchesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
|
||||
return &BranchesContext{
|
||||
self := &BranchesContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Branches,
|
||||
WindowName: "branches",
|
||||
Key: LOCAL_BRANCHES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *BranchesContext) GetSelectedItemId() string {
|
||||
@ -63,3 +63,16 @@ func (self *BranchesContext) GetSelectedRef() types.Ref {
|
||||
}
|
||||
return branch
|
||||
}
|
||||
|
||||
func (self *BranchesContext) GetDiffTerminals() []string {
|
||||
// for our local branches we want to include both the branch and its upstream
|
||||
branch := self.GetSelected()
|
||||
if branch != nil {
|
||||
names := []string{branch.ID()}
|
||||
if branch.IsTrackingRemote() {
|
||||
names = append(names, branch.ID()+"@{u}")
|
||||
}
|
||||
return names
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -13,20 +15,28 @@ type CommitFilesContext struct {
|
||||
*DynamicTitleBuilder
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*CommitFilesContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*CommitFilesContext)(nil)
|
||||
_ types.DiffableContext = (*CommitFilesContext)(nil)
|
||||
)
|
||||
|
||||
func NewCommitFilesContext(
|
||||
getModel func() []*models.CommitFile,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
viewModel := filetree.NewCommitFileTreeViewModel(
|
||||
func() []*models.CommitFile { return c.Model().CommitFiles },
|
||||
c.Log,
|
||||
c.UserConfig.Gui.ShowFileTree,
|
||||
)
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
if viewModel.Len() == 0 {
|
||||
return [][]string{{style.FgRed.Sprint("(none)")}}
|
||||
}
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *CommitFilesContext {
|
||||
viewModel := filetree.NewCommitFileTreeViewModel(getModel, c.Log, c.UserConfig.Gui.ShowFileTree)
|
||||
lines := presentation.RenderCommitFileTree(viewModel, c.Modes().Diffing.Ref, c.Git().Patch.PatchBuilder)
|
||||
return slices.Map(lines, func(line string) []string {
|
||||
return []string{line}
|
||||
})
|
||||
}
|
||||
|
||||
return &CommitFilesContext{
|
||||
CommitFileTreeViewModel: viewModel,
|
||||
@ -34,18 +44,14 @@ func NewCommitFilesContext(
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().CommitFiles,
|
||||
WindowName: "commits",
|
||||
Key: COMMIT_FILES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
}),
|
||||
ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -61,3 +67,7 @@ func (self *CommitFilesContext) GetSelectedItemId() string {
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) GetDiffTerminals() []string {
|
||||
return []string{self.GetRef().RefName()}
|
||||
}
|
||||
|
@ -1,15 +1,21 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type CommitMessageContext struct {
|
||||
c *ContextCommon
|
||||
types.Context
|
||||
viewModel *CommitMessageViewModel
|
||||
}
|
||||
|
||||
var _ types.Context = (*CommitMessageContext)(nil)
|
||||
|
||||
// when selectedIndex (see below) is set to this value, it means that we're not
|
||||
// currently viewing a commit message of an existing commit: instead we're making our own
|
||||
// new commit message
|
||||
@ -35,22 +41,21 @@ type CommitMessageViewModel struct {
|
||||
}
|
||||
|
||||
func NewCommitMessageContext(
|
||||
view *gocui.View,
|
||||
opts ContextCallbackOpts,
|
||||
c *ContextCommon,
|
||||
) *CommitMessageContext {
|
||||
viewModel := &CommitMessageViewModel{}
|
||||
return &CommitMessageContext{
|
||||
c: c,
|
||||
viewModel: viewModel,
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
View: view,
|
||||
View: c.Views().CommitMessage,
|
||||
WindowName: "commitMessage",
|
||||
Key: COMMIT_MESSAGE_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}),
|
||||
opts,
|
||||
),
|
||||
}
|
||||
}
|
||||
@ -93,3 +98,15 @@ func (self *CommitMessageContext) SetPanelState(index int, title string, preserv
|
||||
self.viewModel.onConfirm = onConfirm
|
||||
self.GetView().Title = title
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) RenderCommitLength() {
|
||||
if !self.c.UserConfig.Gui.CommitLength.Show {
|
||||
return
|
||||
}
|
||||
|
||||
self.c.Views().CommitMessage.Subtitle = getBufferLength(self.c.Views().CommitMessage)
|
||||
}
|
||||
|
||||
func getBufferLength(view *gocui.View) string {
|
||||
return " " + strconv.Itoa(strings.Count(view.TextArea.GetContent(), "")-1) + " "
|
||||
}
|
||||
|
35
pkg/gui/context/confirmation_context.go
Normal file
35
pkg/gui/context/confirmation_context.go
Normal file
@ -0,0 +1,35 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type ConfirmationContext struct {
|
||||
*SimpleContext
|
||||
c *ContextCommon
|
||||
|
||||
State ConfirmationContextState
|
||||
}
|
||||
|
||||
type ConfirmationContextState struct {
|
||||
OnConfirm func() error
|
||||
OnClose func() error
|
||||
}
|
||||
|
||||
var _ types.Context = (*ConfirmationContext)(nil)
|
||||
|
||||
func NewConfirmationContext(
|
||||
c *ContextCommon,
|
||||
) *ConfirmationContext {
|
||||
return &ConfirmationContext{
|
||||
c: c,
|
||||
SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Confirmation,
|
||||
WindowName: "confirmation",
|
||||
Key: CONFIRMATION_CONTEXT_KEY,
|
||||
Kind: types.TEMPORARY_POPUP,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
})),
|
||||
}
|
||||
}
|
@ -97,7 +97,7 @@ type ContextTree struct {
|
||||
CustomPatchBuilder *PatchExplorerContext
|
||||
CustomPatchBuilderSecondary types.Context
|
||||
MergeConflicts *MergeConflictsContext
|
||||
Confirmation types.Context
|
||||
Confirmation *ConfirmationContext
|
||||
CommitMessage *CommitMessageContext
|
||||
CommitDescription types.Context
|
||||
CommandLog types.Context
|
||||
|
11
pkg/gui/context/context_common.go
Normal file
11
pkg/gui/context/context_common.go
Normal file
@ -0,0 +1,11 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type ContextCommon struct {
|
||||
*common.Common
|
||||
types.IGuiCommon
|
||||
}
|
@ -10,11 +10,13 @@ import (
|
||||
type ListContextTrait struct {
|
||||
types.Context
|
||||
|
||||
c *types.HelperCommon
|
||||
c *ContextCommon
|
||||
list types.IList
|
||||
getDisplayStrings func(startIdx int, length int) [][]string
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) IsListContext() {}
|
||||
|
||||
func (self *ListContextTrait) GetList() types.IList {
|
||||
return self.list
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"log"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -11,36 +14,57 @@ type LocalCommitsContext struct {
|
||||
*ViewportListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*LocalCommitsContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*LocalCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*LocalCommitsContext)(nil)
|
||||
)
|
||||
|
||||
func NewLocalCommitsContext(
|
||||
getModel func() []*models.Commit,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
||||
viewModel := NewLocalCommitsViewModel(
|
||||
func() []*models.Commit { return c.Model().Commits },
|
||||
c,
|
||||
)
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
selectedCommitSha := ""
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *LocalCommitsContext {
|
||||
viewModel := NewLocalCommitsViewModel(getModel, c)
|
||||
if c.CurrentContext().GetKey() == LOCAL_COMMITS_CONTEXT_KEY {
|
||||
selectedCommit := viewModel.GetSelected()
|
||||
if selectedCommit != nil {
|
||||
selectedCommitSha = selectedCommit.Sha
|
||||
}
|
||||
}
|
||||
|
||||
showYouAreHereLabel := c.Model().WorkingTreeStateAtLastCommitRefresh == enums.REBASE_MODE_REBASING
|
||||
|
||||
return presentation.GetCommitListDisplayStrings(
|
||||
c.Common,
|
||||
c.Model().Commits,
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().CherryPicking.SelectedShaSet(),
|
||||
c.Modes().Diffing.Ref,
|
||||
c.UserConfig.Gui.TimeFormat,
|
||||
c.UserConfig.Git.ParseEmoji,
|
||||
selectedCommitSha,
|
||||
startIdx,
|
||||
length,
|
||||
shouldShowGraph(c),
|
||||
c.Model().BisectInfo,
|
||||
showYouAreHereLabel,
|
||||
)
|
||||
}
|
||||
|
||||
return &LocalCommitsContext{
|
||||
LocalCommitsViewModel: viewModel,
|
||||
ViewportListContextTrait: &ViewportListContextTrait{
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Commits,
|
||||
WindowName: "commits",
|
||||
Key: LOCAL_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -69,7 +93,7 @@ type LocalCommitsViewModel struct {
|
||||
showWholeGitGraph bool
|
||||
}
|
||||
|
||||
func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *types.HelperCommon) *LocalCommitsViewModel {
|
||||
func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *ContextCommon) *LocalCommitsViewModel {
|
||||
self := &LocalCommitsViewModel{
|
||||
BasicViewModel: NewBasicViewModel(getModel),
|
||||
limitCommits: true,
|
||||
@ -91,6 +115,12 @@ func (self *LocalCommitsContext) GetSelectedRef() types.Ref {
|
||||
return commit
|
||||
}
|
||||
|
||||
func (self *LocalCommitsContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *LocalCommitsViewModel) SetLimitCommits(value bool) {
|
||||
self.limitCommits = value
|
||||
}
|
||||
@ -110,3 +140,22 @@ func (self *LocalCommitsViewModel) GetShowWholeGitGraph() bool {
|
||||
func (self *LocalCommitsViewModel) GetCommits() []*models.Commit {
|
||||
return self.getModel()
|
||||
}
|
||||
|
||||
func shouldShowGraph(c *ContextCommon) bool {
|
||||
if c.Modes().Filtering.Active() {
|
||||
return false
|
||||
}
|
||||
|
||||
value := c.UserConfig.Git.Log.ShowGraph
|
||||
switch value {
|
||||
case "always":
|
||||
return true
|
||||
case "never":
|
||||
return false
|
||||
case "when-maximised":
|
||||
return c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL
|
||||
}
|
||||
|
||||
log.Fatalf("Unknown value for git.log.showGraph: %s. Expected one of: 'always', 'never', 'when-maximised'", value)
|
||||
return false
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
@ -16,34 +15,21 @@ type MenuContext struct {
|
||||
var _ types.IListContext = (*MenuContext)(nil)
|
||||
|
||||
func NewMenuContext(
|
||||
view *gocui.View,
|
||||
|
||||
c *types.HelperCommon,
|
||||
getOptionsMap func() map[string]string,
|
||||
renderToDescriptionView func(string),
|
||||
c *ContextCommon,
|
||||
) *MenuContext {
|
||||
viewModel := NewMenuViewModel()
|
||||
|
||||
onFocus := func(types.OnFocusOpts) error {
|
||||
selectedMenuItem := viewModel.GetSelected()
|
||||
renderToDescriptionView(selectedMenuItem.Tooltip)
|
||||
return nil
|
||||
}
|
||||
|
||||
return &MenuContext{
|
||||
MenuViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Menu,
|
||||
WindowName: "menu",
|
||||
Key: "menu",
|
||||
Kind: types.TEMPORARY_POPUP,
|
||||
OnGetOptionsMap: getOptionsMap,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
}),
|
||||
})),
|
||||
getDisplayStrings: viewModel.GetDisplayStrings,
|
||||
list: viewModel,
|
||||
c: c,
|
||||
|
@ -3,7 +3,6 @@ package context
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
@ -12,7 +11,7 @@ import (
|
||||
type MergeConflictsContext struct {
|
||||
types.Context
|
||||
viewModel *ConflictsViewModel
|
||||
c *types.HelperCommon
|
||||
c *ContextCommon
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
@ -25,12 +24,7 @@ type ConflictsViewModel struct {
|
||||
}
|
||||
|
||||
func NewMergeConflictsContext(
|
||||
view *gocui.View,
|
||||
|
||||
opts ContextCallbackOpts,
|
||||
|
||||
c *types.HelperCommon,
|
||||
getOptionsMap func() map[string]string,
|
||||
c *ContextCommon,
|
||||
) *MergeConflictsContext {
|
||||
viewModel := &ConflictsViewModel{
|
||||
state: mergeconflicts.NewState(),
|
||||
@ -43,14 +37,12 @@ func NewMergeConflictsContext(
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: view,
|
||||
View: c.Views().MergeConflicts,
|
||||
WindowName: "main",
|
||||
Key: MERGE_CONFLICTS_CONTEXT_KEY,
|
||||
OnGetOptionsMap: getOptionsMap,
|
||||
Focusable: true,
|
||||
HighlightOnFocus: true,
|
||||
}),
|
||||
opts,
|
||||
),
|
||||
c: c,
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ type PatchExplorerContext struct {
|
||||
state *patch_exploring.State
|
||||
viewTrait *ViewTrait
|
||||
getIncludedLineIndices func() []int
|
||||
c *types.HelperCommon
|
||||
c *ContextCommon
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
@ -24,11 +24,9 @@ func NewPatchExplorerContext(
|
||||
windowName string,
|
||||
key types.ContextKey,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
getIncludedLineIndices func() []int,
|
||||
|
||||
c *types.HelperCommon,
|
||||
c *ContextCommon,
|
||||
) *PatchExplorerContext {
|
||||
return &PatchExplorerContext{
|
||||
state: nil,
|
||||
@ -43,13 +41,12 @@ func NewPatchExplorerContext(
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
Focusable: true,
|
||||
HighlightOnFocus: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
}),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) IsPatchExplorerContext() {}
|
||||
|
||||
func (self *PatchExplorerContext) GetState() *patch_exploring.State {
|
||||
return self.state
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -11,35 +11,35 @@ type ReflogCommitsContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*ReflogCommitsContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*ReflogCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*ReflogCommitsContext)(nil)
|
||||
)
|
||||
|
||||
func NewReflogCommitsContext(
|
||||
getModel func() []*models.Commit,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Commit { return c.Model().FilteredReflogCommits })
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *ReflogCommitsContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetReflogCommitListDisplayStrings(
|
||||
c.Model().FilteredReflogCommits,
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().CherryPicking.SelectedShaSet(),
|
||||
c.Modes().Diffing.Ref,
|
||||
c.UserConfig.Gui.TimeFormat,
|
||||
c.UserConfig.Git.ParseEmoji,
|
||||
)
|
||||
}
|
||||
|
||||
return &ReflogCommitsContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().ReflogCommits,
|
||||
WindowName: "commits",
|
||||
Key: REFLOG_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -71,3 +71,9 @@ func (self *ReflogCommitsContext) GetSelectedRef() types.Ref {
|
||||
func (self *ReflogCommitsContext) GetCommits() []*models.Commit {
|
||||
return self.getModel()
|
||||
}
|
||||
|
||||
func (self *ReflogCommitsContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -12,37 +12,32 @@ type RemoteBranchesContext struct {
|
||||
*DynamicTitleBuilder
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*RemoteBranchesContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*RemoteBranchesContext)(nil)
|
||||
_ types.DiffableContext = (*RemoteBranchesContext)(nil)
|
||||
)
|
||||
|
||||
func NewRemoteBranchesContext(
|
||||
getModel func() []*models.RemoteBranch,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
c *ContextCommon,
|
||||
) *RemoteBranchesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
viewModel := NewBasicViewModel(func() []*models.RemoteBranch { return c.Model().RemoteBranches })
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetRemoteBranchListDisplayStrings(c.Model().RemoteBranches, c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &RemoteBranchesContext{
|
||||
BasicViewModel: viewModel,
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().RemoteBranches,
|
||||
WindowName: "branches",
|
||||
Key: REMOTE_BRANCHES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -66,3 +61,9 @@ func (self *RemoteBranchesContext) GetSelectedRef() types.Ref {
|
||||
}
|
||||
return remoteBranch
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -11,35 +11,28 @@ type RemotesContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*RemotesContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*RemotesContext)(nil)
|
||||
_ types.DiffableContext = (*RemotesContext)(nil)
|
||||
)
|
||||
|
||||
func NewRemotesContext(
|
||||
getModel func() []*models.Remote,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
func NewRemotesContext(c *ContextCommon) *RemotesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Remote { return c.Model().Remotes })
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *RemotesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetRemoteListDisplayStrings(c.Model().Remotes, c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &RemotesContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Remotes,
|
||||
WindowName: "branches",
|
||||
Key: REMOTES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -55,3 +48,9 @@ func (self *RemotesContext) GetSelectedItemId() string {
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *RemotesContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
146
pkg/gui/context/setup.go
Normal file
146
pkg/gui/context/setup.go
Normal file
@ -0,0 +1,146 @@
|
||||
package context
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
func NewContextTree(c *ContextCommon) *ContextTree {
|
||||
commitFilesContext := NewCommitFilesContext(c)
|
||||
|
||||
return &ContextTree{
|
||||
Global: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.GLOBAL_CONTEXT,
|
||||
View: nil, // TODO: see if this breaks anything
|
||||
WindowName: "",
|
||||
Key: GLOBAL_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
HasUncontrolledBounds: true, // setting to true because the global context doesn't even have a view
|
||||
}),
|
||||
),
|
||||
Status: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
View: c.Views().Status,
|
||||
WindowName: "status",
|
||||
Key: STATUS_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
),
|
||||
Files: NewWorkingTreeContext(c),
|
||||
Submodules: NewSubmodulesContext(c),
|
||||
Menu: NewMenuContext(c),
|
||||
Remotes: NewRemotesContext(c),
|
||||
RemoteBranches: NewRemoteBranchesContext(c),
|
||||
LocalCommits: NewLocalCommitsContext(c),
|
||||
CommitFiles: commitFilesContext,
|
||||
ReflogCommits: NewReflogCommitsContext(c),
|
||||
SubCommits: NewSubCommitsContext(c),
|
||||
Branches: NewBranchesContext(c),
|
||||
Tags: NewTagsContext(c),
|
||||
Stash: NewStashContext(c),
|
||||
Suggestions: NewSuggestionsContext(c),
|
||||
Normal: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: c.Views().Main,
|
||||
WindowName: "main",
|
||||
Key: NORMAL_MAIN_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
}),
|
||||
),
|
||||
NormalSecondary: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: c.Views().Secondary,
|
||||
WindowName: "secondary",
|
||||
Key: NORMAL_SECONDARY_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
}),
|
||||
),
|
||||
Staging: NewPatchExplorerContext(
|
||||
c.Views().Staging,
|
||||
"main",
|
||||
STAGING_MAIN_CONTEXT_KEY,
|
||||
func() []int { return nil },
|
||||
c,
|
||||
),
|
||||
StagingSecondary: NewPatchExplorerContext(
|
||||
c.Views().StagingSecondary,
|
||||
"secondary",
|
||||
STAGING_SECONDARY_CONTEXT_KEY,
|
||||
func() []int { return nil },
|
||||
c,
|
||||
),
|
||||
CustomPatchBuilder: NewPatchExplorerContext(
|
||||
c.Views().PatchBuilding,
|
||||
"main",
|
||||
PATCH_BUILDING_MAIN_CONTEXT_KEY,
|
||||
func() []int {
|
||||
filename := commitFilesContext.GetSelectedPath()
|
||||
includedLineIndices, err := c.Git().Patch.PatchBuilder.GetFileIncLineIndices(filename)
|
||||
if err != nil {
|
||||
c.Log.Error(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return includedLineIndices
|
||||
},
|
||||
c,
|
||||
),
|
||||
CustomPatchBuilderSecondary: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: c.Views().PatchBuildingSecondary,
|
||||
WindowName: "secondary",
|
||||
Key: PATCH_BUILDING_SECONDARY_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
}),
|
||||
),
|
||||
MergeConflicts: NewMergeConflictsContext(
|
||||
c,
|
||||
),
|
||||
Confirmation: NewConfirmationContext(c),
|
||||
CommitMessage: NewCommitMessageContext(c),
|
||||
CommitDescription: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
View: c.Views().CommitDescription,
|
||||
WindowName: "commitDescription",
|
||||
Key: COMMIT_DESCRIPTION_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}),
|
||||
),
|
||||
Search: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
View: c.Views().Search,
|
||||
WindowName: "search",
|
||||
Key: SEARCH_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
),
|
||||
CommandLog: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.EXTRAS_CONTEXT,
|
||||
View: c.Views().Extras,
|
||||
WindowName: "extras",
|
||||
Key: COMMAND_LOG_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
),
|
||||
Snake: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
View: c.Views().Snake,
|
||||
WindowName: "files",
|
||||
Key: SNAKE_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
),
|
||||
Options: NewDisplayContext(OPTIONS_CONTEXT_KEY, c.Views().Options, "options"),
|
||||
AppStatus: NewDisplayContext(APP_STATUS_CONTEXT_KEY, c.Views().AppStatus, "appStatus"),
|
||||
SearchPrefix: NewDisplayContext(SEARCH_PREFIX_CONTEXT_KEY, c.Views().SearchPrefix, "searchPrefix"),
|
||||
Information: NewDisplayContext(INFORMATION_CONTEXT_KEY, c.Views().Information, "information"),
|
||||
Limit: NewDisplayContext(LIMIT_CONTEXT_KEY, c.Views().Limit, "limit"),
|
||||
}
|
||||
}
|
@ -6,29 +6,12 @@ import (
|
||||
)
|
||||
|
||||
type SimpleContext struct {
|
||||
OnFocus func(opts types.OnFocusOpts) error
|
||||
OnFocusLost func(opts types.OnFocusLostOpts) error
|
||||
OnRender func() error
|
||||
// this is for pushing some content to the main view
|
||||
OnRenderToMain func() error
|
||||
|
||||
*BaseContext
|
||||
}
|
||||
|
||||
type ContextCallbackOpts struct {
|
||||
OnFocus func(opts types.OnFocusOpts) error
|
||||
OnFocusLost func(opts types.OnFocusLostOpts) error
|
||||
OnRender func() error
|
||||
OnRenderToMain func() error
|
||||
}
|
||||
|
||||
func NewSimpleContext(baseContext *BaseContext, opts ContextCallbackOpts) *SimpleContext {
|
||||
func NewSimpleContext(baseContext *BaseContext) *SimpleContext {
|
||||
return &SimpleContext{
|
||||
OnFocus: opts.OnFocus,
|
||||
OnFocusLost: opts.OnFocusLost,
|
||||
OnRender: opts.OnRender,
|
||||
OnRenderToMain: opts.OnRenderToMain,
|
||||
BaseContext: baseContext,
|
||||
BaseContext: baseContext,
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +28,6 @@ func NewDisplayContext(key types.ContextKey, view *gocui.View, windowName string
|
||||
Focusable: false,
|
||||
Transient: false,
|
||||
}),
|
||||
ContextCallbackOpts{},
|
||||
)
|
||||
}
|
||||
|
||||
@ -54,14 +36,14 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
|
||||
self.GetViewTrait().SetHighlight(true)
|
||||
}
|
||||
|
||||
if self.OnFocus != nil {
|
||||
if err := self.OnFocus(opts); err != nil {
|
||||
if self.onFocusFn != nil {
|
||||
if err := self.onFocusFn(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if self.OnRenderToMain != nil {
|
||||
if err := self.OnRenderToMain(); err != nil {
|
||||
if self.onRenderToMainFn != nil {
|
||||
if err := self.onRenderToMainFn(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -70,22 +52,19 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) error {
|
||||
if self.OnFocusLost != nil {
|
||||
return self.OnFocusLost(opts)
|
||||
if self.onFocusLostFn != nil {
|
||||
return self.onFocusLostFn(opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleRender() error {
|
||||
if self.OnRender != nil {
|
||||
return self.OnRender()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleRenderToMain() error {
|
||||
if self.OnRenderToMain != nil {
|
||||
return self.OnRenderToMain()
|
||||
if self.onRenderToMainFn != nil {
|
||||
return self.onRenderToMainFn()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -1,8 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -11,35 +11,30 @@ type StashContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*StashContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*StashContext)(nil)
|
||||
_ types.DiffableContext = (*StashContext)(nil)
|
||||
)
|
||||
|
||||
func NewStashContext(
|
||||
getModel func() []*models.StashEntry,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
c *ContextCommon,
|
||||
) *StashContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
viewModel := NewBasicViewModel(func() []*models.StashEntry { return c.Model().StashEntries })
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetStashEntryListDisplayStrings(c.Model().StashEntries, c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &StashContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Stash,
|
||||
WindowName: "stash",
|
||||
Key: STASH_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -67,3 +62,9 @@ func (self *StashContext) GetSelectedRef() types.Ref {
|
||||
}
|
||||
return stash
|
||||
}
|
||||
|
||||
func (self *StashContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
@ -3,8 +3,9 @@ package context
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
@ -15,23 +16,45 @@ type SubCommitsContext struct {
|
||||
*DynamicTitleBuilder
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*SubCommitsContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*SubCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*SubCommitsContext)(nil)
|
||||
)
|
||||
|
||||
func NewSubCommitsContext(
|
||||
getModel func() []*models.Commit,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
c *ContextCommon,
|
||||
) *SubCommitsContext {
|
||||
viewModel := &SubCommitsViewModel{
|
||||
BasicViewModel: NewBasicViewModel(getModel),
|
||||
ref: nil,
|
||||
limitCommits: true,
|
||||
BasicViewModel: NewBasicViewModel(
|
||||
func() []*models.Commit { return c.Model().SubCommits },
|
||||
),
|
||||
ref: nil,
|
||||
limitCommits: true,
|
||||
}
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
selectedCommitSha := ""
|
||||
if c.CurrentContext().GetKey() == SUB_COMMITS_CONTEXT_KEY {
|
||||
selectedCommit := viewModel.GetSelected()
|
||||
if selectedCommit != nil {
|
||||
selectedCommitSha = selectedCommit.Sha
|
||||
}
|
||||
}
|
||||
return presentation.GetCommitListDisplayStrings(
|
||||
c.Common,
|
||||
c.Model().SubCommits,
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().CherryPicking.SelectedShaSet(),
|
||||
c.Modes().Diffing.Ref,
|
||||
c.UserConfig.Gui.TimeFormat,
|
||||
c.UserConfig.Git.ParseEmoji,
|
||||
selectedCommitSha,
|
||||
startIdx,
|
||||
length,
|
||||
shouldShowGraph(c),
|
||||
git_commands.NewNullBisectInfo(),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
return &SubCommitsContext{
|
||||
@ -40,17 +63,13 @@ func NewSubCommitsContext(
|
||||
ViewportListContextTrait: &ViewportListContextTrait{
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().SubCommits,
|
||||
WindowName: "branches",
|
||||
Key: SUB_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -111,3 +130,9 @@ func (self *SubCommitsContext) SetLimitCommits(value bool) {
|
||||
func (self *SubCommitsContext) GetLimitCommits() bool {
|
||||
return self.limitCommits
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -13,33 +13,23 @@ type SubmodulesContext struct {
|
||||
|
||||
var _ types.IListContext = (*SubmodulesContext)(nil)
|
||||
|
||||
func NewSubmodulesContext(
|
||||
getModel func() []*models.SubmoduleConfig,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
func NewSubmodulesContext(c *ContextCommon) *SubmodulesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.SubmoduleConfig { return c.Model().Submodules })
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *SubmodulesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetSubmoduleListDisplayStrings(c.Model().Submodules)
|
||||
}
|
||||
|
||||
return &SubmodulesContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Submodules,
|
||||
WindowName: "files",
|
||||
Key: SUBMODULES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
|
@ -1,45 +1,59 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/tasks"
|
||||
)
|
||||
|
||||
type SuggestionsContext struct {
|
||||
*BasicViewModel[*types.Suggestion]
|
||||
*ListContextTrait
|
||||
|
||||
State *SuggestionsContextState
|
||||
}
|
||||
|
||||
type SuggestionsContextState struct {
|
||||
Suggestions []*types.Suggestion
|
||||
OnConfirm func() error
|
||||
OnClose func() error
|
||||
AsyncHandler *tasks.AsyncHandler
|
||||
|
||||
// FindSuggestions will take a string that the user has typed into a prompt
|
||||
// and return a slice of suggestions which match that string.
|
||||
FindSuggestions func(string) []*types.Suggestion
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*SuggestionsContext)(nil)
|
||||
|
||||
func NewSuggestionsContext(
|
||||
getModel func() []*types.Suggestion,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
c *ContextCommon,
|
||||
) *SuggestionsContext {
|
||||
state := &SuggestionsContextState{
|
||||
AsyncHandler: tasks.NewAsyncHandler(),
|
||||
}
|
||||
getModel := func() []*types.Suggestion {
|
||||
return state.Suggestions
|
||||
}
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetSuggestionListDisplayStrings(state.Suggestions)
|
||||
}
|
||||
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
|
||||
return &SuggestionsContext{
|
||||
State: state,
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Suggestions,
|
||||
WindowName: "suggestions",
|
||||
Key: SUGGESTIONS_CONTEXT_KEY,
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -55,3 +69,22 @@ func (self *SuggestionsContext) GetSelectedItemId() string {
|
||||
|
||||
return item.Value
|
||||
}
|
||||
|
||||
func (self *SuggestionsContext) SetSuggestions(suggestions []*types.Suggestion) {
|
||||
self.State.Suggestions = suggestions
|
||||
self.SetSelectedLineIdx(0)
|
||||
self.c.ResetViewOrigin(self.GetView())
|
||||
_ = self.HandleRender()
|
||||
}
|
||||
|
||||
func (self *SuggestionsContext) RefreshSuggestions() {
|
||||
self.State.AsyncHandler.Do(func() func() {
|
||||
findSuggestionsFn := self.State.FindSuggestions
|
||||
if findSuggestionsFn != nil {
|
||||
suggestions := findSuggestionsFn(self.c.GetPromptInput())
|
||||
return func() { self.SetSuggestions(suggestions) }
|
||||
} else {
|
||||
return func() {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -11,35 +11,30 @@ type TagsContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*TagsContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*TagsContext)(nil)
|
||||
_ types.DiffableContext = (*TagsContext)(nil)
|
||||
)
|
||||
|
||||
func NewTagsContext(
|
||||
getModel func() []*models.Tag,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
c *ContextCommon,
|
||||
) *TagsContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
viewModel := NewBasicViewModel(func() []*models.Tag { return c.Model().Tags })
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetTagListDisplayStrings(c.Model().Tags, c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &TagsContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Tags,
|
||||
WindowName: "branches",
|
||||
Key: TAGS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -63,3 +58,9 @@ func (self *TagsContext) GetSelectedRef() types.Ref {
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func (self *TagsContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -14,33 +15,30 @@ type WorkingTreeContext struct {
|
||||
|
||||
var _ types.IListContext = (*WorkingTreeContext)(nil)
|
||||
|
||||
func NewWorkingTreeContext(
|
||||
getModel func() []*models.File,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
viewModel := filetree.NewFileTreeViewModel(
|
||||
func() []*models.File { return c.Model().Files },
|
||||
c.Log,
|
||||
c.UserConfig.Gui.ShowFileTree,
|
||||
)
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *WorkingTreeContext {
|
||||
viewModel := filetree.NewFileTreeViewModel(getModel, c.Log, c.UserConfig.Gui.ShowFileTree)
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
lines := presentation.RenderFileTree(viewModel, c.Modes().Diffing.Ref, c.Model().Submodules)
|
||||
return slices.Map(lines, func(line string) []string {
|
||||
return []string{line}
|
||||
})
|
||||
}
|
||||
|
||||
return &WorkingTreeContext{
|
||||
FileTreeViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Files,
|
||||
WindowName: "files",
|
||||
Key: FILES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
|
@ -6,273 +6,11 @@ import (
|
||||
)
|
||||
|
||||
func (gui *Gui) contextTree() *context.ContextTree {
|
||||
return &context.ContextTree{
|
||||
Global: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.GLOBAL_CONTEXT,
|
||||
View: nil, // TODO: see if this breaks anything
|
||||
WindowName: "",
|
||||
Key: context.GLOBAL_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
HasUncontrolledBounds: true, // setting to true because the global context doesn't even have a view
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnRenderToMain: gui.statusRenderToMain,
|
||||
},
|
||||
),
|
||||
Status: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
View: gui.Views.Status,
|
||||
WindowName: "status",
|
||||
Key: context.STATUS_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnRenderToMain: gui.statusRenderToMain,
|
||||
},
|
||||
),
|
||||
Snake: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
View: gui.Views.Snake,
|
||||
WindowName: "files",
|
||||
Key: context.SNAKE_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: func(opts types.OnFocusOpts) error {
|
||||
gui.startSnake()
|
||||
return nil
|
||||
},
|
||||
OnFocusLost: func(opts types.OnFocusLostOpts) error {
|
||||
gui.snakeGame.Exit()
|
||||
gui.moveToTopOfWindow(gui.State.Contexts.Submodules)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
),
|
||||
Files: gui.filesListContext(),
|
||||
Submodules: gui.submodulesListContext(),
|
||||
Menu: gui.menuListContext(),
|
||||
Remotes: gui.remotesListContext(),
|
||||
RemoteBranches: gui.remoteBranchesListContext(),
|
||||
LocalCommits: gui.branchCommitsListContext(),
|
||||
CommitFiles: gui.commitFilesListContext(),
|
||||
ReflogCommits: gui.reflogCommitsListContext(),
|
||||
SubCommits: gui.subCommitsListContext(),
|
||||
Branches: gui.branchesListContext(),
|
||||
Tags: gui.tagsListContext(),
|
||||
Stash: gui.stashListContext(),
|
||||
Suggestions: gui.suggestionsListContext(),
|
||||
Normal: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: gui.Views.Main,
|
||||
WindowName: "main",
|
||||
Key: context.NORMAL_MAIN_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: func(opts types.OnFocusOpts) error {
|
||||
return nil // TODO: should we do something here? We should allow for scrolling the panel
|
||||
},
|
||||
},
|
||||
),
|
||||
NormalSecondary: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: gui.Views.Secondary,
|
||||
WindowName: "secondary",
|
||||
Key: context.NORMAL_SECONDARY_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
}),
|
||||
context.ContextCallbackOpts{},
|
||||
),
|
||||
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.PatchBuilder.IsEmpty() {
|
||||
gui.git.Patch.PatchBuilder.Reset()
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func() []int {
|
||||
filename := gui.State.Contexts.CommitFiles.GetSelectedPath()
|
||||
includedLineIndices, err := gui.git.Patch.PatchBuilder.GetFileIncLineIndices(filename)
|
||||
if err != nil {
|
||||
gui.Log.Error(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return includedLineIndices
|
||||
},
|
||||
gui.c,
|
||||
),
|
||||
CustomPatchBuilderSecondary: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: gui.Views.PatchBuildingSecondary,
|
||||
WindowName: "secondary",
|
||||
Key: context.PATCH_BUILDING_SECONDARY_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
}),
|
||||
context.ContextCallbackOpts{},
|
||||
),
|
||||
MergeConflicts: context.NewMergeConflictsContext(
|
||||
gui.Views.MergeConflicts,
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: OnFocusWrapper(func() error {
|
||||
gui.Views.MergeConflicts.Wrap = false
|
||||
|
||||
return gui.refreshMergePanel(true)
|
||||
}),
|
||||
OnFocusLost: func(opts types.OnFocusLostOpts) error {
|
||||
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
|
||||
gui.State.Contexts.MergeConflicts.GetState().ResetConflictSelection()
|
||||
gui.Views.MergeConflicts.Wrap = true
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
gui.c,
|
||||
func() map[string]string {
|
||||
// wrapping in a function because contexts are initialized before helpers
|
||||
return gui.helpers.MergeConflicts.GetMergingOptions()
|
||||
},
|
||||
),
|
||||
Confirmation: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.TEMPORARY_POPUP,
|
||||
View: gui.Views.Confirmation,
|
||||
WindowName: "confirmation",
|
||||
Key: context.CONFIRMATION_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: OnFocusWrapper(gui.handleAskFocused),
|
||||
OnFocusLost: func(types.OnFocusLostOpts) error {
|
||||
gui.deactivateConfirmationPrompt()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
),
|
||||
CommitMessage: context.NewCommitMessageContext(
|
||||
gui.Views.CommitMessage,
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: OnFocusWrapper(gui.handleCommitMessageFocused),
|
||||
},
|
||||
),
|
||||
CommitDescription: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
View: gui.Views.CommitDescription,
|
||||
WindowName: "commitDescription",
|
||||
Key: context.COMMIT_DESCRIPTION_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: func(opts types.OnFocusOpts) error {
|
||||
_, err := gui.g.SetViewBeneath("commitDescription", "commitMessage", 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
),
|
||||
Search: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
View: gui.Views.Search,
|
||||
WindowName: "search",
|
||||
Key: context.SEARCH_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
context.ContextCallbackOpts{},
|
||||
),
|
||||
CommandLog: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.EXTRAS_CONTEXT,
|
||||
View: gui.Views.Extras,
|
||||
WindowName: "extras",
|
||||
Key: context.COMMAND_LOG_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnFocusLost: func(opts types.OnFocusLostOpts) error {
|
||||
gui.Views.Extras.Autoscroll = true
|
||||
return nil
|
||||
},
|
||||
},
|
||||
),
|
||||
Options: context.NewDisplayContext(context.OPTIONS_CONTEXT_KEY, gui.Views.Options, "options"),
|
||||
AppStatus: context.NewDisplayContext(context.APP_STATUS_CONTEXT_KEY, gui.Views.AppStatus, "appStatus"),
|
||||
SearchPrefix: context.NewDisplayContext(context.SEARCH_PREFIX_CONTEXT_KEY, gui.Views.SearchPrefix, "searchPrefix"),
|
||||
Information: context.NewDisplayContext(context.INFORMATION_CONTEXT_KEY, gui.Views.Information, "information"),
|
||||
Limit: context.NewDisplayContext(context.LIMIT_CONTEXT_KEY, gui.Views.Limit, "limit"),
|
||||
contextCommon := &context.ContextCommon{
|
||||
IGuiCommon: gui.c.IGuiCommon,
|
||||
Common: gui.c.Common,
|
||||
}
|
||||
return context.NewContextTree(contextCommon)
|
||||
}
|
||||
|
||||
// using this wrapper for when an onFocus function doesn't care about any potential
|
||||
@ -283,10 +21,10 @@ func OnFocusWrapper(f func() error) func(opts types.OnFocusOpts) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) getPatchExplorerContexts() []types.IPatchExplorerContext {
|
||||
return []types.IPatchExplorerContext{
|
||||
gui.State.Contexts.Staging,
|
||||
gui.State.Contexts.StagingSecondary,
|
||||
gui.State.Contexts.CustomPatchBuilder,
|
||||
func (gui *Gui) defaultSideContext() types.Context {
|
||||
if gui.State.Modes.Filtering.Active() {
|
||||
return gui.State.Contexts.LocalCommits
|
||||
} else {
|
||||
return gui.State.Contexts.Files
|
||||
}
|
||||
}
|
||||
|
@ -7,24 +7,22 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/services/custom_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/snake"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/status"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
func (gui *Gui) resetControllers() {
|
||||
helperCommon := gui.c
|
||||
osCommand := gui.os
|
||||
model := gui.State.Model
|
||||
refsHelper := helpers.NewRefsHelper(
|
||||
helperCommon,
|
||||
gui.git,
|
||||
gui.State.Contexts,
|
||||
model,
|
||||
)
|
||||
func (gui *Gui) Helpers() *helpers.Helpers {
|
||||
return gui.helpers
|
||||
}
|
||||
|
||||
func (gui *Gui) resetHelpersAndControllers() {
|
||||
helperCommon := gui.c
|
||||
refsHelper := helpers.NewRefsHelper(helperCommon)
|
||||
|
||||
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, refsHelper)
|
||||
suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon)
|
||||
|
||||
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, gui.State.Contexts, gui.git, refsHelper)
|
||||
suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon, model, gui.refreshSuggestions)
|
||||
setCommitSummary := gui.getCommitMessageSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage })
|
||||
setCommitDescription := gui.getCommitMessageSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitDescription })
|
||||
getCommitSummary := func() string {
|
||||
@ -35,66 +33,86 @@ func (gui *Gui) resetControllers() {
|
||||
return strings.TrimSpace(gui.Views.CommitDescription.TextArea.GetContent())
|
||||
}
|
||||
commitsHelper := helpers.NewCommitsHelper(helperCommon,
|
||||
gui.State.Model,
|
||||
gui.State.Contexts,
|
||||
getCommitSummary,
|
||||
setCommitSummary,
|
||||
getCommitDescription,
|
||||
setCommitDescription,
|
||||
gui.RenderCommitLength,
|
||||
)
|
||||
gpgHelper := helpers.NewGpgHelper(helperCommon, gui.os, gui.git)
|
||||
|
||||
gpgHelper := helpers.NewGpgHelper(helperCommon)
|
||||
viewHelper := helpers.NewViewHelper(helperCommon, gui.State.Contexts)
|
||||
recordDirectoryHelper := helpers.NewRecordDirectoryHelper(helperCommon)
|
||||
patchBuildingHelper := helpers.NewPatchBuildingHelper(helperCommon)
|
||||
stagingHelper := helpers.NewStagingHelper(helperCommon)
|
||||
mergeConflictsHelper := helpers.NewMergeConflictsHelper(helperCommon)
|
||||
refreshHelper := helpers.NewRefreshHelper(helperCommon, refsHelper, rebaseHelper, patchBuildingHelper, stagingHelper, mergeConflictsHelper, gui.fileWatcher)
|
||||
diffHelper := helpers.NewDiffHelper(helperCommon)
|
||||
cherryPickHelper := helpers.NewCherryPickHelper(
|
||||
helperCommon,
|
||||
rebaseHelper,
|
||||
)
|
||||
bisectHelper := helpers.NewBisectHelper(helperCommon)
|
||||
windowHelper := helpers.NewWindowHelper(helperCommon, viewHelper)
|
||||
modeHelper := helpers.NewModeHelper(
|
||||
helperCommon,
|
||||
diffHelper,
|
||||
patchBuildingHelper,
|
||||
cherryPickHelper,
|
||||
rebaseHelper,
|
||||
bisectHelper,
|
||||
)
|
||||
appStatusHelper := helpers.NewAppStatusHelper(
|
||||
helperCommon,
|
||||
func() *status.StatusManager { return gui.statusManager },
|
||||
)
|
||||
gui.helpers = &helpers.Helpers{
|
||||
Refs: refsHelper,
|
||||
Host: helpers.NewHostHelper(helperCommon, gui.git),
|
||||
PatchBuilding: helpers.NewPatchBuildingHelper(helperCommon, gui.git, gui.State.Contexts),
|
||||
Bisect: helpers.NewBisectHelper(helperCommon, gui.git),
|
||||
Suggestions: suggestionsHelper,
|
||||
Files: helpers.NewFilesHelper(helperCommon, gui.git, osCommand),
|
||||
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, gui.git, gui.State.Contexts, refsHelper, model, setCommitSummary, commitsHelper, gpgHelper),
|
||||
Tags: helpers.NewTagsHelper(helperCommon, gui.git),
|
||||
GPG: gpgHelper,
|
||||
MergeAndRebase: rebaseHelper,
|
||||
MergeConflicts: helpers.NewMergeConflictsHelper(helperCommon, gui.State.Contexts, gui.git),
|
||||
CherryPick: helpers.NewCherryPickHelper(
|
||||
helperCommon,
|
||||
gui.git,
|
||||
gui.State.Contexts,
|
||||
func() *cherrypicking.CherryPicking { return gui.State.Modes.CherryPicking },
|
||||
rebaseHelper,
|
||||
Refs: refsHelper,
|
||||
Host: helpers.NewHostHelper(helperCommon),
|
||||
PatchBuilding: patchBuildingHelper,
|
||||
Staging: stagingHelper,
|
||||
Bisect: bisectHelper,
|
||||
Suggestions: suggestionsHelper,
|
||||
Files: helpers.NewFilesHelper(helperCommon),
|
||||
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper),
|
||||
Tags: helpers.NewTagsHelper(helperCommon),
|
||||
GPG: helpers.NewGpgHelper(helperCommon),
|
||||
MergeAndRebase: rebaseHelper,
|
||||
MergeConflicts: mergeConflictsHelper,
|
||||
CherryPick: cherryPickHelper,
|
||||
Upstream: helpers.NewUpstreamHelper(helperCommon, suggestionsHelper.GetRemoteBranchesSuggestionsFunc),
|
||||
AmendHelper: helpers.NewAmendHelper(helperCommon, gpgHelper),
|
||||
Commits: commitsHelper,
|
||||
Snake: helpers.NewSnakeHelper(helperCommon),
|
||||
Diff: diffHelper,
|
||||
Repos: helpers.NewRecentReposHelper(helperCommon, recordDirectoryHelper, gui.onNewRepo),
|
||||
RecordDirectory: recordDirectoryHelper,
|
||||
Update: helpers.NewUpdateHelper(helperCommon, gui.Updater),
|
||||
Window: windowHelper,
|
||||
View: viewHelper,
|
||||
Refresh: refreshHelper,
|
||||
Confirmation: helpers.NewConfirmationHelper(helperCommon),
|
||||
Mode: modeHelper,
|
||||
AppStatus: appStatusHelper,
|
||||
WindowArrangement: helpers.NewWindowArrangementHelper(
|
||||
gui.c,
|
||||
windowHelper,
|
||||
modeHelper,
|
||||
appStatusHelper,
|
||||
),
|
||||
Upstream: helpers.NewUpstreamHelper(helperCommon, model, suggestionsHelper.GetRemoteBranchesSuggestionsFunc),
|
||||
AmendHelper: helpers.NewAmendHelper(helperCommon, gui.git, gpgHelper),
|
||||
Commits: commitsHelper,
|
||||
}
|
||||
|
||||
gui.CustomCommandsClient = custom_commands.NewClient(
|
||||
helperCommon,
|
||||
gui.os,
|
||||
gui.git,
|
||||
gui.State.Contexts,
|
||||
gui.helpers,
|
||||
)
|
||||
|
||||
common := controllers.NewControllerCommon(
|
||||
helperCommon,
|
||||
osCommand,
|
||||
gui.git,
|
||||
gui.helpers,
|
||||
model,
|
||||
gui.State.Contexts,
|
||||
gui.State.Modes,
|
||||
&gui.Mutexes,
|
||||
)
|
||||
common := controllers.NewControllerCommon(helperCommon, gui)
|
||||
|
||||
syncController := controllers.NewSyncController(
|
||||
common,
|
||||
)
|
||||
|
||||
submodulesController := controllers.NewSubmodulesController(
|
||||
common,
|
||||
gui.enterSubmodule,
|
||||
)
|
||||
submodulesController := controllers.NewSubmodulesController(common)
|
||||
|
||||
bisectController := controllers.NewBisectController(common)
|
||||
|
||||
@ -113,8 +131,6 @@ func (gui *Gui) resetControllers() {
|
||||
tagsController := controllers.NewTagsController(common)
|
||||
filesController := controllers.NewFilesController(
|
||||
common,
|
||||
gui.enterSubmodule,
|
||||
setCommitSummary,
|
||||
)
|
||||
mergeConflictsController := controllers.NewMergeConflictsController(common)
|
||||
remotesController := controllers.NewRemotesController(
|
||||
@ -135,7 +151,34 @@ func (gui *Gui) resetControllers() {
|
||||
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)
|
||||
snakeController := controllers.NewSnakeController(common, func() *snake.Game { return gui.snakeGame })
|
||||
snakeController := controllers.NewSnakeController(common)
|
||||
reflogCommitsController := controllers.NewReflogCommitsController(common)
|
||||
subCommitsController := controllers.NewSubCommitsController(common)
|
||||
statusController := controllers.NewStatusController(common)
|
||||
commandLogController := controllers.NewCommandLogController(common)
|
||||
confirmationController := controllers.NewConfirmationController(common)
|
||||
suggestionsController := controllers.NewSuggestionsController(common)
|
||||
jumpToSideWindowController := controllers.NewJumpToSideWindowController(common)
|
||||
|
||||
sideWindowControllerFactory := controllers.NewSideWindowControllerFactory(common)
|
||||
|
||||
// allow for navigating between side window contexts
|
||||
for _, context := range []types.Context{
|
||||
gui.State.Contexts.Status,
|
||||
gui.State.Contexts.Remotes,
|
||||
gui.State.Contexts.Tags,
|
||||
gui.State.Contexts.Branches,
|
||||
gui.State.Contexts.RemoteBranches,
|
||||
gui.State.Contexts.Files,
|
||||
gui.State.Contexts.Submodules,
|
||||
gui.State.Contexts.ReflogCommits,
|
||||
gui.State.Contexts.LocalCommits,
|
||||
gui.State.Contexts.CommitFiles,
|
||||
gui.State.Contexts.SubCommits,
|
||||
gui.State.Contexts.Stash,
|
||||
} {
|
||||
controllers.AttachControllers(context, sideWindowControllerFactory.Create(context))
|
||||
}
|
||||
|
||||
setSubCommits := func(commits []*models.Commit) {
|
||||
gui.Mutexes.SubCommitsMutex.Lock()
|
||||
@ -161,7 +204,7 @@ func (gui *Gui) resetControllers() {
|
||||
gui.State.Contexts.Stash,
|
||||
} {
|
||||
controllers.AttachControllers(context, controllers.NewSwitchToDiffFilesController(
|
||||
common, gui.SwitchToCommitFilesContext, context,
|
||||
common, context, gui.State.Contexts.CommitFiles,
|
||||
))
|
||||
}
|
||||
|
||||
@ -173,6 +216,14 @@ func (gui *Gui) resetControllers() {
|
||||
controllers.AttachControllers(context, controllers.NewBasicCommitsController(common, context))
|
||||
}
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.ReflogCommits,
|
||||
reflogCommitsController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.SubCommits,
|
||||
subCommitsController,
|
||||
)
|
||||
|
||||
// TODO: add scroll controllers for main panels (need to bring some more functionality across for that e.g. reading more from the currently displayed git command)
|
||||
controllers.AttachControllers(gui.State.Contexts.Staging,
|
||||
stagingController,
|
||||
@ -256,11 +307,28 @@ func (gui *Gui) resetControllers() {
|
||||
remoteBranchesController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.Status,
|
||||
statusController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.CommandLog,
|
||||
commandLogController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.Confirmation,
|
||||
confirmationController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.Suggestions,
|
||||
suggestionsController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.Global,
|
||||
syncController,
|
||||
undoController,
|
||||
globalController,
|
||||
contextLinesController,
|
||||
jumpToSideWindowController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.Snake,
|
||||
@ -268,8 +336,19 @@ func (gui *Gui) resetControllers() {
|
||||
)
|
||||
|
||||
// this must come last so that we've got our click handlers defined against the context
|
||||
listControllerFactory := controllers.NewListControllerFactory(gui.c)
|
||||
for _, context := range gui.getListContexts() {
|
||||
listControllerFactory := controllers.NewListControllerFactory(common)
|
||||
for _, context := range gui.c.Context().AllList() {
|
||||
controllers.AttachControllers(context, listControllerFactory.Create(context))
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) getCommitMessageSetTextareaTextFn(getView func() *gocui.View) func(string) {
|
||||
return func(text string) {
|
||||
// using a getView function so that we don't need to worry about when the view is created
|
||||
view := getView()
|
||||
view.ClearTextArea()
|
||||
view.TextArea.TypeString(text)
|
||||
gui.helpers.Confirmation.ResizeCommitMessagePanels()
|
||||
view.RenderTextArea()
|
||||
}
|
||||
}
|
||||
|
@ -7,5 +7,8 @@ func AttachControllers(context types.Context, controllers ...types.IController)
|
||||
context.AddKeybindingsFn(controller.GetKeybindings)
|
||||
context.AddMouseKeybindingsFn(controller.GetMouseKeybindings)
|
||||
context.AddOnClickFn(controller.GetOnClick())
|
||||
context.AddOnRenderToMainFn(controller.GetOnRenderToMain())
|
||||
context.AddOnFocusFn(controller.GetOnFocus())
|
||||
context.AddOnFocusLostFn(controller.GetOnFocusLost())
|
||||
}
|
||||
}
|
||||
|
@ -18,3 +18,15 @@ func (self *baseController) GetMouseKeybindings(opts types.KeybindingsOpts) []*g
|
||||
func (self *baseController) GetOnClick() func() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnRenderToMain() func() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
@ -20,15 +20,15 @@ type ContainsCommits interface {
|
||||
|
||||
type BasicCommitsController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
context ContainsCommits
|
||||
}
|
||||
|
||||
func NewBasicCommitsController(controllerCommon *controllerCommon, context ContainsCommits) *BasicCommitsController {
|
||||
func NewBasicCommitsController(controllerCommon *ControllerCommon, context ContainsCommits) *BasicCommitsController {
|
||||
return &BasicCommitsController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: controllerCommon,
|
||||
context: context,
|
||||
baseController: baseController{},
|
||||
c: controllerCommon,
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Commits.ResetCherryPick),
|
||||
Handler: self.helpers.CherryPick.Reset,
|
||||
Handler: self.c.Helpers().CherryPick.Reset,
|
||||
Description: self.c.Tr.LcResetCherryPick,
|
||||
},
|
||||
}
|
||||
@ -141,7 +141,7 @@ func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) e
|
||||
|
||||
func (self *BasicCommitsController) copyCommitSHAToClipboard(commit *models.Commit) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitSHAToClipboard)
|
||||
if err := self.os.CopyToClipboard(commit.Sha); err != nil {
|
||||
if err := self.c.OS().CopyToClipboard(commit.Sha); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -150,13 +150,13 @@ func (self *BasicCommitsController) copyCommitSHAToClipboard(commit *models.Comm
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyCommitURLToClipboard(commit *models.Commit) error {
|
||||
url, err := self.helpers.Host.GetCommitURL(commit.Sha)
|
||||
url, err := self.c.Helpers().Host.GetCommitURL(commit.Sha)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitURLToClipboard)
|
||||
if err := self.os.CopyToClipboard(url); err != nil {
|
||||
if err := self.c.OS().CopyToClipboard(url); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -165,13 +165,13 @@ func (self *BasicCommitsController) copyCommitURLToClipboard(commit *models.Comm
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyCommitDiffToClipboard(commit *models.Commit) error {
|
||||
diff, err := self.git.Commit.GetCommitDiff(commit.Sha)
|
||||
diff, err := self.c.Git().Commit.GetCommitDiff(commit.Sha)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitDiffToClipboard)
|
||||
if err := self.os.CopyToClipboard(diff); err != nil {
|
||||
if err := self.c.OS().CopyToClipboard(diff); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -180,7 +180,7 @@ func (self *BasicCommitsController) copyCommitDiffToClipboard(commit *models.Com
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyAuthorToClipboard(commit *models.Commit) error {
|
||||
author, err := self.git.Commit.GetCommitAuthor(commit.Sha)
|
||||
author, err := self.c.Git().Commit.GetCommitAuthor(commit.Sha)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
@ -188,7 +188,7 @@ func (self *BasicCommitsController) copyAuthorToClipboard(commit *models.Commit)
|
||||
formattedAuthor := fmt.Sprintf("%s <%s>", author.Name, author.Email)
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitAuthorToClipboard)
|
||||
if err := self.os.CopyToClipboard(formattedAuthor); err != nil {
|
||||
if err := self.c.OS().CopyToClipboard(formattedAuthor); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -197,13 +197,13 @@ func (self *BasicCommitsController) copyAuthorToClipboard(commit *models.Commit)
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyCommitMessageToClipboard(commit *models.Commit) error {
|
||||
message, err := self.git.Commit.GetCommitMessage(commit.Sha)
|
||||
message, err := self.c.Git().Commit.GetCommitMessage(commit.Sha)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitMessageToClipboard)
|
||||
if err := self.os.CopyToClipboard(message); err != nil {
|
||||
if err := self.c.OS().CopyToClipboard(message); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -212,13 +212,13 @@ func (self *BasicCommitsController) copyCommitMessageToClipboard(commit *models.
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) openInBrowser(commit *models.Commit) error {
|
||||
url, err := self.helpers.Host.GetCommitURL(commit.Sha)
|
||||
url, err := self.c.Helpers().Host.GetCommitURL(commit.Sha)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.OpenCommitInBrowser)
|
||||
if err := self.os.OpenLink(url); err != nil {
|
||||
if err := self.c.OS().OpenLink(url); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -226,11 +226,11 @@ func (self *BasicCommitsController) openInBrowser(commit *models.Commit) error {
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) newBranch(commit *models.Commit) error {
|
||||
return self.helpers.Refs.NewBranch(commit.RefName(), commit.Description(), "")
|
||||
return self.c.Helpers().Refs.NewBranch(commit.RefName(), commit.Description(), "")
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) createResetMenu(commit *models.Commit) error {
|
||||
return self.helpers.Refs.CreateGitResetMenu(commit.Sha)
|
||||
return self.c.Helpers().Refs.CreateGitResetMenu(commit.Sha)
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) checkout(commit *models.Commit) error {
|
||||
@ -239,15 +239,15 @@ func (self *BasicCommitsController) checkout(commit *models.Commit) error {
|
||||
Prompt: self.c.Tr.SureCheckoutThisCommit,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
|
||||
return self.helpers.Refs.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
|
||||
return self.c.Helpers().Refs.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copy(commit *models.Commit) error {
|
||||
return self.helpers.CherryPick.Copy(commit, self.context.GetCommits(), self.context)
|
||||
return self.c.Helpers().CherryPick.Copy(commit, self.context.GetCommits(), self.context)
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyRange(*models.Commit) error {
|
||||
return self.helpers.CherryPick.CopyRange(self.context.GetSelectedLineIdx(), self.context.GetCommits(), self.context)
|
||||
return self.c.Helpers().CherryPick.CopyRange(self.context.GetSelectedLineIdx(), self.context.GetCommits(), self.context)
|
||||
}
|
||||
|
@ -12,17 +12,17 @@ import (
|
||||
|
||||
type BisectController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &BisectController{}
|
||||
|
||||
func NewBisectController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *BisectController {
|
||||
return &BisectController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ func (self *BisectController) GetKeybindings(opts types.KeybindingsOpts) []*type
|
||||
func (self *BisectController) openMenu(commit *models.Commit) error {
|
||||
// no shame in getting this directly rather than using the cached value
|
||||
// given how cheap it is to obtain
|
||||
info := self.git.Bisect.GetInfo()
|
||||
info := self.c.Git().Bisect.GetInfo()
|
||||
if info.Started() {
|
||||
return self.openMidBisectMenu(info, commit)
|
||||
} else {
|
||||
@ -63,14 +63,14 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
|
||||
selectCurrentAfter := info.GetCurrentSha() == "" || info.GetCurrentSha() == commit.Sha
|
||||
// we need to wait to reselect if our bisect commits aren't ancestors of our 'start'
|
||||
// ref, because we'll be reloading our commits in that case.
|
||||
waitToReselect := selectCurrentAfter && !self.git.Bisect.ReachableFromStart(info)
|
||||
waitToReselect := selectCurrentAfter && !self.c.Git().Bisect.ReachableFromStart(info)
|
||||
|
||||
menuItems := []*types.MenuItem{
|
||||
{
|
||||
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, commit.ShortSha(), info.NewTerm()),
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.BisectMark)
|
||||
if err := self.git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
|
||||
if err := self.c.Git().Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
|
||||
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, commit.ShortSha(), info.OldTerm()),
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.BisectMark)
|
||||
if err := self.git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
|
||||
if err := self.c.Git().Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
|
||||
Label: fmt.Sprintf(self.c.Tr.Bisect.Skip, commit.ShortSha()),
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.BisectSkip)
|
||||
if err := self.git.Bisect.Skip(commit.Sha); err != nil {
|
||||
if err := self.c.Git().Bisect.Skip(commit.Sha); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
|
||||
{
|
||||
Label: self.c.Tr.Bisect.ResetOption,
|
||||
OnPress: func() error {
|
||||
return self.helpers.Bisect.Reset()
|
||||
return self.c.Helpers().Bisect.Reset()
|
||||
},
|
||||
Key: 'r',
|
||||
},
|
||||
@ -125,15 +125,15 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
|
||||
Label: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortSha(), info.NewTerm()),
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.StartBisect)
|
||||
if err := self.git.Bisect.Start(); err != nil {
|
||||
if err := self.c.Git().Bisect.Start(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if err := self.git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
|
||||
if err := self.c.Git().Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.helpers.Bisect.PostBisectCommandRefresh()
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
},
|
||||
Key: 'b',
|
||||
},
|
||||
@ -141,15 +141,15 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
|
||||
Label: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortSha(), info.OldTerm()),
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.StartBisect)
|
||||
if err := self.git.Bisect.Start(); err != nil {
|
||||
if err := self.c.Git().Bisect.Start(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if err := self.git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
|
||||
if err := self.c.Git().Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.helpers.Bisect.PostBisectCommandRefresh()
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
},
|
||||
Key: 'g',
|
||||
},
|
||||
@ -163,7 +163,7 @@ func (self *BisectController) showBisectCompleteMessage(candidateShas []string)
|
||||
prompt = self.c.Tr.Bisect.CompletePromptIndeterminate
|
||||
}
|
||||
|
||||
formattedCommits, err := self.git.Commit.GetCommitsOneline(candidateShas)
|
||||
formattedCommits, err := self.c.Git().Commit.GetCommitsOneline(candidateShas)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
@ -173,17 +173,17 @@ func (self *BisectController) showBisectCompleteMessage(candidateShas []string)
|
||||
Prompt: fmt.Sprintf(prompt, strings.TrimSpace(formattedCommits)),
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ResetBisect)
|
||||
if err := self.git.Bisect.Reset(); err != nil {
|
||||
if err := self.c.Git().Bisect.Reset(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.helpers.Bisect.PostBisectCommandRefresh()
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool) error {
|
||||
done, candidateShas, err := self.git.Bisect.IsDone()
|
||||
done, candidateShas, err := self.c.Git().Bisect.IsDone()
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
@ -211,15 +211,15 @@ func (self *BisectController) afterBisectMarkRefresh(selectCurrent bool, waitToR
|
||||
} else {
|
||||
selectFn()
|
||||
|
||||
return self.helpers.Bisect.PostBisectCommandRefresh()
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BisectController) selectCurrentBisectCommit() {
|
||||
info := self.git.Bisect.GetInfo()
|
||||
info := self.c.Git().Bisect.GetInfo()
|
||||
if info.GetCurrentSha() != "" {
|
||||
// find index of commit with that sha, move cursor to that.
|
||||
for i, commit := range self.model.Commits {
|
||||
for i, commit := range self.c.Model().Commits {
|
||||
if commit.Sha == info.GetCurrentSha() {
|
||||
self.context().SetSelectedLineIdx(i)
|
||||
_ = self.context().HandleFocus(types.OnFocusOpts{})
|
||||
@ -245,5 +245,5 @@ func (self *BisectController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *BisectController) context() *context.LocalCommitsContext {
|
||||
return self.contexts.LocalCommits
|
||||
return self.c.Contexts().LocalCommits
|
||||
}
|
||||
|
@ -14,17 +14,17 @@ import (
|
||||
|
||||
type BranchesController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &BranchesController{}
|
||||
|
||||
func NewBranchesController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *BranchesController {
|
||||
return &BranchesController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,6 +111,30 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BranchesController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.c.Helpers().Diff.WithDiffModeCheck(func() error {
|
||||
var task types.UpdateTask
|
||||
branch := self.context().GetSelected()
|
||||
if branch == nil {
|
||||
task = types.NewRenderStringTask(self.c.Tr.NoBranchesThisRepo)
|
||||
} else {
|
||||
cmdObj := self.c.Git().Branch.GetGraphCmdObj(branch.FullRefName())
|
||||
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: self.c.Tr.LogTitle,
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error {
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.Actions.SetUnsetUpstream,
|
||||
@ -118,7 +142,7 @@ func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.LcUnsetUpstream},
|
||||
OnPress: func() error {
|
||||
if err := self.git.Branch.UnsetUpstream(selectedBranch.Name); err != nil {
|
||||
if err := self.c.Git().Branch.UnsetUpstream(selectedBranch.Name); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.c.Refresh(types.RefreshOptions{
|
||||
@ -137,13 +161,13 @@ func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.LcSetUpstream},
|
||||
OnPress: func() error {
|
||||
return self.helpers.Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error {
|
||||
upstreamRemote, upstreamBranch, err := self.helpers.Upstream.ParseUpstream(upstream)
|
||||
return self.c.Helpers().Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error {
|
||||
upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if err := self.git.Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil {
|
||||
if err := self.c.Git().Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.c.Refresh(types.RefreshOptions{
|
||||
@ -169,16 +193,16 @@ func (self *BranchesController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *BranchesController) context() *context.BranchesContext {
|
||||
return self.contexts.Branches
|
||||
return self.c.Contexts().Branches
|
||||
}
|
||||
|
||||
func (self *BranchesController) press(selectedBranch *models.Branch) error {
|
||||
if selectedBranch == self.helpers.Refs.GetCheckedOutRef() {
|
||||
if selectedBranch == self.c.Helpers().Refs.GetCheckedOutRef() {
|
||||
return self.c.ErrorMsg(self.c.Tr.AlreadyCheckedOutBranch)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CheckoutBranch)
|
||||
return self.helpers.Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
|
||||
return self.c.Helpers().Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
|
||||
}
|
||||
|
||||
func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.Branch) error {
|
||||
@ -186,7 +210,7 @@ func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.B
|
||||
}
|
||||
|
||||
func (self *BranchesController) handleCreatePullRequestMenu(selectedBranch *models.Branch) error {
|
||||
checkedOutBranch := self.helpers.Refs.GetCheckedOutRef()
|
||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
|
||||
return self.createPullRequestMenu(selectedBranch, checkedOutBranch)
|
||||
}
|
||||
@ -194,18 +218,18 @@ func (self *BranchesController) handleCreatePullRequestMenu(selectedBranch *mode
|
||||
func (self *BranchesController) copyPullRequestURL() error {
|
||||
branch := self.context().GetSelected()
|
||||
|
||||
branchExistsOnRemote := self.git.Remote.CheckRemoteBranchExists(branch.Name)
|
||||
branchExistsOnRemote := self.c.Git().Remote.CheckRemoteBranchExists(branch.Name)
|
||||
|
||||
if !branchExistsOnRemote {
|
||||
return self.c.Error(errors.New(self.c.Tr.NoBranchOnRemote))
|
||||
}
|
||||
|
||||
url, err := self.helpers.Host.GetPullRequestURL(branch.Name, "")
|
||||
url, err := self.c.Helpers().Host.GetPullRequestURL(branch.Name, "")
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyPullRequestURL)
|
||||
if err := self.os.CopyToClipboard(url); err != nil {
|
||||
if err := self.c.OS().CopyToClipboard(url); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -224,7 +248,7 @@ func (self *BranchesController) forceCheckout() error {
|
||||
Prompt: message,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ForceCheckoutBranch)
|
||||
if err := self.git.Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
|
||||
if err := self.c.Git().Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
@ -235,10 +259,10 @@ func (self *BranchesController) forceCheckout() error {
|
||||
func (self *BranchesController) checkoutByName() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.BranchName + ":",
|
||||
FindSuggestionsFunc: self.helpers.Suggestions.GetRefsSuggestionsFunc(),
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
|
||||
HandleConfirm: func(response string) error {
|
||||
self.c.LogAction("Checkout branch")
|
||||
return self.helpers.Refs.CheckoutRef(response, types.CheckoutRefOptions{
|
||||
return self.c.Helpers().Refs.CheckoutRef(response, types.CheckoutRefOptions{
|
||||
OnRefNotFound: func(ref string) error {
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.BranchNotFoundTitle,
|
||||
@ -260,7 +284,7 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := self.git.Branch.New(newBranchName, branch.FullRefName()); err != nil {
|
||||
if err := self.c.Git().Branch.New(newBranchName, branch.FullRefName()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -269,7 +293,7 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
|
||||
}
|
||||
|
||||
func (self *BranchesController) delete(branch *models.Branch) error {
|
||||
checkedOutBranch := self.helpers.Refs.GetCheckedOutRef()
|
||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
if checkedOutBranch.Name == branch.Name {
|
||||
return self.c.ErrorMsg(self.c.Tr.CantDeleteCheckOutBranch)
|
||||
}
|
||||
@ -296,7 +320,7 @@ func (self *BranchesController) deleteWithForce(selectedBranch *models.Branch, f
|
||||
Prompt: message,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteBranch)
|
||||
if err := self.git.Branch.Delete(selectedBranch.Name, force); err != nil {
|
||||
if err := self.c.Git().Branch.Delete(selectedBranch.Name, force); err != nil {
|
||||
errMessage := err.Error()
|
||||
if !force && strings.Contains(errMessage, "git branch -D ") {
|
||||
return self.deleteWithForce(selectedBranch, true)
|
||||
@ -310,12 +334,12 @@ func (self *BranchesController) deleteWithForce(selectedBranch *models.Branch, f
|
||||
|
||||
func (self *BranchesController) merge() error {
|
||||
selectedBranchName := self.context().GetSelected().Name
|
||||
return self.helpers.MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName)
|
||||
return self.c.Helpers().MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName)
|
||||
}
|
||||
|
||||
func (self *BranchesController) rebase() error {
|
||||
selectedBranchName := self.context().GetSelected().Name
|
||||
return self.helpers.MergeAndRebase.RebaseOntoRef(selectedBranchName)
|
||||
return self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranchName)
|
||||
}
|
||||
|
||||
func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
@ -340,10 +364,10 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
)
|
||||
|
||||
return self.c.WithLoaderPanel(message, func() error {
|
||||
if branch == self.helpers.Refs.GetCheckedOutRef() {
|
||||
if branch == self.c.Helpers().Refs.GetCheckedOutRef() {
|
||||
self.c.LogAction(action)
|
||||
|
||||
err := self.git.Sync.Pull(
|
||||
err := self.c.Git().Sync.Pull(
|
||||
git_commands.PullOptions{
|
||||
RemoteName: branch.UpstreamRemote,
|
||||
BranchName: branch.UpstreamBranch,
|
||||
@ -357,7 +381,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
} else {
|
||||
self.c.LogAction(action)
|
||||
err := self.git.Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
|
||||
err := self.c.Git().Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
@ -369,11 +393,11 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
}
|
||||
|
||||
func (self *BranchesController) createTag(branch *models.Branch) error {
|
||||
return self.helpers.Tags.CreateTagMenu(branch.FullRefName(), func() {})
|
||||
return self.c.Helpers().Tags.CreateTagMenu(branch.FullRefName(), func() {})
|
||||
}
|
||||
|
||||
func (self *BranchesController) createResetMenu(selectedBranch *models.Branch) error {
|
||||
return self.helpers.Refs.CreateGitResetMenu(selectedBranch.Name)
|
||||
return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.Name)
|
||||
}
|
||||
|
||||
func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
@ -383,7 +407,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
InitialContent: branch.Name,
|
||||
HandleConfirm: func(newBranchName string) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.RenameBranch)
|
||||
if err := self.git.Branch.Rename(branch.Name, newBranchName); err != nil {
|
||||
if err := self.c.Git().Branch.Rename(branch.Name, newBranchName); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -391,7 +415,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
|
||||
// now that we've got our stuff again we need to find that branch and reselect it.
|
||||
for i, newBranch := range self.model.Branches {
|
||||
for i, newBranch := range self.c.Model().Branches {
|
||||
if newBranch.Name == newBranchName {
|
||||
self.context().SetSelectedLineIdx(i)
|
||||
if err := self.context().HandleRender(); err != nil {
|
||||
@ -420,7 +444,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
}
|
||||
|
||||
func (self *BranchesController) newBranch(selectedBranch *models.Branch) error {
|
||||
return self.helpers.Refs.NewBranch(selectedBranch.FullRefName(), selectedBranch.RefName(), "")
|
||||
return self.c.Helpers().Refs.NewBranch(selectedBranch.FullRefName(), selectedBranch.RefName(), "")
|
||||
}
|
||||
|
||||
func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Branch, checkedOutBranch *models.Branch) error {
|
||||
@ -443,7 +467,7 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
||||
OnPress: func() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: branch.Name + " →",
|
||||
FindSuggestionsFunc: self.helpers.Suggestions.GetBranchNameSuggestionsFunc(),
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetBranchNameSuggestionsFunc(),
|
||||
HandleConfirm: func(targetBranchName string) error {
|
||||
return self.createPullRequest(branch.Name, targetBranchName)
|
||||
},
|
||||
@ -471,14 +495,14 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
||||
}
|
||||
|
||||
func (self *BranchesController) createPullRequest(from string, to string) error {
|
||||
url, err := self.helpers.Host.GetPullRequestURL(from, to)
|
||||
url, err := self.c.Helpers().Host.GetPullRequestURL(from, to)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.OpenPullRequest)
|
||||
|
||||
if err := self.os.OpenLink(url); err != nil {
|
||||
if err := self.c.OS().OpenLink(url); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
|
42
pkg/gui/controllers/command_log_controller.go
Normal file
42
pkg/gui/controllers/command_log_controller.go
Normal file
@ -0,0 +1,42 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type CommandLogController struct {
|
||||
baseController
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &CommandLogController{}
|
||||
|
||||
func NewCommandLogController(
|
||||
common *ControllerCommon,
|
||||
) *CommandLogController {
|
||||
return &CommandLogController{
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommandLogController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
bindings := []*types.Binding{}
|
||||
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *CommandLogController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return func(types.OnFocusLostOpts) error {
|
||||
self.c.Views().Extras.Autoscroll = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommandLogController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
||||
func (self *CommandLogController) context() types.Context {
|
||||
return self.c.Contexts().CommandLog
|
||||
}
|
@ -6,17 +6,17 @@ import (
|
||||
|
||||
type CommitDescriptionController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &CommitMessageController{}
|
||||
|
||||
func NewCommitDescriptionController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *CommitDescriptionController {
|
||||
return &CommitDescriptionController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,17 +44,17 @@ func (self *CommitDescriptionController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) context() types.Context {
|
||||
return self.contexts.CommitMessage
|
||||
return self.c.Contexts().CommitMessage
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) switchToCommitMessage() error {
|
||||
return self.c.PushContext(self.contexts.CommitMessage)
|
||||
return self.c.PushContext(self.c.Contexts().CommitMessage)
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) close() error {
|
||||
return self.helpers.Commits.CloseCommitMessagePanel()
|
||||
return self.c.Helpers().Commits.CloseCommitMessagePanel()
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) confirm() error {
|
||||
return self.helpers.Commits.HandleCommitConfirm()
|
||||
return self.c.Helpers().Commits.HandleCommitConfirm()
|
||||
}
|
||||
|
@ -8,29 +8,33 @@ import (
|
||||
|
||||
type CommitMessageController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &CommitMessageController{}
|
||||
|
||||
func NewCommitMessageController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *CommitMessageController {
|
||||
return &CommitMessageController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: merge that commit panel PR because we're not currently showing how to add a newline as it's
|
||||
// handled by the editor func rather than by the controller here.
|
||||
func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
bindings := []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.SubmitEditorText),
|
||||
Handler: self.confirm,
|
||||
Key: opts.GetKey(opts.Config.Universal.SubmitEditorText),
|
||||
Handler: self.confirm,
|
||||
Description: self.c.Tr.LcConfirm,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: self.close,
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: self.close,
|
||||
Description: self.c.Tr.LcClose,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevItem),
|
||||
@ -49,12 +53,19 @@ func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return func(types.OnFocusLostOpts) error {
|
||||
self.context().RenderCommitLength()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) context() *context.CommitMessageContext {
|
||||
return self.contexts.CommitMessage
|
||||
return self.c.Contexts().CommitMessage
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) handlePreviousCommit() error {
|
||||
@ -69,7 +80,7 @@ func (self *CommitMessageController) handleNextCommit() error {
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) switchToCommitDescription() error {
|
||||
if err := self.c.PushContext(self.contexts.CommitDescription); err != nil {
|
||||
if err := self.c.PushContext(self.c.Contexts().CommitDescription); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@ -80,10 +91,10 @@ func (self *CommitMessageController) handleCommitIndexChange(value int) error {
|
||||
newIndex := currentIndex + value
|
||||
if newIndex == context.NoCommitIndex {
|
||||
self.context().SetSelectedIndex(newIndex)
|
||||
self.helpers.Commits.SetMessageAndDescriptionInView(self.context().GetHistoryMessage())
|
||||
self.c.Helpers().Commits.SetMessageAndDescriptionInView(self.context().GetHistoryMessage())
|
||||
return nil
|
||||
} else if currentIndex == context.NoCommitIndex {
|
||||
self.context().SetHistoryMessage(self.helpers.Commits.JoinCommitMessageAndDescription())
|
||||
self.context().SetHistoryMessage(self.c.Helpers().Commits.JoinCommitMessageAndDescription())
|
||||
}
|
||||
|
||||
validCommit, err := self.setCommitMessageAtIndex(newIndex)
|
||||
@ -95,21 +106,21 @@ func (self *CommitMessageController) handleCommitIndexChange(value int) error {
|
||||
|
||||
// returns true if the given index is for a valid commit
|
||||
func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, error) {
|
||||
commitMessage, err := self.git.Commit.GetCommitMessageFromHistory(index)
|
||||
commitMessage, err := self.c.Git().Commit.GetCommitMessageFromHistory(index)
|
||||
if err != nil {
|
||||
if err == git_commands.ErrInvalidCommitIndex {
|
||||
return false, nil
|
||||
}
|
||||
return false, self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr)
|
||||
}
|
||||
self.helpers.Commits.UpdateCommitPanelView(commitMessage)
|
||||
self.c.Helpers().Commits.UpdateCommitPanelView(commitMessage)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) confirm() error {
|
||||
return self.helpers.Commits.HandleCommitConfirm()
|
||||
return self.c.Helpers().Commits.HandleCommitConfirm()
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) close() error {
|
||||
return self.helpers.Commits.CloseCommitMessagePanel()
|
||||
return self.c.Helpers().Commits.CloseCommitMessagePanel()
|
||||
}
|
||||
|
@ -11,17 +11,17 @@ import (
|
||||
|
||||
type CommitFilesController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &CommitFilesController{}
|
||||
|
||||
func NewCommitFilesController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *CommitFilesController {
|
||||
return &CommitFilesController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,7 +99,39 @@ func (self *CommitFilesController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) context() *context.CommitFilesContext {
|
||||
return self.contexts.CommitFiles
|
||||
return self.c.Contexts().CommitFiles
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
node := self.context().GetSelected()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ref := self.context().GetRef()
|
||||
to := ref.RefName()
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
|
||||
cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(
|
||||
from, to, reverse, node.GetPath(), false, self.c.State().GetIgnoreWhitespaceInDiffView(),
|
||||
)
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
pair := self.c.MainViewPairs().Normal
|
||||
if node.File != nil {
|
||||
pair = self.c.MainViewPairs().PatchBuilding
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: pair,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: self.c.Tr.Patch,
|
||||
Task: task,
|
||||
},
|
||||
Secondary: secondaryPatchPanelUpdateOpts(self.c),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) onClickMain(opts gocui.ViewMouseBindingOpts) error {
|
||||
@ -112,7 +144,7 @@ func (self *CommitFilesController) onClickMain(opts gocui.ViewMouseBindingOpts)
|
||||
|
||||
func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CheckoutFile)
|
||||
if err := self.git.WorkingTree.CheckoutFile(self.context().GetRef().RefName(), node.GetPath()); err != nil {
|
||||
if err := self.c.Git().WorkingTree.CheckoutFile(self.context().GetRef().RefName(), node.GetPath()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -120,7 +152,7 @@ func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) discard(node *filetree.CommitFileNode) error {
|
||||
if ok, err := self.helpers.PatchBuilding.ValidateNormalWorkingTreeState(); !ok {
|
||||
if ok, err := self.c.Helpers().PatchBuilding.ValidateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -130,8 +162,8 @@ func (self *CommitFilesController) discard(node *filetree.CommitFileNode) error
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DiscardOldFileChange)
|
||||
if err := self.git.Rebase.DiscardOldFileChanges(self.model.Commits, self.contexts.LocalCommits.GetSelectedLineIdx(), node.GetPath()); err != nil {
|
||||
if err := self.helpers.MergeAndRebase.CheckMergeOrRebase(err); err != nil {
|
||||
if err := self.c.Git().Rebase.DiscardOldFileChanges(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), node.GetPath()); err != nil {
|
||||
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -143,7 +175,7 @@ func (self *CommitFilesController) discard(node *filetree.CommitFileNode) error
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) open(node *filetree.CommitFileNode) error {
|
||||
return self.helpers.Files.OpenFile(node.GetPath())
|
||||
return self.c.Helpers().Files.OpenFile(node.GetPath())
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) edit(node *filetree.CommitFileNode) error {
|
||||
@ -151,13 +183,13 @@ func (self *CommitFilesController) edit(node *filetree.CommitFileNode) error {
|
||||
return self.c.ErrorMsg(self.c.Tr.ErrCannotEditDirectory)
|
||||
}
|
||||
|
||||
return self.helpers.Files.EditFile(node.GetPath())
|
||||
return self.c.Helpers().Files.EditFile(node.GetPath())
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) toggleForPatch(node *filetree.CommitFileNode) error {
|
||||
toggle := func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.LcUpdatingPatch, func() error {
|
||||
if !self.git.Patch.PatchBuilder.Active() {
|
||||
if !self.c.Git().Patch.PatchBuilder.Active() {
|
||||
if err := self.startPatchBuilder(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -166,34 +198,34 @@ func (self *CommitFilesController) toggleForPatch(node *filetree.CommitFileNode)
|
||||
// if there is any file that hasn't been fully added we'll fully add everything,
|
||||
// otherwise we'll remove everything
|
||||
adding := node.SomeFile(func(file *models.CommitFile) bool {
|
||||
return self.git.Patch.PatchBuilder.GetFileStatus(file.Name, self.context().GetRef().RefName()) != patch.WHOLE
|
||||
return self.c.Git().Patch.PatchBuilder.GetFileStatus(file.Name, self.context().GetRef().RefName()) != patch.WHOLE
|
||||
})
|
||||
|
||||
err := node.ForEachFile(func(file *models.CommitFile) error {
|
||||
if adding {
|
||||
return self.git.Patch.PatchBuilder.AddFileWhole(file.Name)
|
||||
return self.c.Git().Patch.PatchBuilder.AddFileWhole(file.Name)
|
||||
} else {
|
||||
return self.git.Patch.PatchBuilder.RemoveFile(file.Name)
|
||||
return self.c.Git().Patch.PatchBuilder.RemoveFile(file.Name)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if self.git.Patch.PatchBuilder.IsEmpty() {
|
||||
self.git.Patch.PatchBuilder.Reset()
|
||||
if self.c.Git().Patch.PatchBuilder.IsEmpty() {
|
||||
self.c.Git().Patch.PatchBuilder.Reset()
|
||||
}
|
||||
|
||||
return self.c.PostRefreshUpdate(self.context())
|
||||
})
|
||||
}
|
||||
|
||||
if self.git.Patch.PatchBuilder.Active() && self.git.Patch.PatchBuilder.To != self.context().GetRef().RefName() {
|
||||
if self.c.Git().Patch.PatchBuilder.Active() && self.c.Git().Patch.PatchBuilder.To != self.context().GetRef().RefName() {
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.DiscardPatch,
|
||||
Prompt: self.c.Tr.DiscardPatchConfirm,
|
||||
HandleConfirm: func() error {
|
||||
self.git.Patch.PatchBuilder.Reset()
|
||||
self.c.Git().Patch.PatchBuilder.Reset()
|
||||
return toggle()
|
||||
},
|
||||
})
|
||||
@ -213,9 +245,9 @@ func (self *CommitFilesController) startPatchBuilder() error {
|
||||
canRebase := commitFilesContext.GetCanRebase()
|
||||
ref := commitFilesContext.GetRef()
|
||||
to := ref.RefName()
|
||||
from, reverse := self.modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
|
||||
self.git.Patch.PatchBuilder.Start(from, to, reverse, canRebase)
|
||||
self.c.Git().Patch.PatchBuilder.Start(from, to, reverse, canRebase)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -229,21 +261,21 @@ func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode
|
||||
}
|
||||
|
||||
enterTheFile := func() error {
|
||||
if !self.git.Patch.PatchBuilder.Active() {
|
||||
if !self.c.Git().Patch.PatchBuilder.Active() {
|
||||
if err := self.startPatchBuilder(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return self.c.PushContext(self.contexts.CustomPatchBuilder, opts)
|
||||
return self.c.PushContext(self.c.Contexts().CustomPatchBuilder, opts)
|
||||
}
|
||||
|
||||
if self.git.Patch.PatchBuilder.Active() && self.git.Patch.PatchBuilder.To != self.context().GetRef().RefName() {
|
||||
if self.c.Git().Patch.PatchBuilder.Active() && self.c.Git().Patch.PatchBuilder.To != self.context().GetRef().RefName() {
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.DiscardPatch,
|
||||
Prompt: self.c.Tr.DiscardPatchConfirm,
|
||||
HandleConfirm: func() error {
|
||||
self.git.Patch.PatchBuilder.Reset()
|
||||
self.c.Git().Patch.PatchBuilder.Reset()
|
||||
return enterTheFile()
|
||||
},
|
||||
})
|
||||
|
@ -1,42 +1,24 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type controllerCommon struct {
|
||||
c *types.HelperCommon
|
||||
os *oscommands.OSCommand
|
||||
git *commands.GitCommand
|
||||
helpers *helpers.Helpers
|
||||
model *types.Model
|
||||
contexts *context.ContextTree
|
||||
modes *types.Modes
|
||||
mutexes *types.Mutexes
|
||||
type ControllerCommon struct {
|
||||
*helpers.HelperCommon
|
||||
IGetHelpers
|
||||
}
|
||||
|
||||
type IGetHelpers interface {
|
||||
Helpers() *helpers.Helpers
|
||||
}
|
||||
|
||||
func NewControllerCommon(
|
||||
c *types.HelperCommon,
|
||||
os *oscommands.OSCommand,
|
||||
git *commands.GitCommand,
|
||||
helpers *helpers.Helpers,
|
||||
model *types.Model,
|
||||
contexts *context.ContextTree,
|
||||
modes *types.Modes,
|
||||
mutexes *types.Mutexes,
|
||||
) *controllerCommon {
|
||||
return &controllerCommon{
|
||||
c: c,
|
||||
os: os,
|
||||
git: git,
|
||||
helpers: helpers,
|
||||
model: model,
|
||||
contexts: contexts,
|
||||
modes: modes,
|
||||
mutexes: mutexes,
|
||||
c *helpers.HelperCommon,
|
||||
IGetHelpers IGetHelpers,
|
||||
) *ControllerCommon {
|
||||
return &ControllerCommon{
|
||||
HelperCommon: c,
|
||||
IGetHelpers: IGetHelpers,
|
||||
}
|
||||
}
|
||||
|
65
pkg/gui/controllers/confirmation_controller.go
Normal file
65
pkg/gui/controllers/confirmation_controller.go
Normal file
@ -0,0 +1,65 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type ConfirmationController struct {
|
||||
baseController
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &ConfirmationController{}
|
||||
|
||||
func NewConfirmationController(
|
||||
common *ControllerCommon,
|
||||
) *ConfirmationController {
|
||||
return &ConfirmationController{
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
bindings := []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Confirm),
|
||||
Handler: func() error { return self.context().State.OnConfirm() },
|
||||
Description: self.c.Tr.LcConfirm,
|
||||
Display: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: func() error { return self.context().State.OnClose() },
|
||||
Description: self.c.Tr.LcCloseCancel,
|
||||
Display: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: func() error {
|
||||
if len(self.c.Contexts().Suggestions.State.Suggestions) > 0 {
|
||||
return self.c.ReplaceContext(self.c.Contexts().Suggestions)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *ConfirmationController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return func(types.OnFocusLostOpts) error {
|
||||
self.c.Helpers().Confirmation.DeactivateConfirmationPrompt()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ConfirmationController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
||||
func (self *ConfirmationController) context() *context.ConfirmationContext {
|
||||
return self.c.Contexts().Confirmation
|
||||
}
|
@ -24,17 +24,17 @@ var CONTEXT_KEYS_SHOWING_DIFFS = []types.ContextKey{
|
||||
|
||||
type ContextLinesController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &ContextLinesController{}
|
||||
|
||||
func NewContextLinesController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *ContextLinesController {
|
||||
return &ContextLinesController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ func (self *ContextLinesController) applyChange() error {
|
||||
}
|
||||
|
||||
func (self *ContextLinesController) checkCanChangeContext() error {
|
||||
if self.git.Patch.PatchBuilder.Active() {
|
||||
if self.c.Git().Patch.PatchBuilder.Active() {
|
||||
return errors.New(self.c.Tr.CantChangeContextSizeError)
|
||||
}
|
||||
|
||||
|
53
pkg/gui/controllers/custom_command_action.go
Normal file
53
pkg/gui/controllers/custom_command_action.go
Normal file
@ -0,0 +1,53 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type CustomCommandAction struct {
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func (self *CustomCommandAction) Call() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.CustomCommand,
|
||||
FindSuggestionsFunc: self.GetCustomCommandsHistorySuggestionsFunc(),
|
||||
HandleConfirm: func(command string) error {
|
||||
if self.shouldSaveCommand(command) {
|
||||
self.c.GetAppState().CustomCommandsHistory = utils.Limit(
|
||||
lo.Uniq(append(self.c.GetAppState().CustomCommandsHistory, command)),
|
||||
1000,
|
||||
)
|
||||
}
|
||||
|
||||
err := self.c.SaveAppState()
|
||||
if err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CustomCommand)
|
||||
return self.c.RunSubprocessAndRefresh(
|
||||
self.c.OS().Cmd.NewShell(command),
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *CustomCommandAction) GetCustomCommandsHistorySuggestionsFunc() func(string) []*types.Suggestion {
|
||||
// reversing so that we display the latest command first
|
||||
history := slices.Reverse(self.c.GetAppState().CustomCommandsHistory)
|
||||
|
||||
return helpers.FuzzySearchFunc(history)
|
||||
}
|
||||
|
||||
// this mimics the shell functionality `ignorespace`
|
||||
// which doesn't save a command to history if it starts with a space
|
||||
func (self *CustomCommandAction) shouldSaveCommand(command string) bool {
|
||||
return !strings.HasPrefix(command, " ")
|
||||
}
|
219
pkg/gui/controllers/custom_patch_options_menu_action.go
Normal file
219
pkg/gui/controllers/custom_patch_options_menu_action.go
Normal file
@ -0,0 +1,219 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type CustomPatchOptionsMenuAction struct {
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) Call() error {
|
||||
if !self.c.Git().Patch.PatchBuilder.Active() {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoPatchError)
|
||||
}
|
||||
|
||||
menuItems := []*types.MenuItem{
|
||||
{
|
||||
Label: "reset patch",
|
||||
OnPress: self.c.Helpers().PatchBuilding.Reset,
|
||||
Key: 'c',
|
||||
},
|
||||
{
|
||||
Label: "apply patch",
|
||||
OnPress: func() error { return self.handleApplyPatch(false) },
|
||||
Key: 'a',
|
||||
},
|
||||
{
|
||||
Label: "apply patch in reverse",
|
||||
OnPress: func() error { return self.handleApplyPatch(true) },
|
||||
Key: 'r',
|
||||
},
|
||||
}
|
||||
|
||||
if self.c.Git().Patch.PatchBuilder.CanRebase && self.c.Git().Status.WorkingTreeState() == enums.REBASE_MODE_NONE {
|
||||
menuItems = append(menuItems, []*types.MenuItem{
|
||||
{
|
||||
Label: fmt.Sprintf("remove patch from original commit (%s)", self.c.Git().Patch.PatchBuilder.To),
|
||||
OnPress: self.handleDeletePatchFromCommit,
|
||||
Key: 'd',
|
||||
},
|
||||
{
|
||||
Label: "move patch out into index",
|
||||
OnPress: self.handleMovePatchIntoWorkingTree,
|
||||
Key: 'i',
|
||||
},
|
||||
{
|
||||
Label: "move patch into new commit",
|
||||
OnPress: self.handlePullPatchIntoNewCommit,
|
||||
Key: 'n',
|
||||
},
|
||||
}...)
|
||||
|
||||
if self.c.CurrentContext().GetKey() == self.c.Contexts().LocalCommits.GetKey() {
|
||||
selectedCommit := self.c.Contexts().LocalCommits.GetSelected()
|
||||
if selectedCommit != nil && self.c.Git().Patch.PatchBuilder.To != selectedCommit.Sha {
|
||||
// adding this option to index 1
|
||||
menuItems = append(
|
||||
menuItems[:1],
|
||||
append(
|
||||
[]*types.MenuItem{
|
||||
{
|
||||
Label: fmt.Sprintf("move patch to selected commit (%s)", selectedCommit.Sha),
|
||||
OnPress: self.handleMovePatchToSelectedCommit,
|
||||
Key: 'm',
|
||||
},
|
||||
}, menuItems[1:]...,
|
||||
)...,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menuItems = append(menuItems, []*types.MenuItem{
|
||||
{
|
||||
Label: "copy patch to clipboard",
|
||||
OnPress: func() error { return self.copyPatchToClipboard() },
|
||||
Key: 'y',
|
||||
},
|
||||
}...)
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.PatchOptionsTitle, Items: menuItems})
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) getPatchCommitIndex() int {
|
||||
for index, commit := range self.c.Model().Commits {
|
||||
if commit.Sha == self.c.Git().Patch.PatchBuilder.To {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) validateNormalWorkingTreeState() (bool, error) {
|
||||
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
return false, self.c.ErrorMsg(self.c.Tr.CantPatchWhileRebasingError)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) returnFocusFromPatchExplorerIfNecessary() error {
|
||||
if self.c.CurrentContext().GetKey() == self.c.Contexts().CustomPatchBuilder.GetKey() {
|
||||
return self.c.Helpers().PatchBuilding.Escape()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) handleDeletePatchFromCommit() error {
|
||||
if ok, err := self.validateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.returnFocusFromPatchExplorerIfNecessary(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.RemovePatchFromCommit)
|
||||
err := self.c.Git().Patch.DeletePatchesFromCommit(self.c.Model().Commits, commitIndex)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) handleMovePatchToSelectedCommit() error {
|
||||
if ok, err := self.validateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.returnFocusFromPatchExplorerIfNecessary(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchToSelectedCommit)
|
||||
err := self.c.Git().Patch.MovePatchToSelectedCommit(self.c.Model().Commits, commitIndex, self.c.Contexts().LocalCommits.GetSelectedLineIdx())
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) handleMovePatchIntoWorkingTree() error {
|
||||
if ok, err := self.validateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.returnFocusFromPatchExplorerIfNecessary(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pull := func(stash bool) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoIndex)
|
||||
err := self.c.Git().Patch.MovePatchIntoIndex(self.c.Model().Commits, commitIndex, stash)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
|
||||
if self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.MustStashTitle,
|
||||
Prompt: self.c.Tr.MustStashWarning,
|
||||
HandleConfirm: func() error {
|
||||
return pull(true)
|
||||
},
|
||||
})
|
||||
} else {
|
||||
return pull(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error {
|
||||
if ok, err := self.validateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.returnFocusFromPatchExplorerIfNecessary(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit)
|
||||
err := self.c.Git().Patch.PullPatchIntoNewCommit(self.c.Model().Commits, commitIndex)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) handleApplyPatch(reverse bool) error {
|
||||
if err := self.returnFocusFromPatchExplorerIfNecessary(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
action := self.c.Tr.Actions.ApplyPatch
|
||||
if reverse {
|
||||
action = "Apply patch in reverse"
|
||||
}
|
||||
self.c.LogAction(action)
|
||||
if err := self.c.Git().Patch.PatchBuilder.ApplyPatches(reverse); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) copyPatchToClipboard() error {
|
||||
patch := self.c.Git().Patch.PatchBuilder.RenderAggregatedPatch(true)
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyPatchToClipboard)
|
||||
if err := self.c.OS().CopyToClipboard(patch); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.Toast(self.c.Tr.PatchCopiedToClipboard)
|
||||
|
||||
return nil
|
||||
}
|
69
pkg/gui/controllers/diffing_menu_action.go
Normal file
69
pkg/gui/controllers/diffing_menu_action.go
Normal file
@ -0,0 +1,69 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type DiffingMenuAction struct {
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func (self *DiffingMenuAction) Call() error {
|
||||
names := self.c.Helpers().Diff.CurrentDiffTerminals()
|
||||
|
||||
menuItems := []*types.MenuItem{}
|
||||
for _, name := range names {
|
||||
name := name
|
||||
menuItems = append(menuItems, []*types.MenuItem{
|
||||
{
|
||||
Label: fmt.Sprintf("%s %s", self.c.Tr.LcDiff, name),
|
||||
OnPress: func() error {
|
||||
self.c.Modes().Diffing.Ref = name
|
||||
// can scope this down based on current view but too lazy right now
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
},
|
||||
},
|
||||
}...)
|
||||
}
|
||||
|
||||
menuItems = append(menuItems, []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.LcEnterRefToDiff,
|
||||
OnPress: func() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.LcEnteRefName,
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
|
||||
HandleConfirm: func(response string) error {
|
||||
self.c.Modes().Diffing.Ref = strings.TrimSpace(response)
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}...)
|
||||
|
||||
if self.c.Modes().Diffing.Active() {
|
||||
menuItems = append(menuItems, []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.LcSwapDiff,
|
||||
OnPress: func() error {
|
||||
self.c.Modes().Diffing.Reverse = !self.c.Modes().Diffing.Reverse
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.LcExitDiffMode,
|
||||
OnPress: func() error {
|
||||
self.c.Modes().Diffing = diffing.New()
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
},
|
||||
},
|
||||
}...)
|
||||
}
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.DiffingMenuTitle, Items: menuItems})
|
||||
}
|
@ -13,23 +13,16 @@ import (
|
||||
|
||||
type FilesController struct {
|
||||
baseController // nolint: unused
|
||||
*controllerCommon
|
||||
|
||||
enterSubmodule func(submodule *models.SubmoduleConfig) error
|
||||
setCommitMessage func(message string)
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &FilesController{}
|
||||
|
||||
func NewFilesController(
|
||||
common *controllerCommon,
|
||||
enterSubmodule func(submodule *models.SubmoduleConfig) error,
|
||||
setCommitMessage func(message string),
|
||||
common *ControllerCommon,
|
||||
) *FilesController {
|
||||
return &FilesController{
|
||||
controllerCommon: common,
|
||||
enterSubmodule: enterSubmodule,
|
||||
setCommitMessage: setCommitMessage,
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,12 +40,12 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Files.CommitChanges),
|
||||
Handler: self.helpers.WorkingTree.HandleCommitPress,
|
||||
Handler: self.c.Helpers().WorkingTree.HandleCommitPress,
|
||||
Description: self.c.Tr.CommitChanges,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Files.CommitChangesWithoutHook),
|
||||
Handler: self.helpers.WorkingTree.HandleWIPCommitPress,
|
||||
Handler: self.c.Helpers().WorkingTree.HandleWIPCommitPress,
|
||||
Description: self.c.Tr.LcCommitChangesWithoutHook,
|
||||
},
|
||||
{
|
||||
@ -62,7 +55,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Files.CommitChangesWithEditor),
|
||||
Handler: self.helpers.WorkingTree.HandleCommitEditorPress,
|
||||
Handler: self.c.Helpers().WorkingTree.HandleCommitEditorPress,
|
||||
Description: self.c.Tr.CommitChangesWithEditor,
|
||||
},
|
||||
{
|
||||
@ -126,7 +119,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
|
||||
Handler: self.helpers.WorkingTree.OpenMergeTool,
|
||||
Handler: self.c.Helpers().WorkingTree.OpenMergeTool,
|
||||
Description: self.c.Tr.LcOpenMergeTool,
|
||||
},
|
||||
{
|
||||
@ -172,6 +165,74 @@ func (self *FilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []*
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilesController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.c.Helpers().Diff.WithDiffModeCheck(func() error {
|
||||
node := self.context().GetSelected()
|
||||
|
||||
if node == nil {
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: self.c.Tr.DiffTitle,
|
||||
Task: types.NewRenderStringTask(self.c.Tr.NoChangedFiles),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if node.File != nil && node.File.HasInlineMergeConflicts {
|
||||
hasConflicts, err := self.c.Helpers().MergeConflicts.SetMergeState(node.GetPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hasConflicts {
|
||||
return self.c.Helpers().MergeConflicts.Render(false)
|
||||
}
|
||||
}
|
||||
|
||||
self.c.Helpers().MergeConflicts.ResetMergeState()
|
||||
|
||||
pair := self.c.MainViewPairs().Normal
|
||||
if node.File != nil {
|
||||
pair = self.c.MainViewPairs().Staging
|
||||
}
|
||||
|
||||
split := self.c.UserConfig.Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges())
|
||||
mainShowsStaged := !split && node.GetHasStagedChanges()
|
||||
|
||||
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged, self.c.State().GetIgnoreWhitespaceInDiffView())
|
||||
title := self.c.Tr.UnstagedChanges
|
||||
if mainShowsStaged {
|
||||
title = self.c.Tr.StagedChanges
|
||||
}
|
||||
refreshOpts := types.RefreshMainOpts{
|
||||
Pair: pair,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: types.NewRunPtyTask(cmdObj.GetCmd()),
|
||||
Title: title,
|
||||
},
|
||||
}
|
||||
|
||||
if split {
|
||||
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, true, self.c.State().GetIgnoreWhitespaceInDiffView())
|
||||
|
||||
title := self.c.Tr.StagedChanges
|
||||
if mainShowsStaged {
|
||||
title = self.c.Tr.UnstagedChanges
|
||||
}
|
||||
|
||||
refreshOpts.Secondary = &types.ViewUpdateOpts{
|
||||
Title: title,
|
||||
Task: types.NewRunPtyTask(cmdObj.GetCmd()),
|
||||
}
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(refreshOpts)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilesController) GetOnClick() func() error {
|
||||
return self.checkSelectedFileNode(self.press)
|
||||
}
|
||||
@ -227,7 +288,7 @@ func (self *FilesController) optimisticChange(node *filetree.FileNode, optimisti
|
||||
rerender := false
|
||||
err := node.ForEachFile(func(f *models.File) error {
|
||||
// can't act on the file itself: we need to update the original model file
|
||||
for _, modelFile := range self.model.Files {
|
||||
for _, modelFile := range self.c.Model().Files {
|
||||
if modelFile.Name == f.Name {
|
||||
if optimisticChangeFn(modelFile) {
|
||||
rerender = true
|
||||
@ -242,7 +303,7 @@ func (self *FilesController) optimisticChange(node *filetree.FileNode, optimisti
|
||||
return err
|
||||
}
|
||||
if rerender {
|
||||
if err := self.c.PostRefreshUpdate(self.contexts.Files); err != nil {
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -253,8 +314,8 @@ func (self *FilesController) optimisticChange(node *filetree.FileNode, optimisti
|
||||
func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
|
||||
// Obtaining this lock because optimistic rendering requires us to mutate
|
||||
// the files in our model.
|
||||
self.mutexes.RefreshingFilesMutex.Lock()
|
||||
defer self.mutexes.RefreshingFilesMutex.Unlock()
|
||||
self.c.Mutexes().RefreshingFilesMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
|
||||
|
||||
if node.IsFile() {
|
||||
file := node.File
|
||||
@ -266,7 +327,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.git.WorkingTree.StageFile(file.Name); err != nil {
|
||||
if err := self.c.Git().WorkingTree.StageFile(file.Name); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
} else {
|
||||
@ -276,7 +337,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
|
||||
if err := self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
}
|
||||
@ -294,7 +355,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.git.WorkingTree.StageFile(node.Path); err != nil {
|
||||
if err := self.c.Git().WorkingTree.StageFile(node.Path); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
} else {
|
||||
@ -305,7 +366,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
|
||||
}
|
||||
|
||||
// pretty sure it doesn't matter that we're always passing true here
|
||||
if err := self.git.WorkingTree.UnStageFile([]string{node.Path}, true); err != nil {
|
||||
if err := self.c.Git().WorkingTree.UnStageFile([]string{node.Path}, true); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
}
|
||||
@ -346,7 +407,7 @@ func (self *FilesController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *FilesController) context() *context.WorkingTreeContext {
|
||||
return self.contexts.Files
|
||||
return self.c.Contexts().Files
|
||||
}
|
||||
|
||||
func (self *FilesController) getSelectedFile() *models.File {
|
||||
@ -373,10 +434,10 @@ func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
|
||||
|
||||
file := node.File
|
||||
|
||||
submoduleConfigs := self.model.Submodules
|
||||
submoduleConfigs := self.c.Model().Submodules
|
||||
if file.IsSubmodule(submoduleConfigs) {
|
||||
submoduleConfig := file.SubmoduleConfig(submoduleConfigs)
|
||||
return self.enterSubmodule(submoduleConfig)
|
||||
return self.c.Helpers().Repos.EnterSubmodule(submoduleConfig)
|
||||
}
|
||||
|
||||
if file.HasInlineMergeConflicts {
|
||||
@ -386,7 +447,7 @@ func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
|
||||
return self.c.ErrorMsg(self.c.Tr.FileStagingRequirements)
|
||||
}
|
||||
|
||||
return self.c.PushContext(self.contexts.Staging, opts)
|
||||
return self.c.PushContext(self.c.Contexts().Staging, opts)
|
||||
}
|
||||
|
||||
func (self *FilesController) toggleStagedAll() error {
|
||||
@ -402,8 +463,8 @@ func (self *FilesController) toggleStagedAll() error {
|
||||
}
|
||||
|
||||
func (self *FilesController) toggleStagedAllWithLock() error {
|
||||
self.mutexes.RefreshingFilesMutex.Lock()
|
||||
defer self.mutexes.RefreshingFilesMutex.Unlock()
|
||||
self.c.Mutexes().RefreshingFilesMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
|
||||
|
||||
root := self.context().FileTreeViewModel.GetRoot()
|
||||
|
||||
@ -420,7 +481,7 @@ func (self *FilesController) toggleStagedAllWithLock() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.git.WorkingTree.StageAll(); err != nil {
|
||||
if err := self.c.Git().WorkingTree.StageAll(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
} else {
|
||||
@ -430,7 +491,7 @@ func (self *FilesController) toggleStagedAllWithLock() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.git.WorkingTree.UnstageAll(); err != nil {
|
||||
if err := self.c.Git().WorkingTree.UnstageAll(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
}
|
||||
@ -441,7 +502,7 @@ func (self *FilesController) toggleStagedAllWithLock() error {
|
||||
func (self *FilesController) unstageFiles(node *filetree.FileNode) error {
|
||||
return node.ForEachFile(func(file *models.File) error {
|
||||
if file.HasStagedChanges {
|
||||
if err := self.git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
|
||||
if err := self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -457,7 +518,7 @@ func (self *FilesController) ignoreOrExcludeTracked(node *filetree.FileNode, trA
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.git.WorkingTree.RemoveTrackedFiles(node.GetPath()); err != nil {
|
||||
if err := self.c.Git().WorkingTree.RemoveTrackedFiles(node.GetPath()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -495,7 +556,7 @@ func (self *FilesController) ignore(node *filetree.FileNode) error {
|
||||
if node.GetPath() == ".gitignore" {
|
||||
return self.c.ErrorMsg(self.c.Tr.Actions.IgnoreFileErr)
|
||||
}
|
||||
err := self.ignoreOrExcludeFile(node, self.c.Tr.IgnoreTracked, self.c.Tr.IgnoreTrackedPrompt, self.c.Tr.Actions.LcIgnoreExcludeFile, self.git.WorkingTree.Ignore)
|
||||
err := self.ignoreOrExcludeFile(node, self.c.Tr.IgnoreTracked, self.c.Tr.IgnoreTrackedPrompt, self.c.Tr.Actions.LcIgnoreExcludeFile, self.c.Git().WorkingTree.Ignore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -512,7 +573,7 @@ func (self *FilesController) exclude(node *filetree.FileNode) error {
|
||||
return self.c.ErrorMsg(self.c.Tr.Actions.ExcludeGitIgnoreErr)
|
||||
}
|
||||
|
||||
err := self.ignoreOrExcludeFile(node, self.c.Tr.ExcludeTracked, self.c.Tr.ExcludeTrackedPrompt, self.c.Tr.Actions.ExcludeFile, self.git.WorkingTree.Exclude)
|
||||
err := self.ignoreOrExcludeFile(node, self.c.Tr.ExcludeTracked, self.c.Tr.ExcludeTrackedPrompt, self.c.Tr.Actions.ExcludeFile, self.c.Git().WorkingTree.Exclude)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -552,19 +613,19 @@ func (self *FilesController) refresh() error {
|
||||
}
|
||||
|
||||
func (self *FilesController) handleAmendCommitPress() error {
|
||||
if len(self.model.Files) == 0 {
|
||||
if len(self.c.Model().Files) == 0 {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
|
||||
}
|
||||
|
||||
if !self.helpers.WorkingTree.AnyStagedFiles() {
|
||||
return self.helpers.WorkingTree.PromptToStageAllAndRetry(self.handleAmendCommitPress)
|
||||
if !self.c.Helpers().WorkingTree.AnyStagedFiles() {
|
||||
return self.c.Helpers().WorkingTree.PromptToStageAllAndRetry(self.handleAmendCommitPress)
|
||||
}
|
||||
|
||||
if len(self.model.Commits) == 0 {
|
||||
if len(self.c.Model().Commits) == 0 {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoCommitToAmend)
|
||||
}
|
||||
|
||||
return self.helpers.AmendHelper.AmendHead()
|
||||
return self.c.Helpers().AmendHelper.AmendHead()
|
||||
}
|
||||
|
||||
func (self *FilesController) handleStatusFilterPressed() error {
|
||||
@ -603,7 +664,7 @@ func (self *FilesController) edit(node *filetree.FileNode) error {
|
||||
return self.c.ErrorMsg(self.c.Tr.ErrCannotEditDirectory)
|
||||
}
|
||||
|
||||
return self.helpers.Files.EditFile(node.GetPath())
|
||||
return self.c.Helpers().Files.EditFile(node.GetPath())
|
||||
}
|
||||
|
||||
func (self *FilesController) Open() error {
|
||||
@ -612,7 +673,7 @@ func (self *FilesController) Open() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.helpers.Files.OpenFile(node.GetPath())
|
||||
return self.c.Helpers().Files.OpenFile(node.GetPath())
|
||||
}
|
||||
|
||||
func (self *FilesController) switchToMerge() error {
|
||||
@ -621,7 +682,7 @@ func (self *FilesController) switchToMerge() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.helpers.MergeConflicts.SwitchToMerge(file.Name)
|
||||
return self.c.Helpers().MergeConflicts.SwitchToMerge(file.Name)
|
||||
}
|
||||
|
||||
func (self *FilesController) createStashMenu() error {
|
||||
@ -631,28 +692,28 @@ func (self *FilesController) createStashMenu() error {
|
||||
{
|
||||
Label: self.c.Tr.LcStashAllChanges,
|
||||
OnPress: func() error {
|
||||
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
|
||||
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
|
||||
}
|
||||
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges)
|
||||
return self.handleStashSave(self.c.Git().Stash.Save, self.c.Tr.Actions.StashAllChanges)
|
||||
},
|
||||
Key: 'a',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.LcStashAllChangesKeepIndex,
|
||||
OnPress: func() error {
|
||||
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
|
||||
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
|
||||
}
|
||||
// if there are no staged files it behaves the same as Stash.Save
|
||||
return self.handleStashSave(self.git.Stash.StashAndKeepIndex, self.c.Tr.Actions.StashAllChangesKeepIndex)
|
||||
return self.handleStashSave(self.c.Git().Stash.StashAndKeepIndex, self.c.Tr.Actions.StashAllChangesKeepIndex)
|
||||
},
|
||||
Key: 'i',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.LcStashIncludeUntrackedChanges,
|
||||
OnPress: func() error {
|
||||
return self.handleStashSave(self.git.Stash.StashIncludeUntrackedChanges, self.c.Tr.Actions.StashIncludeUntrackedChanges)
|
||||
return self.handleStashSave(self.c.Git().Stash.StashIncludeUntrackedChanges, self.c.Tr.Actions.StashIncludeUntrackedChanges)
|
||||
},
|
||||
Key: 'U',
|
||||
},
|
||||
@ -660,24 +721,24 @@ func (self *FilesController) createStashMenu() error {
|
||||
Label: self.c.Tr.LcStashStagedChanges,
|
||||
OnPress: func() error {
|
||||
// there must be something in staging otherwise the current implementation mucks the stash up
|
||||
if !self.helpers.WorkingTree.AnyStagedFiles() {
|
||||
if !self.c.Helpers().WorkingTree.AnyStagedFiles() {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoTrackedStagedFilesStash)
|
||||
}
|
||||
return self.handleStashSave(self.git.Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges)
|
||||
return self.handleStashSave(self.c.Git().Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges)
|
||||
},
|
||||
Key: 's',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.LcStashUnstagedChanges,
|
||||
OnPress: func() error {
|
||||
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
|
||||
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
|
||||
}
|
||||
if self.helpers.WorkingTree.AnyStagedFiles() {
|
||||
return self.handleStashSave(self.git.Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges)
|
||||
if self.c.Helpers().WorkingTree.AnyStagedFiles() {
|
||||
return self.handleStashSave(self.c.Git().Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges)
|
||||
}
|
||||
// ordinary stash
|
||||
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashUnstagedChanges)
|
||||
return self.handleStashSave(self.c.Git().Stash.Save, self.c.Tr.Actions.StashUnstagedChanges)
|
||||
},
|
||||
Key: 'u',
|
||||
},
|
||||
@ -686,11 +747,11 @@ func (self *FilesController) createStashMenu() error {
|
||||
}
|
||||
|
||||
func (self *FilesController) stash() error {
|
||||
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges)
|
||||
return self.handleStashSave(self.c.Git().Stash.Save, self.c.Tr.Actions.StashAllChanges)
|
||||
}
|
||||
|
||||
func (self *FilesController) createResetToUpstreamMenu() error {
|
||||
return self.helpers.Refs.CreateGitResetMenu("@{upstream}")
|
||||
return self.c.Helpers().Refs.CreateGitResetMenu("@{upstream}")
|
||||
}
|
||||
|
||||
func (self *FilesController) handleToggleDirCollapsed() error {
|
||||
@ -701,7 +762,7 @@ func (self *FilesController) handleToggleDirCollapsed() error {
|
||||
|
||||
self.context().FileTreeViewModel.ToggleCollapsed(node.GetPath())
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.contexts.Files); err != nil {
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
@ -747,7 +808,7 @@ func (self *FilesController) fetch() error {
|
||||
|
||||
func (self *FilesController) fetchAux() (err error) {
|
||||
self.c.LogAction("Fetch")
|
||||
err = self.git.Sync.Fetch(git_commands.FetchOptions{})
|
||||
err = self.c.Git().Sync.Fetch(git_commands.FetchOptions{})
|
||||
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 128") {
|
||||
_ = self.c.ErrorMsg(self.c.Tr.PassUnameWrong)
|
||||
|
@ -12,17 +12,17 @@ import (
|
||||
|
||||
type FilesRemoveController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &FilesRemoveController{}
|
||||
|
||||
func NewFilesRemoveController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *FilesRemoveController {
|
||||
return &FilesRemoveController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
|
||||
Label: self.c.Tr.LcDiscardAllChanges,
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DiscardAllChangesInDirectory)
|
||||
if err := self.git.WorkingTree.DiscardAllDirChanges(node); err != nil {
|
||||
if err := self.c.Git().WorkingTree.DiscardAllDirChanges(node); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
|
||||
@ -67,7 +67,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
|
||||
Label: self.c.Tr.LcDiscardUnstagedChanges,
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DiscardUnstagedChangesInDirectory)
|
||||
if err := self.git.WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
|
||||
if err := self.c.Git().WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -85,7 +85,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
|
||||
} else {
|
||||
file := node.File
|
||||
|
||||
submodules := self.model.Submodules
|
||||
submodules := self.c.Model().Submodules
|
||||
if file.IsSubmodule(submodules) {
|
||||
submodule := file.SubmoduleConfig(submodules)
|
||||
|
||||
@ -103,7 +103,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
|
||||
Label: self.c.Tr.LcDiscardAllChanges,
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DiscardAllChangesInFile)
|
||||
if err := self.git.WorkingTree.DiscardAllFileChanges(file); err != nil {
|
||||
if err := self.c.Git().WorkingTree.DiscardAllFileChanges(file); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
|
||||
@ -123,7 +123,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
|
||||
Label: self.c.Tr.LcDiscardUnstagedChanges,
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DiscardAllUnstagedChangesInFile)
|
||||
if err := self.git.WorkingTree.DiscardUnstagedFileChanges(file); err != nil {
|
||||
if err := self.c.Git().WorkingTree.DiscardUnstagedFileChanges(file); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -148,17 +148,17 @@ func (self *FilesRemoveController) ResetSubmodule(submodule *models.SubmoduleCon
|
||||
return self.c.WithWaitingStatus(self.c.Tr.LcResettingSubmoduleStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ResetSubmodule)
|
||||
|
||||
file := self.helpers.WorkingTree.FileForSubmodule(submodule)
|
||||
file := self.c.Helpers().WorkingTree.FileForSubmodule(submodule)
|
||||
if file != nil {
|
||||
if err := self.git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
|
||||
if err := self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := self.git.Submodule.Stash(submodule); err != nil {
|
||||
if err := self.c.Git().Submodule.Stash(submodule); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.git.Submodule.Reset(submodule); err != nil {
|
||||
if err := self.c.Git().Submodule.Reset(submodule); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -182,5 +182,5 @@ func (self *FilesRemoveController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *FilesRemoveController) context() *context.WorkingTreeContext {
|
||||
return self.contexts.Files
|
||||
return self.c.Contexts().Files
|
||||
}
|
||||
|
78
pkg/gui/controllers/filtering_menu_action.go
Normal file
78
pkg/gui/controllers/filtering_menu_action.go
Normal file
@ -0,0 +1,78 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type FilteringMenuAction struct {
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func (self *FilteringMenuAction) Call() error {
|
||||
fileName := ""
|
||||
switch self.c.CurrentSideContext() {
|
||||
case self.c.Contexts().Files:
|
||||
node := self.c.Contexts().Files.GetSelected()
|
||||
if node != nil {
|
||||
fileName = node.GetPath()
|
||||
}
|
||||
case self.c.Contexts().CommitFiles:
|
||||
node := self.c.Contexts().CommitFiles.GetSelected()
|
||||
if node != nil {
|
||||
fileName = node.GetPath()
|
||||
}
|
||||
}
|
||||
|
||||
menuItems := []*types.MenuItem{}
|
||||
|
||||
if fileName != "" {
|
||||
menuItems = append(menuItems, &types.MenuItem{
|
||||
Label: fmt.Sprintf("%s '%s'", self.c.Tr.LcFilterBy, fileName),
|
||||
OnPress: func() error {
|
||||
return self.setFiltering(fileName)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
menuItems = append(menuItems, &types.MenuItem{
|
||||
Label: self.c.Tr.LcFilterPathOption,
|
||||
OnPress: func() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetFilePathSuggestionsFunc(),
|
||||
Title: self.c.Tr.EnterFileName,
|
||||
HandleConfirm: func(response string) error {
|
||||
return self.setFiltering(strings.TrimSpace(response))
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
if self.c.Modes().Filtering.Active() {
|
||||
menuItems = append(menuItems, &types.MenuItem{
|
||||
Label: self.c.Tr.LcExitFilterMode,
|
||||
OnPress: self.c.Helpers().Mode.ClearFiltering,
|
||||
})
|
||||
}
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.FilteringMenuTitle, Items: menuItems})
|
||||
}
|
||||
|
||||
func (self *FilteringMenuAction) setFiltering(path string) error {
|
||||
self.c.Modes().Filtering.SetPath(path)
|
||||
|
||||
repoState := self.c.State().GetRepoState()
|
||||
if repoState.GetScreenMode() == types.SCREEN_NORMAL {
|
||||
repoState.SetScreenMode(types.SCREEN_HALF)
|
||||
}
|
||||
|
||||
if err := self.c.PushContext(self.c.Contexts().LocalCommits); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}, Then: func() {
|
||||
self.c.Contexts().LocalCommits.SetSelectedLineIdx(0)
|
||||
}})
|
||||
}
|
@ -11,17 +11,17 @@ import (
|
||||
|
||||
type GitFlowController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &GitFlowController{}
|
||||
|
||||
func NewGitFlowController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *GitFlowController {
|
||||
return &GitFlowController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ func (self *GitFlowController) GetKeybindings(opts types.KeybindingsOpts) []*typ
|
||||
}
|
||||
|
||||
func (self *GitFlowController) handleCreateGitFlowMenu(branch *models.Branch) error {
|
||||
if !self.git.Flow.GitFlowEnabled() {
|
||||
if !self.c.Git().Flow.GitFlowEnabled() {
|
||||
return self.c.ErrorMsg("You need to install git-flow and enable it in this repo to use git-flow features")
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ func (self *GitFlowController) handleCreateGitFlowMenu(branch *models.Branch) er
|
||||
HandleConfirm: func(name string) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.GitFlowStart)
|
||||
return self.c.RunSubprocessAndRefresh(
|
||||
self.git.Flow.StartCmdObj(branchType, name),
|
||||
self.c.Git().Flow.StartCmdObj(branchType, name),
|
||||
)
|
||||
},
|
||||
})
|
||||
@ -94,7 +94,7 @@ func (self *GitFlowController) handleCreateGitFlowMenu(branch *models.Branch) er
|
||||
}
|
||||
|
||||
func (self *GitFlowController) gitFlowFinishBranch(branchName string) error {
|
||||
cmdObj, err := self.git.Flow.FinishCmdObj(branchName)
|
||||
cmdObj, err := self.c.Git().Flow.FinishCmdObj(branchName)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
@ -119,5 +119,5 @@ func (self *GitFlowController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *GitFlowController) context() *context.BranchesContext {
|
||||
return self.contexts.Branches
|
||||
return self.c.Contexts().Branches
|
||||
}
|
||||
|
@ -1,26 +1,21 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type GlobalController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func NewGlobalController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *GlobalController {
|
||||
return &GlobalController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,47 +26,143 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
|
||||
Handler: self.customCommand,
|
||||
Description: self.c.Tr.LcExecuteCustomCommand,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *GlobalController) customCommand() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.CustomCommand,
|
||||
FindSuggestionsFunc: self.GetCustomCommandsHistorySuggestionsFunc(),
|
||||
HandleConfirm: func(command string) error {
|
||||
if self.shouldSaveCommand(command) {
|
||||
self.c.GetAppState().CustomCommandsHistory = utils.Limit(
|
||||
lo.Uniq(append(self.c.GetAppState().CustomCommandsHistory, command)),
|
||||
1000,
|
||||
)
|
||||
}
|
||||
|
||||
err := self.c.SaveAppState()
|
||||
if err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CustomCommand)
|
||||
return self.c.RunSubprocessAndRefresh(
|
||||
self.os.Cmd.NewShell(command),
|
||||
)
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.CreatePatchOptionsMenu),
|
||||
Handler: self.createCustomPatchOptionsMenu,
|
||||
Description: self.c.Tr.ViewPatchOptions,
|
||||
OpensMenu: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// this mimics the shell functionality `ignorespace`
|
||||
// which doesn't save a command to history if it starts with a space
|
||||
func (self *GlobalController) shouldSaveCommand(command string) bool {
|
||||
return !strings.HasPrefix(command, " ")
|
||||
}
|
||||
|
||||
func (self *GlobalController) GetCustomCommandsHistorySuggestionsFunc() func(string) []*types.Suggestion {
|
||||
// reversing so that we display the latest command first
|
||||
history := slices.Reverse(self.c.GetAppState().CustomCommandsHistory)
|
||||
|
||||
return helpers.FuzzySearchFunc(history)
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.CreateRebaseOptionsMenu),
|
||||
Handler: self.c.Helpers().MergeAndRebase.CreateRebaseOptionsMenu,
|
||||
Description: self.c.Tr.ViewMergeRebaseOptions,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Refresh),
|
||||
Handler: self.refresh,
|
||||
Description: self.c.Tr.LcRefresh,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.NextScreenMode),
|
||||
Handler: self.nextScreenMode,
|
||||
Description: self.c.Tr.LcNextScreenMode,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevScreenMode),
|
||||
Handler: self.prevScreenMode,
|
||||
Description: self.c.Tr.LcPrevScreenMode,
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
Key: opts.GetKey(opts.Config.Universal.OptionMenu),
|
||||
Handler: self.createOptionsMenu,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
Key: opts.GetKey(opts.Config.Universal.OptionMenuAlt1),
|
||||
Modifier: gocui.ModNone,
|
||||
// we have the description on the alt key and not the main key for legacy reasons
|
||||
// (the original main key was 'x' but we've reassigned that to other purposes)
|
||||
Description: self.c.Tr.LcOpenMenu,
|
||||
Handler: self.createOptionsMenu,
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
Key: opts.GetKey(opts.Config.Universal.FilteringMenu),
|
||||
Handler: self.createFilteringMenu,
|
||||
Description: self.c.Tr.LcOpenFilteringMenu,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.DiffingMenu),
|
||||
Handler: self.createDiffingMenu,
|
||||
Description: self.c.Tr.LcOpenDiffingMenu,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.DiffingMenuAlt),
|
||||
Handler: self.createDiffingMenu,
|
||||
Description: self.c.Tr.LcOpenDiffingMenu,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Quit),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.quit,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.QuitAlt1),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.quit,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.QuitWithoutChangingDirectory),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.quitWithoutChangingDirectory,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.escape,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.ToggleWhitespaceInDiffView),
|
||||
Handler: self.toggleWhitespace,
|
||||
Description: self.c.Tr.ToggleWhitespaceInDiffView,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *GlobalController) Context() types.Context {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *GlobalController) customCommand() error {
|
||||
return (&CustomCommandAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
func (self *GlobalController) createCustomPatchOptionsMenu() error {
|
||||
return (&CustomPatchOptionsMenuAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
func (self *GlobalController) refresh() error {
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
}
|
||||
|
||||
func (self *GlobalController) nextScreenMode() error {
|
||||
return (&ScreenModeActions{c: self.c}).Next()
|
||||
}
|
||||
|
||||
func (self *GlobalController) prevScreenMode() error {
|
||||
return (&ScreenModeActions{c: self.c}).Prev()
|
||||
}
|
||||
|
||||
func (self *GlobalController) createOptionsMenu() error {
|
||||
return (&OptionsMenuAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
func (self *GlobalController) createFilteringMenu() error {
|
||||
return (&FilteringMenuAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
func (self *GlobalController) createDiffingMenu() error {
|
||||
return (&DiffingMenuAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
func (self *GlobalController) quit() error {
|
||||
return (&QuitActions{c: self.c}).Quit()
|
||||
}
|
||||
|
||||
func (self *GlobalController) quitWithoutChangingDirectory() error {
|
||||
return (&QuitActions{c: self.c}).QuitWithoutChangingDirectory()
|
||||
}
|
||||
|
||||
func (self *GlobalController) escape() error {
|
||||
return (&QuitActions{c: self.c}).Escape()
|
||||
}
|
||||
|
||||
func (self *GlobalController) toggleWhitespace() error {
|
||||
return (&ToggleWhitespaceAction{c: self.c}).Call()
|
||||
}
|
||||
|
@ -1,24 +1,20 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type AmendHelper struct {
|
||||
c *types.HelperCommon
|
||||
git *commands.GitCommand
|
||||
c *HelperCommon
|
||||
gpg *GpgHelper
|
||||
}
|
||||
|
||||
func NewAmendHelper(
|
||||
c *types.HelperCommon,
|
||||
git *commands.GitCommand,
|
||||
c *HelperCommon,
|
||||
gpg *GpgHelper,
|
||||
) *AmendHelper {
|
||||
return &AmendHelper{
|
||||
c: c,
|
||||
git: git,
|
||||
gpg: gpg,
|
||||
}
|
||||
}
|
||||
@ -28,7 +24,7 @@ func (self *AmendHelper) AmendHead() error {
|
||||
Title: self.c.Tr.AmendLastCommitTitle,
|
||||
Prompt: self.c.Tr.SureToAmend,
|
||||
HandleConfirm: func() error {
|
||||
cmdObj := self.git.Commit.AmendHeadCmdObj()
|
||||
cmdObj := self.c.Git().Commit.AmendHeadCmdObj()
|
||||
self.c.LogAction(self.c.Tr.Actions.AmendCommit)
|
||||
return self.gpg.WithGpgHandling(cmdObj, self.c.Tr.AmendingStatus, nil)
|
||||
},
|
||||
|
68
pkg/gui/controllers/helpers/app_status_helper.go
Normal file
68
pkg/gui/controllers/helpers/app_status_helper.go
Normal file
@ -0,0 +1,68 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/status"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type AppStatusHelper struct {
|
||||
c *HelperCommon
|
||||
|
||||
statusMgr func() *status.StatusManager
|
||||
}
|
||||
|
||||
func NewAppStatusHelper(c *HelperCommon, statusMgr func() *status.StatusManager) *AppStatusHelper {
|
||||
return &AppStatusHelper{
|
||||
c: c,
|
||||
statusMgr: statusMgr,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *AppStatusHelper) Toast(message string) {
|
||||
self.statusMgr().AddToastStatus(message)
|
||||
|
||||
self.renderAppStatus()
|
||||
}
|
||||
|
||||
// withWaitingStatus wraps a function and shows a waiting status while the function is still executing
|
||||
func (self *AppStatusHelper) WithWaitingStatus(message string, f func() error) {
|
||||
go utils.Safe(func() {
|
||||
self.statusMgr().WithWaitingStatus(message, func() {
|
||||
self.renderAppStatus()
|
||||
|
||||
if err := f(); err != nil {
|
||||
self.c.OnUIThread(func() error {
|
||||
return self.c.Error(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (self *AppStatusHelper) HasStatus() bool {
|
||||
return self.statusMgr().HasStatus()
|
||||
}
|
||||
|
||||
func (self *AppStatusHelper) GetStatusString() string {
|
||||
return self.statusMgr().GetStatusString()
|
||||
}
|
||||
|
||||
func (self *AppStatusHelper) renderAppStatus() {
|
||||
go utils.Safe(func() {
|
||||
ticker := time.NewTicker(time.Millisecond * 50)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
appStatus := self.statusMgr().GetStatusString()
|
||||
self.c.OnUIThread(func() error {
|
||||
self.c.SetViewContent(self.c.Views().AppStatus, appStatus)
|
||||
return nil
|
||||
})
|
||||
|
||||
if appStatus == "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -1,23 +1,15 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type BisectHelper struct {
|
||||
c *types.HelperCommon
|
||||
git *commands.GitCommand
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewBisectHelper(
|
||||
c *types.HelperCommon,
|
||||
git *commands.GitCommand,
|
||||
) *BisectHelper {
|
||||
return &BisectHelper{
|
||||
c: c,
|
||||
git: git,
|
||||
}
|
||||
func NewBisectHelper(c *HelperCommon) *BisectHelper {
|
||||
return &BisectHelper{c: c}
|
||||
}
|
||||
|
||||
func (self *BisectHelper) Reset() error {
|
||||
@ -26,7 +18,7 @@ func (self *BisectHelper) Reset() error {
|
||||
Prompt: self.c.Tr.Bisect.ResetPrompt,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ResetBisect)
|
||||
if err := self.git.Bisect.Reset(); err != nil {
|
||||
if err := self.c.Git().Bisect.Reset(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
|
@ -1,22 +1,13 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type CherryPickHelper struct {
|
||||
c *types.HelperCommon
|
||||
|
||||
git *commands.GitCommand
|
||||
|
||||
contexts *context.ContextTree
|
||||
getData func() *cherrypicking.CherryPicking
|
||||
c *HelperCommon
|
||||
|
||||
rebaseHelper *MergeAndRebaseHelper
|
||||
}
|
||||
@ -25,38 +16,31 @@ type CherryPickHelper struct {
|
||||
// even if in truth we're running git cherry-pick
|
||||
|
||||
func NewCherryPickHelper(
|
||||
c *types.HelperCommon,
|
||||
git *commands.GitCommand,
|
||||
contexts *context.ContextTree,
|
||||
getData func() *cherrypicking.CherryPicking,
|
||||
c *HelperCommon,
|
||||
rebaseHelper *MergeAndRebaseHelper,
|
||||
) *CherryPickHelper {
|
||||
return &CherryPickHelper{
|
||||
c: c,
|
||||
git: git,
|
||||
contexts: contexts,
|
||||
getData: getData,
|
||||
rebaseHelper: rebaseHelper,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CherryPickHelper) getData() *cherrypicking.CherryPicking {
|
||||
return self.c.Modes().CherryPicking
|
||||
}
|
||||
|
||||
func (self *CherryPickHelper) Copy(commit *models.Commit, commitsList []*models.Commit, context types.Context) error {
|
||||
if err := self.resetIfNecessary(context); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// we will un-copy it if it's already copied
|
||||
for index, cherryPickedCommit := range self.getData().CherryPickedCommits {
|
||||
if commit.Sha == cherryPickedCommit.Sha {
|
||||
self.getData().CherryPickedCommits = append(
|
||||
self.getData().CherryPickedCommits[0:index],
|
||||
self.getData().CherryPickedCommits[index+1:]...,
|
||||
)
|
||||
return self.rerender()
|
||||
}
|
||||
if self.getData().SelectedShaSet().Includes(commit.Sha) {
|
||||
self.getData().Remove(commit, commitsList)
|
||||
} else {
|
||||
self.getData().Add(commit, commitsList)
|
||||
}
|
||||
|
||||
self.add(commit, commitsList)
|
||||
return self.rerender()
|
||||
}
|
||||
|
||||
@ -65,7 +49,7 @@ func (self *CherryPickHelper) CopyRange(selectedIndex int, commitsList []*models
|
||||
return err
|
||||
}
|
||||
|
||||
commitSet := self.CherryPickedCommitShaSet()
|
||||
commitSet := self.getData().SelectedShaSet()
|
||||
|
||||
// find the last commit that is copied that's above our position
|
||||
// if there are none, startIndex = 0
|
||||
@ -78,7 +62,7 @@ func (self *CherryPickHelper) CopyRange(selectedIndex int, commitsList []*models
|
||||
|
||||
for index := startIndex; index <= selectedIndex; index++ {
|
||||
commit := commitsList[index]
|
||||
self.add(commit, commitsList)
|
||||
self.getData().Add(commit, commitsList)
|
||||
}
|
||||
|
||||
return self.rerender()
|
||||
@ -93,7 +77,7 @@ func (self *CherryPickHelper) Paste() error {
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.CherryPickingStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CherryPick)
|
||||
err := self.git.Rebase.CherryPickCommits(self.getData().CherryPickedCommits)
|
||||
err := self.c.Git().Rebase.CherryPickCommits(self.getData().CherryPickedCommits)
|
||||
return self.rebaseHelper.CheckMergeOrRebase(err)
|
||||
})
|
||||
},
|
||||
@ -107,26 +91,6 @@ func (self *CherryPickHelper) Reset() error {
|
||||
return self.rerender()
|
||||
}
|
||||
|
||||
func (self *CherryPickHelper) CherryPickedCommitShaSet() *set.Set[string] {
|
||||
shas := slices.Map(self.getData().CherryPickedCommits, func(commit *models.Commit) string {
|
||||
return commit.Sha
|
||||
})
|
||||
return set.NewFromSlice(shas)
|
||||
}
|
||||
|
||||
func (self *CherryPickHelper) add(selectedCommit *models.Commit, commitsList []*models.Commit) {
|
||||
commitSet := self.CherryPickedCommitShaSet()
|
||||
commitSet.Add(selectedCommit.Sha)
|
||||
|
||||
cherryPickedCommits := slices.Filter(commitsList, func(commit *models.Commit) bool {
|
||||
return commitSet.Includes(commit.Sha)
|
||||
})
|
||||
|
||||
self.getData().CherryPickedCommits = slices.Map(cherryPickedCommits, func(commit *models.Commit) *models.Commit {
|
||||
return &models.Commit{Name: commit.Name, Sha: commit.Sha}
|
||||
})
|
||||
}
|
||||
|
||||
// you can only copy from one context at a time, because the order and position of commits matter
|
||||
func (self *CherryPickHelper) resetIfNecessary(context types.Context) error {
|
||||
oldContextKey := types.ContextKey(self.getData().ContextKey)
|
||||
@ -142,9 +106,9 @@ func (self *CherryPickHelper) resetIfNecessary(context types.Context) error {
|
||||
|
||||
func (self *CherryPickHelper) rerender() error {
|
||||
for _, context := range []types.Context{
|
||||
self.contexts.LocalCommits,
|
||||
self.contexts.ReflogCommits,
|
||||
self.contexts.SubCommits,
|
||||
self.c.Contexts().LocalCommits,
|
||||
self.c.Contexts().ReflogCommits,
|
||||
self.c.Contexts().SubCommits,
|
||||
} {
|
||||
if err := self.c.PostRefreshUpdate(context); err != nil {
|
||||
return err
|
||||
|
@ -3,7 +3,6 @@ package helpers
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -12,38 +11,29 @@ type ICommitsHelper interface {
|
||||
}
|
||||
|
||||
type CommitsHelper struct {
|
||||
c *types.HelperCommon
|
||||
c *HelperCommon
|
||||
|
||||
model *types.Model
|
||||
contexts *context.ContextTree
|
||||
getCommitSummary func() string
|
||||
setCommitSummary func(string)
|
||||
getCommitDescription func() string
|
||||
setCommitDescription func(string)
|
||||
renderCommitLength func()
|
||||
}
|
||||
|
||||
var _ ICommitsHelper = &CommitsHelper{}
|
||||
|
||||
func NewCommitsHelper(
|
||||
c *types.HelperCommon,
|
||||
model *types.Model,
|
||||
contexts *context.ContextTree,
|
||||
c *HelperCommon,
|
||||
getCommitSummary func() string,
|
||||
setCommitSummary func(string),
|
||||
getCommitDescription func() string,
|
||||
setCommitDescription func(string),
|
||||
renderCommitLength func(),
|
||||
) *CommitsHelper {
|
||||
return &CommitsHelper{
|
||||
c: c,
|
||||
model: model,
|
||||
contexts: contexts,
|
||||
getCommitSummary: getCommitSummary,
|
||||
setCommitSummary: setCommitSummary,
|
||||
getCommitDescription: getCommitDescription,
|
||||
setCommitDescription: setCommitDescription,
|
||||
renderCommitLength: renderCommitLength,
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +52,7 @@ func (self *CommitsHelper) SetMessageAndDescriptionInView(message string) {
|
||||
|
||||
self.setCommitSummary(summary)
|
||||
self.setCommitDescription(description)
|
||||
self.renderCommitLength()
|
||||
self.c.Contexts().CommitMessage.RenderCommitLength()
|
||||
}
|
||||
|
||||
func (self *CommitsHelper) JoinCommitMessageAndDescription() string {
|
||||
@ -78,7 +68,7 @@ func (self *CommitsHelper) UpdateCommitPanelView(message string) {
|
||||
self.SetMessageAndDescriptionInView(message)
|
||||
return
|
||||
}
|
||||
message = self.contexts.CommitMessage.GetPreservedMessage()
|
||||
message = self.c.Contexts().CommitMessage.GetPreservedMessage()
|
||||
if message != "" {
|
||||
self.SetMessageAndDescriptionInView(message)
|
||||
} else {
|
||||
@ -95,7 +85,7 @@ type OpenCommitMessagePanelOpts struct {
|
||||
}
|
||||
|
||||
func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOpts) error {
|
||||
self.contexts.CommitMessage.SetPanelState(
|
||||
self.c.Contexts().CommitMessage.SetPanelState(
|
||||
opts.CommitIndex,
|
||||
opts.Title,
|
||||
opts.PreserveMessage,
|
||||
@ -109,8 +99,8 @@ func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOp
|
||||
|
||||
func (self *CommitsHelper) OnCommitSuccess() {
|
||||
// if we have a preserved message we want to clear it on success
|
||||
if self.contexts.CommitMessage.GetPreserveMessage() {
|
||||
self.contexts.CommitMessage.SetPreservedMessage("")
|
||||
if self.c.Contexts().CommitMessage.GetPreserveMessage() {
|
||||
self.c.Contexts().CommitMessage.SetPreservedMessage("")
|
||||
}
|
||||
self.SetMessageAndDescriptionInView("")
|
||||
}
|
||||
@ -122,7 +112,7 @@ func (self *CommitsHelper) HandleCommitConfirm() error {
|
||||
return self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr)
|
||||
}
|
||||
|
||||
err := self.contexts.CommitMessage.OnConfirm(fullMessage)
|
||||
err := self.c.Contexts().CommitMessage.OnConfirm(fullMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -131,15 +121,15 @@ func (self *CommitsHelper) HandleCommitConfirm() error {
|
||||
}
|
||||
|
||||
func (self *CommitsHelper) CloseCommitMessagePanel() error {
|
||||
if self.contexts.CommitMessage.GetPreserveMessage() {
|
||||
if self.c.Contexts().CommitMessage.GetPreserveMessage() {
|
||||
message := self.JoinCommitMessageAndDescription()
|
||||
|
||||
self.contexts.CommitMessage.SetPreservedMessage(message)
|
||||
self.c.Contexts().CommitMessage.SetPreservedMessage(message)
|
||||
} else {
|
||||
self.SetMessageAndDescriptionInView("")
|
||||
}
|
||||
|
||||
self.contexts.CommitMessage.SetHistoryMessage("")
|
||||
self.c.Contexts().CommitMessage.SetHistoryMessage("")
|
||||
|
||||
return self.PopCommitMessageContexts()
|
||||
}
|
||||
@ -160,7 +150,7 @@ func (self *CommitsHelper) pushCommitMessageContexts() error {
|
||||
|
||||
func (self *CommitsHelper) commitMessageContexts() []types.Context {
|
||||
return []types.Context{
|
||||
self.contexts.CommitDescription,
|
||||
self.contexts.CommitMessage,
|
||||
self.c.Contexts().CommitDescription,
|
||||
self.c.Contexts().CommitMessage,
|
||||
}
|
||||
}
|
||||
|
349
pkg/gui/controllers/helpers/confirmation_helper.go
Normal file
349
pkg/gui/controllers/helpers/confirmation_helper.go
Normal file
@ -0,0 +1,349 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
goContext "context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
type ConfirmationHelper struct {
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewConfirmationHelper(c *HelperCommon) *ConfirmationHelper {
|
||||
return &ConfirmationHelper{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
// This file is for the rendering of confirmation panels along with setting and handling associated
|
||||
// keybindings.
|
||||
|
||||
func (self *ConfirmationHelper) wrappedConfirmationFunction(cancel goContext.CancelFunc, function func() error) func() error {
|
||||
return func() error {
|
||||
cancel()
|
||||
|
||||
if err := self.c.PopContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if function != nil {
|
||||
if err := function(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) wrappedPromptConfirmationFunction(cancel goContext.CancelFunc, function func(string) error, getResponse func() string) func() error {
|
||||
return self.wrappedConfirmationFunction(cancel, func() error {
|
||||
return function(getResponse())
|
||||
})
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) DeactivateConfirmationPrompt() {
|
||||
self.c.Mutexes().PopupMutex.Lock()
|
||||
self.c.State().GetRepoState().SetCurrentPopupOpts(nil)
|
||||
self.c.Mutexes().PopupMutex.Unlock()
|
||||
|
||||
self.c.Views().Confirmation.Visible = false
|
||||
self.c.Views().Suggestions.Visible = false
|
||||
|
||||
self.clearConfirmationViewKeyBindings()
|
||||
}
|
||||
|
||||
func getMessageHeight(wrap bool, message string, width int) int {
|
||||
lines := strings.Split(message, "\n")
|
||||
lineCount := 0
|
||||
// if we need to wrap, calculate height to fit content within view's width
|
||||
if wrap {
|
||||
for _, line := range lines {
|
||||
lineCount += runewidth.StringWidth(line)/width + 1
|
||||
}
|
||||
} else {
|
||||
lineCount = len(lines)
|
||||
}
|
||||
return lineCount
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) getPopupPanelDimensions(wrap bool, prompt string) (int, int, int, int) {
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
panelHeight := getMessageHeight(wrap, prompt, panelWidth)
|
||||
return self.getPopupPanelDimensionsAux(panelWidth, panelHeight)
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) getPopupPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) {
|
||||
return self.getPopupPanelDimensionsAux(panelWidth, contentHeight)
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) getPopupPanelDimensionsAux(panelWidth int, panelHeight int) (int, int, int, int) {
|
||||
width, height := self.c.GocuiGui().Size()
|
||||
if panelHeight > height*3/4 {
|
||||
panelHeight = height * 3 / 4
|
||||
}
|
||||
return width/2 - panelWidth/2,
|
||||
height/2 - panelHeight/2 - panelHeight%2 - 1,
|
||||
width/2 + panelWidth/2,
|
||||
height/2 + panelHeight/2
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) getPopupPanelWidth() int {
|
||||
width, _ := self.c.GocuiGui().Size()
|
||||
// we want a minimum width up to a point, then we do it based on ratio.
|
||||
panelWidth := 4 * width / 7
|
||||
minWidth := 80
|
||||
if panelWidth < minWidth {
|
||||
if width-2 < minWidth {
|
||||
panelWidth = width - 2
|
||||
} else {
|
||||
panelWidth = minWidth
|
||||
}
|
||||
}
|
||||
|
||||
return panelWidth
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) prepareConfirmationPanel(
|
||||
ctx goContext.Context,
|
||||
opts types.ConfirmOpts,
|
||||
) error {
|
||||
self.c.Views().Confirmation.HasLoader = opts.HasLoader
|
||||
if opts.HasLoader {
|
||||
self.c.GocuiGui().StartTicking(ctx)
|
||||
}
|
||||
self.c.Views().Confirmation.Title = opts.Title
|
||||
// for now we do not support wrapping in our editor
|
||||
self.c.Views().Confirmation.Wrap = !opts.Editable
|
||||
self.c.Views().Confirmation.FgColor = theme.GocuiDefaultTextColor
|
||||
self.c.Views().Confirmation.Mask = runeForMask(opts.Mask)
|
||||
_ = self.c.Views().Confirmation.SetOrigin(0, 0)
|
||||
|
||||
suggestionsContext := self.c.Contexts().Suggestions
|
||||
suggestionsContext.State.FindSuggestions = opts.FindSuggestionsFunc
|
||||
if opts.FindSuggestionsFunc != nil {
|
||||
suggestionsView := self.c.Views().Suggestions
|
||||
suggestionsView.Wrap = false
|
||||
suggestionsView.FgColor = theme.GocuiDefaultTextColor
|
||||
suggestionsContext.SetSuggestions(opts.FindSuggestionsFunc(""))
|
||||
suggestionsView.Visible = true
|
||||
suggestionsView.Title = fmt.Sprintf(self.c.Tr.SuggestionsTitle, self.c.UserConfig.Keybinding.Universal.TogglePanel)
|
||||
}
|
||||
|
||||
self.ResizeConfirmationPanel()
|
||||
return nil
|
||||
}
|
||||
|
||||
func runeForMask(mask bool) rune {
|
||||
if mask {
|
||||
return '*'
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts types.CreatePopupPanelOpts) error {
|
||||
self.c.Mutexes().PopupMutex.Lock()
|
||||
defer self.c.Mutexes().PopupMutex.Unlock()
|
||||
|
||||
ctx, cancel := goContext.WithCancel(ctx)
|
||||
|
||||
// we don't allow interruptions of non-loader popups in case we get stuck somehow
|
||||
// e.g. a credentials popup never gets its required user input so a process hangs
|
||||
// forever.
|
||||
// The proper solution is to have a queue of popup options
|
||||
currentPopupOpts := self.c.State().GetRepoState().GetCurrentPopupOpts()
|
||||
if currentPopupOpts != nil && !currentPopupOpts.HasLoader {
|
||||
self.c.Log.Error("ignoring create popup panel because a popup panel is already open")
|
||||
cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
// remove any previous keybindings
|
||||
self.clearConfirmationViewKeyBindings()
|
||||
|
||||
err := self.prepareConfirmationPanel(
|
||||
ctx,
|
||||
types.ConfirmOpts{
|
||||
Title: opts.Title,
|
||||
Prompt: opts.Prompt,
|
||||
HasLoader: opts.HasLoader,
|
||||
FindSuggestionsFunc: opts.FindSuggestionsFunc,
|
||||
Editable: opts.Editable,
|
||||
Mask: opts.Mask,
|
||||
})
|
||||
if err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
confirmationView := self.c.Views().Confirmation
|
||||
confirmationView.Editable = opts.Editable
|
||||
|
||||
if opts.Editable {
|
||||
textArea := confirmationView.TextArea
|
||||
textArea.Clear()
|
||||
textArea.TypeString(opts.Prompt)
|
||||
self.ResizeConfirmationPanel()
|
||||
confirmationView.RenderTextArea()
|
||||
} else {
|
||||
self.c.ResetViewOrigin(confirmationView)
|
||||
self.c.SetViewContent(confirmationView, style.AttrBold.Sprint(opts.Prompt))
|
||||
}
|
||||
|
||||
if err := self.setKeyBindings(cancel, opts); err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.State().GetRepoState().SetCurrentPopupOpts(&opts)
|
||||
|
||||
return self.c.PushContext(self.c.Contexts().Confirmation)
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) setKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) error {
|
||||
var onConfirm func() error
|
||||
if opts.HandleConfirmPrompt != nil {
|
||||
onConfirm = self.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt, func() string { return self.c.Views().Confirmation.TextArea.GetContent() })
|
||||
} else {
|
||||
onConfirm = self.wrappedConfirmationFunction(cancel, opts.HandleConfirm)
|
||||
}
|
||||
|
||||
onSuggestionConfirm := self.wrappedPromptConfirmationFunction(
|
||||
cancel,
|
||||
opts.HandleConfirmPrompt,
|
||||
self.getSelectedSuggestionValue,
|
||||
)
|
||||
|
||||
onClose := self.wrappedConfirmationFunction(cancel, opts.HandleClose)
|
||||
|
||||
self.c.Contexts().Confirmation.State.OnConfirm = onConfirm
|
||||
self.c.Contexts().Confirmation.State.OnClose = onClose
|
||||
self.c.Contexts().Suggestions.State.OnConfirm = onSuggestionConfirm
|
||||
self.c.Contexts().Suggestions.State.OnClose = onClose
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) clearConfirmationViewKeyBindings() {
|
||||
noop := func() error { return nil }
|
||||
self.c.Contexts().Confirmation.State.OnConfirm = noop
|
||||
self.c.Contexts().Confirmation.State.OnClose = noop
|
||||
self.c.Contexts().Suggestions.State.OnConfirm = noop
|
||||
self.c.Contexts().Suggestions.State.OnClose = noop
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) getSelectedSuggestionValue() string {
|
||||
selectedSuggestion := self.c.Contexts().Suggestions.GetSelected()
|
||||
|
||||
if selectedSuggestion != nil {
|
||||
return selectedSuggestion.Value
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) ResizeConfirmationPanel() {
|
||||
suggestionsViewHeight := 0
|
||||
if self.c.Views().Suggestions.Visible {
|
||||
suggestionsViewHeight = 11
|
||||
}
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
prompt := self.c.Views().Confirmation.Buffer()
|
||||
wrap := true
|
||||
if self.c.Views().Confirmation.Editable {
|
||||
prompt = self.c.Views().Confirmation.TextArea.GetContent()
|
||||
wrap = false
|
||||
}
|
||||
panelHeight := getMessageHeight(wrap, prompt, panelWidth) + suggestionsViewHeight
|
||||
x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight)
|
||||
confirmationViewBottom := y1 - suggestionsViewHeight
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Confirmation.Name(), x0, y0, x1, confirmationViewBottom, 0)
|
||||
|
||||
suggestionsViewTop := confirmationViewBottom + 1
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Suggestions.Name(), x0, suggestionsViewTop, x1, suggestionsViewTop+suggestionsViewHeight, 0)
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) ResizeCurrentPopupPanel() error {
|
||||
c := self.c.CurrentContext()
|
||||
|
||||
switch c {
|
||||
case self.c.Contexts().Menu:
|
||||
self.resizeMenu()
|
||||
case self.c.Contexts().Confirmation, self.c.Contexts().Suggestions:
|
||||
self.resizeConfirmationPanel()
|
||||
case self.c.Contexts().CommitMessage, self.c.Contexts().CommitDescription:
|
||||
self.ResizeCommitMessagePanels()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) ResizePopupPanel(v *gocui.View, content string) error {
|
||||
x0, y0, x1, y1 := self.getPopupPanelDimensions(v.Wrap, content)
|
||||
_, err := self.c.GocuiGui().SetView(v.Name(), x0, y0, x1, y1, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) resizeMenu() {
|
||||
itemCount := self.c.Contexts().Menu.GetList().Len()
|
||||
offset := 3
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
x0, y0, x1, y1 := self.getPopupPanelDimensionsForContentHeight(panelWidth, itemCount+offset)
|
||||
menuBottom := y1 - offset
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Menu.Name(), x0, y0, x1, menuBottom, 0)
|
||||
|
||||
tooltipTop := menuBottom + 1
|
||||
tooltipHeight := getMessageHeight(true, self.c.Contexts().Menu.GetSelected().Tooltip, panelWidth) + 2 // plus 2 for the frame
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Tooltip.Name(), x0, tooltipTop, x1, tooltipTop+tooltipHeight-1, 0)
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) resizeConfirmationPanel() {
|
||||
suggestionsViewHeight := 0
|
||||
if self.c.Views().Suggestions.Visible {
|
||||
suggestionsViewHeight = 11
|
||||
}
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
prompt := self.c.Views().Confirmation.Buffer()
|
||||
wrap := true
|
||||
if self.c.Views().Confirmation.Editable {
|
||||
prompt = self.c.Views().Confirmation.TextArea.GetContent()
|
||||
wrap = false
|
||||
}
|
||||
panelHeight := getMessageHeight(wrap, prompt, panelWidth) + suggestionsViewHeight
|
||||
x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight)
|
||||
confirmationViewBottom := y1 - suggestionsViewHeight
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Confirmation.Name(), x0, y0, x1, confirmationViewBottom, 0)
|
||||
|
||||
suggestionsViewTop := confirmationViewBottom + 1
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Suggestions.Name(), x0, suggestionsViewTop, x1, suggestionsViewTop+suggestionsViewHeight, 0)
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) ResizeCommitMessagePanels() {
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
content := self.c.Views().CommitDescription.TextArea.GetContent()
|
||||
summaryViewHeight := 3
|
||||
panelHeight := getMessageHeight(false, content, panelWidth)
|
||||
minHeight := 7
|
||||
if panelHeight < minHeight {
|
||||
panelHeight = minHeight
|
||||
}
|
||||
x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight)
|
||||
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().CommitMessage.Name(), x0, y0, x1, y0+summaryViewHeight-1, 0)
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().CommitDescription.Name(), x0, y0+summaryViewHeight, x1, y1+summaryViewHeight, 0)
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) IsPopupPanel(viewName string) bool {
|
||||
return viewName == "commitMessage" || viewName == "confirmation" || viewName == "menu"
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) IsPopupPanelFocused() bool {
|
||||
return self.IsPopupPanel(self.c.CurrentContext().GetViewName())
|
||||
}
|
@ -8,11 +8,11 @@ import (
|
||||
)
|
||||
|
||||
type CredentialsHelper struct {
|
||||
c *types.HelperCommon
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewCredentialsHelper(
|
||||
c *types.HelperCommon,
|
||||
c *HelperCommon,
|
||||
) *CredentialsHelper {
|
||||
return &CredentialsHelper{
|
||||
c: c,
|
||||
|
114
pkg/gui/controllers/helpers/diff_helper.go
Normal file
114
pkg/gui/controllers/helpers/diff_helper.go
Normal file
@ -0,0 +1,114 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type DiffHelper struct {
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewDiffHelper(c *HelperCommon) *DiffHelper {
|
||||
return &DiffHelper{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *DiffHelper) DiffStr() string {
|
||||
output := self.c.Modes().Diffing.Ref
|
||||
|
||||
right := self.currentDiffTerminal()
|
||||
if right != "" {
|
||||
output += " " + right
|
||||
}
|
||||
|
||||
if self.c.Modes().Diffing.Reverse {
|
||||
output += " -R"
|
||||
}
|
||||
|
||||
if self.c.State().GetIgnoreWhitespaceInDiffView() {
|
||||
output += " --ignore-all-space"
|
||||
}
|
||||
|
||||
file := self.currentlySelectedFilename()
|
||||
if file != "" {
|
||||
output += " -- " + file
|
||||
} else if self.c.Modes().Filtering.Active() {
|
||||
output += " -- " + self.c.Modes().Filtering.GetPath()
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func (self *DiffHelper) ExitDiffMode() error {
|
||||
self.c.Modes().Diffing = diffing.New()
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
}
|
||||
|
||||
func (self *DiffHelper) RenderDiff() error {
|
||||
cmdObj := self.c.OS().Cmd.New(
|
||||
fmt.Sprintf("git diff --submodule --no-ext-diff --color %s", self.DiffStr()),
|
||||
)
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Diff",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CurrentDiffTerminals returns the current diff terminals of the currently selected item.
|
||||
// in the case of a branch it returns both the branch and it's upstream name,
|
||||
// which becomes an option when you bring up the diff menu, but when you're just
|
||||
// flicking through branches it will be using the local branch name.
|
||||
func (self *DiffHelper) CurrentDiffTerminals() []string {
|
||||
c := self.c.CurrentSideContext()
|
||||
|
||||
if c.GetKey() == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v := c.(type) {
|
||||
case types.DiffableContext:
|
||||
return v.GetDiffTerminals()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *DiffHelper) currentDiffTerminal() string {
|
||||
names := self.CurrentDiffTerminals()
|
||||
if len(names) == 0 {
|
||||
return ""
|
||||
}
|
||||
return names[0]
|
||||
}
|
||||
|
||||
func (self *DiffHelper) currentlySelectedFilename() string {
|
||||
currentContext := self.c.CurrentContext()
|
||||
|
||||
switch currentContext := currentContext.(type) {
|
||||
case types.IListContext:
|
||||
if lo.Contains([]types.ContextKey{context.FILES_CONTEXT_KEY, context.COMMIT_FILES_CONTEXT_KEY}, currentContext.GetKey()) {
|
||||
return currentContext.GetSelectedItemId()
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *DiffHelper) WithDiffModeCheck(f func() error) error {
|
||||
if self.c.Modes().Diffing.Active() {
|
||||
return self.RenderDiff()
|
||||
}
|
||||
|
||||
return f()
|
||||
}
|
@ -1,11 +1,5 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type IFilesHelper interface {
|
||||
EditFile(filename string) error
|
||||
EditFileAtLine(filename string, lineNumber int) error
|
||||
@ -13,37 +7,29 @@ type IFilesHelper interface {
|
||||
}
|
||||
|
||||
type FilesHelper struct {
|
||||
c *types.HelperCommon
|
||||
git *commands.GitCommand
|
||||
os *oscommands.OSCommand
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewFilesHelper(
|
||||
c *types.HelperCommon,
|
||||
git *commands.GitCommand,
|
||||
os *oscommands.OSCommand,
|
||||
) *FilesHelper {
|
||||
func NewFilesHelper(c *HelperCommon) *FilesHelper {
|
||||
return &FilesHelper{
|
||||
c: c,
|
||||
git: git,
|
||||
os: os,
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
var _ IFilesHelper = &FilesHelper{}
|
||||
|
||||
func (self *FilesHelper) EditFile(filename string) error {
|
||||
cmdStr, editInTerminal := self.git.File.GetEditCmdStr(filename)
|
||||
cmdStr, editInTerminal := self.c.Git().File.GetEditCmdStr(filename)
|
||||
return self.callEditor(cmdStr, editInTerminal)
|
||||
}
|
||||
|
||||
func (self *FilesHelper) EditFileAtLine(filename string, lineNumber int) error {
|
||||
cmdStr, editInTerminal := self.git.File.GetEditAtLineCmdStr(filename, lineNumber)
|
||||
cmdStr, editInTerminal := self.c.Git().File.GetEditAtLineCmdStr(filename, lineNumber)
|
||||
return self.callEditor(cmdStr, editInTerminal)
|
||||
}
|
||||
|
||||
func (self *FilesHelper) EditFileAtLineAndWait(filename string, lineNumber int) error {
|
||||
cmdStr := self.git.File.GetEditAtLineAndWaitCmdStr(filename, lineNumber)
|
||||
cmdStr := self.c.Git().File.GetEditAtLineAndWaitCmdStr(filename, lineNumber)
|
||||
|
||||
// Always suspend, regardless of the value of the editInTerminal config,
|
||||
// since we want to prevent interacting with the UI until the editor
|
||||
@ -54,16 +40,16 @@ func (self *FilesHelper) EditFileAtLineAndWait(filename string, lineNumber int)
|
||||
func (self *FilesHelper) callEditor(cmdStr string, editInTerminal bool) error {
|
||||
if editInTerminal {
|
||||
return self.c.RunSubprocessAndRefresh(
|
||||
self.os.Cmd.NewShell(cmdStr),
|
||||
self.c.OS().Cmd.NewShell(cmdStr),
|
||||
)
|
||||
}
|
||||
|
||||
return self.os.Cmd.NewShell(cmdStr).Run()
|
||||
return self.c.OS().Cmd.NewShell(cmdStr).Run()
|
||||
}
|
||||
|
||||
func (self *FilesHelper) OpenFile(filename string) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.OpenFile)
|
||||
if err := self.os.OpenFile(filename); err != nil {
|
||||
if err := self.c.OS().OpenFile(filename); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return nil
|
||||
|
@ -3,26 +3,17 @@ package helpers
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type GpgHelper struct {
|
||||
c *types.HelperCommon
|
||||
os *oscommands.OSCommand
|
||||
git *commands.GitCommand
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewGpgHelper(
|
||||
c *types.HelperCommon,
|
||||
os *oscommands.OSCommand,
|
||||
git *commands.GitCommand,
|
||||
) *GpgHelper {
|
||||
func NewGpgHelper(c *HelperCommon) *GpgHelper {
|
||||
return &GpgHelper{
|
||||
c: c,
|
||||
os: os,
|
||||
git: git,
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,9 +23,9 @@ func NewGpgHelper(
|
||||
// we don't need to see a loading status if we're in a subprocess.
|
||||
// TODO: we shouldn't need to use a shell here, but looks like that NewShell function contains some windows specific quoting stuff. We should centralise that.
|
||||
func (self *GpgHelper) WithGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
|
||||
useSubprocess := self.git.Config.UsingGpg()
|
||||
useSubprocess := self.c.Git().Config.UsingGpg()
|
||||
if useSubprocess {
|
||||
success, err := self.c.RunSubprocess(self.os.Cmd.NewShell(cmdObj.ToString()))
|
||||
success, err := self.c.RunSubprocess(self.c.OS().Cmd.NewShell(cmdObj.ToString()))
|
||||
if success && onSuccess != nil {
|
||||
if err := onSuccess(); err != nil {
|
||||
return err
|
||||
@ -51,7 +42,7 @@ func (self *GpgHelper) WithGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus
|
||||
}
|
||||
|
||||
func (self *GpgHelper) runAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
|
||||
cmdObj = self.os.Cmd.NewShell(cmdObj.ToString())
|
||||
cmdObj = self.c.OS().Cmd.NewShell(cmdObj.ToString())
|
||||
|
||||
return self.c.WithWaitingStatus(waitingStatus, func() error {
|
||||
if err := cmdObj.StreamOutput().Run(); err != nil {
|
||||
|
@ -1,5 +1,21 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type HelperCommon struct {
|
||||
*common.Common
|
||||
types.IGuiCommon
|
||||
IGetContexts
|
||||
}
|
||||
|
||||
type IGetContexts interface {
|
||||
Contexts() *context.ContextTree
|
||||
}
|
||||
|
||||
type Helpers struct {
|
||||
Refs *RefsHelper
|
||||
Bisect *BisectHelper
|
||||
@ -12,28 +28,55 @@ type Helpers struct {
|
||||
CherryPick *CherryPickHelper
|
||||
Host *HostHelper
|
||||
PatchBuilding *PatchBuildingHelper
|
||||
Staging *StagingHelper
|
||||
GPG *GpgHelper
|
||||
Upstream *UpstreamHelper
|
||||
AmendHelper *AmendHelper
|
||||
Commits *CommitsHelper
|
||||
Snake *SnakeHelper
|
||||
// lives in context package because our contexts need it to render to main
|
||||
Diff *DiffHelper
|
||||
Repos *ReposHelper
|
||||
RecordDirectory *RecordDirectoryHelper
|
||||
Update *UpdateHelper
|
||||
Window *WindowHelper
|
||||
View *ViewHelper
|
||||
Refresh *RefreshHelper
|
||||
Confirmation *ConfirmationHelper
|
||||
Mode *ModeHelper
|
||||
AppStatus *AppStatusHelper
|
||||
WindowArrangement *WindowArrangementHelper
|
||||
}
|
||||
|
||||
func NewStubHelpers() *Helpers {
|
||||
return &Helpers{
|
||||
Refs: &RefsHelper{},
|
||||
Bisect: &BisectHelper{},
|
||||
Suggestions: &SuggestionsHelper{},
|
||||
Files: &FilesHelper{},
|
||||
WorkingTree: &WorkingTreeHelper{},
|
||||
Tags: &TagsHelper{},
|
||||
MergeAndRebase: &MergeAndRebaseHelper{},
|
||||
MergeConflicts: &MergeConflictsHelper{},
|
||||
CherryPick: &CherryPickHelper{},
|
||||
Host: &HostHelper{},
|
||||
PatchBuilding: &PatchBuildingHelper{},
|
||||
GPG: &GpgHelper{},
|
||||
Upstream: &UpstreamHelper{},
|
||||
AmendHelper: &AmendHelper{},
|
||||
Commits: &CommitsHelper{},
|
||||
Refs: &RefsHelper{},
|
||||
Bisect: &BisectHelper{},
|
||||
Suggestions: &SuggestionsHelper{},
|
||||
Files: &FilesHelper{},
|
||||
WorkingTree: &WorkingTreeHelper{},
|
||||
Tags: &TagsHelper{},
|
||||
MergeAndRebase: &MergeAndRebaseHelper{},
|
||||
MergeConflicts: &MergeConflictsHelper{},
|
||||
CherryPick: &CherryPickHelper{},
|
||||
Host: &HostHelper{},
|
||||
PatchBuilding: &PatchBuildingHelper{},
|
||||
Staging: &StagingHelper{},
|
||||
GPG: &GpgHelper{},
|
||||
Upstream: &UpstreamHelper{},
|
||||
AmendHelper: &AmendHelper{},
|
||||
Commits: &CommitsHelper{},
|
||||
Snake: &SnakeHelper{},
|
||||
Diff: &DiffHelper{},
|
||||
Repos: &ReposHelper{},
|
||||
RecordDirectory: &RecordDirectoryHelper{},
|
||||
Update: &UpdateHelper{},
|
||||
Window: &WindowHelper{},
|
||||
View: &ViewHelper{},
|
||||
Refresh: &RefreshHelper{},
|
||||
Confirmation: &ConfirmationHelper{},
|
||||
Mode: &ModeHelper{},
|
||||
AppStatus: &AppStatusHelper{},
|
||||
WindowArrangement: &WindowArrangementHelper{},
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/hosting_service"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
// this helper just wraps our hosting_service package
|
||||
@ -14,17 +12,14 @@ type IHostHelper interface {
|
||||
}
|
||||
|
||||
type HostHelper struct {
|
||||
c *types.HelperCommon
|
||||
git *commands.GitCommand
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewHostHelper(
|
||||
c *types.HelperCommon,
|
||||
git *commands.GitCommand,
|
||||
c *HelperCommon,
|
||||
) *HostHelper {
|
||||
return &HostHelper{
|
||||
c: c,
|
||||
git: git,
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,7 +35,7 @@ func (self *HostHelper) GetCommitURL(commitSha string) (string, error) {
|
||||
// from one invocation to the next. Note however that we're currently caching config
|
||||
// results so we might want to invalidate the cache here if it becomes a problem.
|
||||
func (self *HostHelper) getHostingServiceMgr() *hosting_service.HostingServiceMgr {
|
||||
remoteUrl := self.git.Config.GetRemoteURL()
|
||||
remoteUrl := self.c.Git().Config.GetRemoteURL()
|
||||
configServices := self.c.UserConfig.Services
|
||||
return hosting_service.NewHostingServiceMgr(self.c.Log, self.c.Tr, remoteUrl, configServices)
|
||||
}
|
||||
|
@ -5,31 +5,23 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"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/utils"
|
||||
)
|
||||
|
||||
type MergeAndRebaseHelper struct {
|
||||
c *types.HelperCommon
|
||||
contexts *context.ContextTree
|
||||
git *commands.GitCommand
|
||||
c *HelperCommon
|
||||
refsHelper *RefsHelper
|
||||
}
|
||||
|
||||
func NewMergeAndRebaseHelper(
|
||||
c *types.HelperCommon,
|
||||
contexts *context.ContextTree,
|
||||
git *commands.GitCommand,
|
||||
c *HelperCommon,
|
||||
refsHelper *RefsHelper,
|
||||
) *MergeAndRebaseHelper {
|
||||
return &MergeAndRebaseHelper{
|
||||
c: c,
|
||||
contexts: contexts,
|
||||
git: git,
|
||||
refsHelper: refsHelper,
|
||||
}
|
||||
}
|
||||
@ -53,7 +45,7 @@ func (self *MergeAndRebaseHelper) CreateRebaseOptionsMenu() error {
|
||||
{option: REBASE_OPTION_ABORT, key: 'a'},
|
||||
}
|
||||
|
||||
if self.git.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
|
||||
if self.c.Git().Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
|
||||
options = append(options, optionAndKey{
|
||||
option: REBASE_OPTION_SKIP, key: 's',
|
||||
})
|
||||
@ -70,7 +62,7 @@ func (self *MergeAndRebaseHelper) CreateRebaseOptionsMenu() error {
|
||||
})
|
||||
|
||||
var title string
|
||||
if self.git.Status.WorkingTreeState() == enums.REBASE_MODE_MERGING {
|
||||
if self.c.Git().Status.WorkingTreeState() == enums.REBASE_MODE_MERGING {
|
||||
title = self.c.Tr.MergeOptionsTitle
|
||||
} else {
|
||||
title = self.c.Tr.RebaseOptionsTitle
|
||||
@ -80,7 +72,7 @@ func (self *MergeAndRebaseHelper) CreateRebaseOptionsMenu() error {
|
||||
}
|
||||
|
||||
func (self *MergeAndRebaseHelper) genericMergeCommand(command string) error {
|
||||
status := self.git.Status.WorkingTreeState()
|
||||
status := self.c.Git().Status.WorkingTreeState()
|
||||
|
||||
if status != enums.REBASE_MODE_MERGING && status != enums.REBASE_MODE_REBASING {
|
||||
return self.c.ErrorMsg(self.c.Tr.NotMergingOrRebasing)
|
||||
@ -104,10 +96,10 @@ func (self *MergeAndRebaseHelper) genericMergeCommand(command string) error {
|
||||
if status == enums.REBASE_MODE_MERGING && command != REBASE_OPTION_ABORT && self.c.UserConfig.Git.Merging.ManualCommit {
|
||||
// TODO: see if we should be calling more of the code from self.Git.Rebase.GenericMergeOrRebaseAction
|
||||
return self.c.RunSubprocessAndRefresh(
|
||||
self.git.Rebase.GenericMergeOrRebaseActionCmdObj(commandType, command),
|
||||
self.c.Git().Rebase.GenericMergeOrRebaseActionCmdObj(commandType, command),
|
||||
)
|
||||
}
|
||||
result := self.git.Rebase.GenericMergeOrRebaseAction(commandType, command)
|
||||
result := self.c.Git().Rebase.GenericMergeOrRebaseAction(commandType, command)
|
||||
if err := self.CheckMergeOrRebase(result); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -150,7 +142,7 @@ func (self *MergeAndRebaseHelper) CheckMergeOrRebase(result error) error {
|
||||
Title: self.c.Tr.FoundConflictsTitle,
|
||||
Prompt: self.c.Tr.FoundConflicts,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.PushContext(self.contexts.Files)
|
||||
return self.c.PushContext(self.c.Contexts().Files)
|
||||
},
|
||||
HandleClose: func() error {
|
||||
return self.genericMergeCommand(REBASE_OPTION_ABORT)
|
||||
@ -174,7 +166,7 @@ func (self *MergeAndRebaseHelper) AbortMergeOrRebaseWithConfirm() error {
|
||||
}
|
||||
|
||||
func (self *MergeAndRebaseHelper) workingTreeStateNoun() string {
|
||||
workingTreeState := self.git.Status.WorkingTreeState()
|
||||
workingTreeState := self.c.Git().Status.WorkingTreeState()
|
||||
switch workingTreeState {
|
||||
case enums.REBASE_MODE_NONE:
|
||||
return ""
|
||||
@ -207,7 +199,7 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
|
||||
Key: 's',
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.RebaseBranch)
|
||||
err := self.git.Rebase.RebaseBranch(ref)
|
||||
err := self.c.Git().Rebase.RebaseBranch(ref)
|
||||
return self.CheckMergeOrRebase(err)
|
||||
},
|
||||
},
|
||||
@ -217,11 +209,11 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
|
||||
Tooltip: self.c.Tr.InteractiveRebaseTooltip,
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.RebaseBranch)
|
||||
err := self.git.Rebase.EditRebase(ref)
|
||||
err := self.c.Git().Rebase.EditRebase(ref)
|
||||
if err = self.CheckMergeOrRebase(err); err != nil {
|
||||
return err
|
||||
}
|
||||
return self.c.PushContext(self.contexts.LocalCommits)
|
||||
return self.c.PushContext(self.c.Contexts().LocalCommits)
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -241,7 +233,7 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
|
||||
}
|
||||
|
||||
func (self *MergeAndRebaseHelper) MergeRefIntoCheckedOutBranch(refName string) error {
|
||||
if self.git.Branch.IsHeadDetached() {
|
||||
if self.c.Git().Branch.IsHeadDetached() {
|
||||
return self.c.ErrorMsg("Cannot merge branch in detached head state. You might have checked out a commit directly or a remote branch, in which case you should checkout the local branch you want to be on")
|
||||
}
|
||||
checkedOutBranchName := self.refsHelper.GetCheckedOutRef().Name
|
||||
@ -261,7 +253,7 @@ func (self *MergeAndRebaseHelper) MergeRefIntoCheckedOutBranch(refName string) e
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.Merge)
|
||||
err := self.git.Branch.Merge(refName, git_commands.MergeOpts{})
|
||||
err := self.c.Git().Branch.Merge(refName, git_commands.MergeOpts{})
|
||||
return self.CheckMergeOrRebase(err)
|
||||
},
|
||||
})
|
||||
|
@ -1,41 +1,19 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type MergeConflictsHelper struct {
|
||||
c *types.HelperCommon
|
||||
contexts *context.ContextTree
|
||||
git *commands.GitCommand
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewMergeConflictsHelper(
|
||||
c *types.HelperCommon,
|
||||
contexts *context.ContextTree,
|
||||
git *commands.GitCommand,
|
||||
c *HelperCommon,
|
||||
) *MergeConflictsHelper {
|
||||
return &MergeConflictsHelper{
|
||||
c: c,
|
||||
contexts: contexts,
|
||||
git: git,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) GetMergingOptions() map[string]string {
|
||||
keybindingConfig := self.c.UserConfig.Keybinding
|
||||
|
||||
return map[string]string{
|
||||
fmt.Sprintf("%s %s", keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)): self.c.Tr.LcSelectHunk,
|
||||
fmt.Sprintf("%s %s", keybindings.Label(keybindingConfig.Universal.PrevBlock), keybindings.Label(keybindingConfig.Universal.NextBlock)): self.c.Tr.LcNavigateConflicts,
|
||||
keybindings.Label(keybindingConfig.Universal.Select): self.c.Tr.LcPickHunk,
|
||||
keybindings.Label(keybindingConfig.Main.PickBothHunks): self.c.Tr.LcPickAllHunks,
|
||||
keybindings.Label(keybindingConfig.Universal.Undo): self.c.Tr.LcUndo,
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +25,7 @@ func (self *MergeConflictsHelper) SetMergeState(path string) (bool, error) {
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) setMergeStateWithoutLock(path string) (bool, error) {
|
||||
content, err := self.git.File.Cat(path)
|
||||
content, err := self.c.Git().File.Cat(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -78,7 +56,7 @@ func (self *MergeConflictsHelper) EscapeMerge() error {
|
||||
|
||||
// doing this in separate UI thread so that we're not still holding the lock by the time refresh the file
|
||||
self.c.OnUIThread(func() error {
|
||||
return self.c.PushContext(self.contexts.Files)
|
||||
return self.c.PushContext(self.c.Contexts().Files)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@ -107,9 +85,48 @@ func (self *MergeConflictsHelper) SwitchToMerge(path string) error {
|
||||
}
|
||||
}
|
||||
|
||||
return self.c.PushContext(self.contexts.MergeConflicts)
|
||||
return self.c.PushContext(self.c.Contexts().MergeConflicts)
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) context() *context.MergeConflictsContext {
|
||||
return self.contexts.MergeConflicts
|
||||
return self.c.Contexts().MergeConflicts
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) Render(isFocused bool) error {
|
||||
content := self.context().GetContentToRender(isFocused)
|
||||
|
||||
var task types.UpdateTask
|
||||
if self.context().IsUserScrolling() {
|
||||
task = types.NewRenderStringWithoutScrollTask(content)
|
||||
} else {
|
||||
originY := self.context().GetOriginY()
|
||||
task = types.NewRenderStringWithScrollTask(content, 0, originY)
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().MergeConflicts,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) RefreshMergeState() error {
|
||||
self.c.Contexts().MergeConflicts.GetMutex().Lock()
|
||||
defer self.c.Contexts().MergeConflicts.GetMutex().Unlock()
|
||||
|
||||
if self.c.CurrentContext().GetKey() != context.MERGE_CONFLICTS_CONTEXT_KEY {
|
||||
return nil
|
||||
}
|
||||
|
||||
hasConflicts, err := self.SetConflictsAndRender(self.c.Contexts().MergeConflicts.GetState().GetPath(), true)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if !hasConflicts {
|
||||
return self.EscapeMerge()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
159
pkg/gui/controllers/helpers/mode_helper.go
Normal file
159
pkg/gui/controllers/helpers/mode_helper.go
Normal file
@ -0,0 +1,159 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type ModeHelper struct {
|
||||
c *HelperCommon
|
||||
|
||||
diffHelper *DiffHelper
|
||||
patchBuildingHelper *PatchBuildingHelper
|
||||
cherryPickHelper *CherryPickHelper
|
||||
mergeAndRebaseHelper *MergeAndRebaseHelper
|
||||
bisectHelper *BisectHelper
|
||||
}
|
||||
|
||||
func NewModeHelper(
|
||||
c *HelperCommon,
|
||||
diffHelper *DiffHelper,
|
||||
patchBuildingHelper *PatchBuildingHelper,
|
||||
cherryPickHelper *CherryPickHelper,
|
||||
mergeAndRebaseHelper *MergeAndRebaseHelper,
|
||||
bisectHelper *BisectHelper,
|
||||
) *ModeHelper {
|
||||
return &ModeHelper{
|
||||
c: c,
|
||||
diffHelper: diffHelper,
|
||||
patchBuildingHelper: patchBuildingHelper,
|
||||
cherryPickHelper: cherryPickHelper,
|
||||
mergeAndRebaseHelper: mergeAndRebaseHelper,
|
||||
bisectHelper: bisectHelper,
|
||||
}
|
||||
}
|
||||
|
||||
type ModeStatus struct {
|
||||
IsActive func() bool
|
||||
Description func() string
|
||||
Reset func() error
|
||||
}
|
||||
|
||||
func (self *ModeHelper) Statuses() []ModeStatus {
|
||||
return []ModeStatus{
|
||||
{
|
||||
IsActive: self.c.Modes().Diffing.Active,
|
||||
Description: func() string {
|
||||
return self.withResetButton(
|
||||
fmt.Sprintf(
|
||||
"%s %s",
|
||||
self.c.Tr.LcShowingGitDiff,
|
||||
"git diff "+self.diffHelper.DiffStr(),
|
||||
),
|
||||
style.FgMagenta,
|
||||
)
|
||||
},
|
||||
Reset: self.diffHelper.ExitDiffMode,
|
||||
},
|
||||
{
|
||||
IsActive: self.c.Git().Patch.PatchBuilder.Active,
|
||||
Description: func() string {
|
||||
return self.withResetButton(self.c.Tr.LcBuildingPatch, style.FgYellow.SetBold())
|
||||
},
|
||||
Reset: self.patchBuildingHelper.Reset,
|
||||
},
|
||||
{
|
||||
IsActive: self.c.Modes().Filtering.Active,
|
||||
Description: func() string {
|
||||
return self.withResetButton(
|
||||
fmt.Sprintf(
|
||||
"%s '%s'",
|
||||
self.c.Tr.LcFilteringBy,
|
||||
self.c.Modes().Filtering.GetPath(),
|
||||
),
|
||||
style.FgRed,
|
||||
)
|
||||
},
|
||||
Reset: self.ExitFilterMode,
|
||||
},
|
||||
{
|
||||
IsActive: self.c.Modes().CherryPicking.Active,
|
||||
Description: func() string {
|
||||
copiedCount := len(self.c.Modes().CherryPicking.CherryPickedCommits)
|
||||
text := self.c.Tr.LcCommitsCopied
|
||||
if copiedCount == 1 {
|
||||
text = self.c.Tr.LcCommitCopied
|
||||
}
|
||||
|
||||
return self.withResetButton(
|
||||
fmt.Sprintf(
|
||||
"%d %s",
|
||||
copiedCount,
|
||||
text,
|
||||
),
|
||||
style.FgCyan,
|
||||
)
|
||||
},
|
||||
Reset: self.cherryPickHelper.Reset,
|
||||
},
|
||||
{
|
||||
IsActive: func() bool {
|
||||
return self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE
|
||||
},
|
||||
Description: func() string {
|
||||
workingTreeState := self.c.Git().Status.WorkingTreeState()
|
||||
return self.withResetButton(
|
||||
presentation.FormatWorkingTreeState(workingTreeState), style.FgYellow,
|
||||
)
|
||||
},
|
||||
Reset: self.mergeAndRebaseHelper.AbortMergeOrRebaseWithConfirm,
|
||||
},
|
||||
{
|
||||
IsActive: func() bool {
|
||||
return self.c.Model().BisectInfo.Started()
|
||||
},
|
||||
Description: func() string {
|
||||
return self.withResetButton("bisecting", style.FgGreen)
|
||||
},
|
||||
Reset: self.bisectHelper.Reset,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ModeHelper) withResetButton(content string, textStyle style.TextStyle) string {
|
||||
return textStyle.Sprintf(
|
||||
"%s %s",
|
||||
content,
|
||||
style.AttrUnderline.Sprint(self.c.Tr.ResetInParentheses),
|
||||
)
|
||||
}
|
||||
|
||||
func (self *ModeHelper) GetActiveMode() (ModeStatus, bool) {
|
||||
return slices.Find(self.Statuses(), func(mode ModeStatus) bool {
|
||||
return mode.IsActive()
|
||||
})
|
||||
}
|
||||
|
||||
func (self *ModeHelper) IsAnyModeActive() bool {
|
||||
return slices.Some(self.Statuses(), func(mode ModeStatus) bool {
|
||||
return mode.IsActive()
|
||||
})
|
||||
}
|
||||
|
||||
func (self *ModeHelper) ExitFilterMode() error {
|
||||
return self.ClearFiltering()
|
||||
}
|
||||
|
||||
func (self *ModeHelper) ClearFiltering() error {
|
||||
self.c.Modes().Filtering.Reset()
|
||||
if self.c.State().GetRepoState().GetScreenMode() == types.SCREEN_HALF {
|
||||
self.c.State().GetRepoState().SetScreenMode(types.SCREEN_NORMAL)
|
||||
}
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}})
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -12,25 +11,19 @@ type IPatchBuildingHelper interface {
|
||||
}
|
||||
|
||||
type PatchBuildingHelper struct {
|
||||
c *types.HelperCommon
|
||||
git *commands.GitCommand
|
||||
contexts *context.ContextTree
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewPatchBuildingHelper(
|
||||
c *types.HelperCommon,
|
||||
git *commands.GitCommand,
|
||||
contexts *context.ContextTree,
|
||||
c *HelperCommon,
|
||||
) *PatchBuildingHelper {
|
||||
return &PatchBuildingHelper{
|
||||
c: c,
|
||||
git: git,
|
||||
contexts: contexts,
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *PatchBuildingHelper) ValidateNormalWorkingTreeState() (bool, error) {
|
||||
if self.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
return false, self.c.ErrorMsg(self.c.Tr.CantPatchWhileRebasingError)
|
||||
}
|
||||
return true, nil
|
||||
@ -43,7 +36,7 @@ func (self *PatchBuildingHelper) Escape() error {
|
||||
|
||||
// kills the custom patch and returns us back to the commit files panel if needed
|
||||
func (self *PatchBuildingHelper) Reset() error {
|
||||
self.git.Patch.PatchBuilder.Reset()
|
||||
self.c.Git().Patch.PatchBuilder.Reset()
|
||||
|
||||
if self.c.CurrentStaticContext().GetKind() != types.SIDE_CONTEXT {
|
||||
if err := self.Escape(); err != nil {
|
||||
@ -60,3 +53,59 @@ func (self *PatchBuildingHelper) Reset() error {
|
||||
// refreshing the current context so that the secondary panel is hidden if necessary.
|
||||
return self.c.PostRefreshUpdate(self.c.CurrentContext())
|
||||
}
|
||||
|
||||
func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpts) error {
|
||||
selectedLineIdx := -1
|
||||
if opts.ClickedWindowName == "main" {
|
||||
selectedLineIdx = opts.ClickedViewLineIdx
|
||||
}
|
||||
|
||||
if !self.c.Git().Patch.PatchBuilder.Active() {
|
||||
return self.Escape()
|
||||
}
|
||||
|
||||
// get diff from commit file that's currently selected
|
||||
path := self.c.Contexts().CommitFiles.GetSelectedPath()
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ref := self.c.Contexts().CommitFiles.CommitFileTreeViewModel.GetRef()
|
||||
to := ref.RefName()
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
diff, err := self.c.Git().WorkingTree.ShowFileDiff(from, to, reverse, path, true, self.c.State().GetIgnoreWhitespaceInDiffView())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secondaryDiff := self.c.Git().Patch.PatchBuilder.RenderPatchForFile(path, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
context := self.c.Contexts().CustomPatchBuilder
|
||||
|
||||
oldState := context.GetState()
|
||||
|
||||
state := patch_exploring.NewState(diff, selectedLineIdx, oldState, self.c.Log)
|
||||
context.SetState(state)
|
||||
if state == nil {
|
||||
return self.Escape()
|
||||
}
|
||||
|
||||
mainContent := context.GetContentToRender(true)
|
||||
|
||||
self.c.Contexts().CustomPatchBuilder.FocusSelection()
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().PatchBuilding,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(mainContent),
|
||||
Title: self.c.Tr.Patch,
|
||||
},
|
||||
Secondary: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(secondaryDiff),
|
||||
Title: self.c.Tr.CustomPatch,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
36
pkg/gui/controllers/helpers/record_directory_helper.go
Normal file
36
pkg/gui/controllers/helpers/record_directory_helper.go
Normal file
@ -0,0 +1,36 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
type RecordDirectoryHelper struct {
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewRecordDirectoryHelper(c *HelperCommon) *RecordDirectoryHelper {
|
||||
return &RecordDirectoryHelper{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
// when a user runs lazygit with the LAZYGIT_NEW_DIR_FILE env variable defined
|
||||
// we will write the current directory to that file on exit so that their
|
||||
// shell can then change to that directory. That means you don't get kicked
|
||||
// back to the directory that you started with.
|
||||
func (self *RecordDirectoryHelper) RecordCurrentDirectory() error {
|
||||
// determine current directory, set it in LAZYGIT_NEW_DIR_FILE
|
||||
dirName, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return self.RecordDirectory(dirName)
|
||||
}
|
||||
|
||||
func (self *RecordDirectoryHelper) RecordDirectory(dirName string) error {
|
||||
newDirFilePath := os.Getenv("LAZYGIT_NEW_DIR_FILE")
|
||||
if newDirFilePath == "" {
|
||||
return nil
|
||||
}
|
||||
return self.c.OS().CreateFileWithContent(newDirFilePath, dirName)
|
||||
}
|
611
pkg/gui/controllers/helpers/refresh_helper.go
Normal file
611
pkg/gui/controllers/helpers/refresh_helper.go
Normal file
@ -0,0 +1,611 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type RefreshHelper struct {
|
||||
c *HelperCommon
|
||||
refsHelper *RefsHelper
|
||||
mergeAndRebaseHelper *MergeAndRebaseHelper
|
||||
patchBuildingHelper *PatchBuildingHelper
|
||||
stagingHelper *StagingHelper
|
||||
mergeConflictsHelper *MergeConflictsHelper
|
||||
fileWatcher types.IFileWatcher
|
||||
}
|
||||
|
||||
func NewRefreshHelper(
|
||||
c *HelperCommon,
|
||||
refsHelper *RefsHelper,
|
||||
mergeAndRebaseHelper *MergeAndRebaseHelper,
|
||||
patchBuildingHelper *PatchBuildingHelper,
|
||||
stagingHelper *StagingHelper,
|
||||
mergeConflictsHelper *MergeConflictsHelper,
|
||||
fileWatcher types.IFileWatcher,
|
||||
) *RefreshHelper {
|
||||
return &RefreshHelper{
|
||||
c: c,
|
||||
refsHelper: refsHelper,
|
||||
mergeAndRebaseHelper: mergeAndRebaseHelper,
|
||||
patchBuildingHelper: patchBuildingHelper,
|
||||
stagingHelper: stagingHelper,
|
||||
mergeConflictsHelper: mergeConflictsHelper,
|
||||
fileWatcher: fileWatcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
||||
if options.Scope == nil {
|
||||
self.c.Log.Infof(
|
||||
"refreshing all scopes in %s mode",
|
||||
getModeName(options.Mode),
|
||||
)
|
||||
} else {
|
||||
self.c.Log.Infof(
|
||||
"refreshing the following scopes in %s mode: %s",
|
||||
getModeName(options.Mode),
|
||||
strings.Join(getScopeNames(options.Scope), ","),
|
||||
)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
f := func() {
|
||||
var scopeSet *set.Set[types.RefreshableView]
|
||||
if len(options.Scope) == 0 {
|
||||
// not refreshing staging/patch-building unless explicitly requested because we only need
|
||||
// to refresh those while focused.
|
||||
scopeSet = set.NewFromSlice([]types.RefreshableView{
|
||||
types.COMMITS,
|
||||
types.BRANCHES,
|
||||
types.FILES,
|
||||
types.STASH,
|
||||
types.REFLOG,
|
||||
types.TAGS,
|
||||
types.REMOTES,
|
||||
types.STATUS,
|
||||
types.BISECT_INFO,
|
||||
types.STAGING,
|
||||
})
|
||||
} else {
|
||||
scopeSet = set.NewFromSlice(options.Scope)
|
||||
}
|
||||
|
||||
refresh := func(f func()) {
|
||||
wg.Add(1)
|
||||
func() {
|
||||
if options.Mode == types.ASYNC {
|
||||
go utils.Safe(f)
|
||||
} else {
|
||||
f()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.COMMITS) || scopeSet.Includes(types.BRANCHES) || scopeSet.Includes(types.REFLOG) || scopeSet.Includes(types.BISECT_INFO) {
|
||||
refresh(self.refreshCommits)
|
||||
} else if scopeSet.Includes(types.REBASE_COMMITS) {
|
||||
// the above block handles rebase commits so we only need to call this one
|
||||
// if we've asked specifically for rebase commits and not those other things
|
||||
refresh(func() { _ = self.refreshRebaseCommits() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.SUB_COMMITS) {
|
||||
refresh(func() { _ = self.refreshSubCommitsWithLimit() })
|
||||
}
|
||||
|
||||
// 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() { _ = self.refreshCommitFilesContext() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.FILES) || scopeSet.Includes(types.SUBMODULES) {
|
||||
refresh(func() { _ = self.refreshFilesAndSubmodules() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.STASH) {
|
||||
refresh(func() { _ = self.refreshStashEntries() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.TAGS) {
|
||||
refresh(func() { _ = self.refreshTags() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.REMOTES) {
|
||||
refresh(func() { _ = self.refreshRemotes() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.STAGING) {
|
||||
refresh(func() { _ = self.stagingHelper.RefreshStagingPanel(types.OnFocusOpts{}) })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.PATCH_BUILDING) {
|
||||
refresh(func() { _ = self.patchBuildingHelper.RefreshPatchBuildingPanel(types.OnFocusOpts{}) })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.MERGE_CONFLICTS) || scopeSet.Includes(types.FILES) {
|
||||
refresh(func() { _ = self.mergeConflictsHelper.RefreshMergeState() })
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
self.refreshStatus()
|
||||
|
||||
if options.Then != nil {
|
||||
options.Then()
|
||||
}
|
||||
}
|
||||
|
||||
if options.Mode == types.BLOCK_UI {
|
||||
self.c.OnUIThread(func() error {
|
||||
f()
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
f()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getScopeNames(scopes []types.RefreshableView) []string {
|
||||
scopeNameMap := map[types.RefreshableView]string{
|
||||
types.COMMITS: "commits",
|
||||
types.BRANCHES: "branches",
|
||||
types.FILES: "files",
|
||||
types.SUBMODULES: "submodules",
|
||||
types.SUB_COMMITS: "subCommits",
|
||||
types.STASH: "stash",
|
||||
types.REFLOG: "reflog",
|
||||
types.TAGS: "tags",
|
||||
types.REMOTES: "remotes",
|
||||
types.STATUS: "status",
|
||||
types.BISECT_INFO: "bisect",
|
||||
types.STAGING: "staging",
|
||||
types.MERGE_CONFLICTS: "mergeConflicts",
|
||||
}
|
||||
|
||||
return slices.Map(scopes, func(scope types.RefreshableView) string {
|
||||
return scopeNameMap[scope]
|
||||
})
|
||||
}
|
||||
|
||||
func getModeName(mode types.RefreshMode) string {
|
||||
switch mode {
|
||||
case types.SYNC:
|
||||
return "sync"
|
||||
case types.ASYNC:
|
||||
return "async"
|
||||
case types.BLOCK_UI:
|
||||
return "block-ui"
|
||||
default:
|
||||
return "unknown mode"
|
||||
}
|
||||
}
|
||||
|
||||
// during startup, the bottleneck is fetching the reflog entries. We need these
|
||||
// on startup to sort the branches by recency. So we have two phases: INITIAL, and COMPLETE.
|
||||
// In the initial phase we don't get any reflog commits, but we asynchronously get them
|
||||
// and refresh the branches after that
|
||||
func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
|
||||
switch self.c.State().GetRepoState().GetStartupStage() {
|
||||
case types.INITIAL:
|
||||
go utils.Safe(func() {
|
||||
_ = self.refreshReflogCommits()
|
||||
self.refreshBranches()
|
||||
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
|
||||
})
|
||||
|
||||
case types.COMPLETE:
|
||||
_ = self.refreshReflogCommits()
|
||||
}
|
||||
}
|
||||
|
||||
// whenever we change commits, we should update branches because the upstream/downstream
|
||||
// counts can change. Whenever we change branches we should probably also change commits
|
||||
// e.g. in the case of switching branches.
|
||||
func (self *RefreshHelper) refreshCommits() {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
|
||||
go utils.Safe(func() {
|
||||
self.refreshReflogCommitsConsideringStartup()
|
||||
|
||||
self.refreshBranches()
|
||||
wg.Done()
|
||||
})
|
||||
|
||||
go utils.Safe(func() {
|
||||
_ = self.refreshCommitsWithLimit()
|
||||
ctx, ok := self.c.Contexts().CommitFiles.GetParentContext()
|
||||
if ok && ctx.GetKey() == context.LOCAL_COMMITS_CONTEXT_KEY {
|
||||
// This makes sense when we've e.g. just amended a commit, meaning we get a new commit SHA at the same position.
|
||||
// However if we've just added a brand new commit, it pushes the list down by one and so we would end up
|
||||
// showing the contents of a different commit than the one we initially entered.
|
||||
// Ideally we would know when to refresh the commit files context and when not to,
|
||||
// or perhaps we could just pop that context off the stack whenever cycling windows.
|
||||
// For now the awkwardness remains.
|
||||
commit := self.c.Contexts().LocalCommits.GetSelected()
|
||||
if commit != nil {
|
||||
self.c.Contexts().CommitFiles.SetRef(commit)
|
||||
self.c.Contexts().CommitFiles.SetTitleRef(commit.RefName())
|
||||
_ = self.refreshCommitFilesContext()
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshCommitsWithLimit() error {
|
||||
self.c.Mutexes().LocalCommitsMutex.Lock()
|
||||
defer self.c.Mutexes().LocalCommitsMutex.Unlock()
|
||||
|
||||
commits, err := self.c.Git().Loaders.CommitLoader.GetCommits(
|
||||
git_commands.GetCommitsOptions{
|
||||
Limit: self.c.Contexts().LocalCommits.GetLimitCommits(),
|
||||
FilterPath: self.c.Modes().Filtering.GetPath(),
|
||||
IncludeRebaseCommits: true,
|
||||
RefName: self.refForLog(),
|
||||
All: self.c.Contexts().LocalCommits.GetShowWholeGitGraph(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Model().Commits = commits
|
||||
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState()
|
||||
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
|
||||
self.c.Mutexes().SubCommitsMutex.Lock()
|
||||
defer self.c.Mutexes().SubCommitsMutex.Unlock()
|
||||
|
||||
commits, err := self.c.Git().Loaders.CommitLoader.GetCommits(
|
||||
git_commands.GetCommitsOptions{
|
||||
Limit: self.c.Contexts().SubCommits.GetLimitCommits(),
|
||||
FilterPath: self.c.Modes().Filtering.GetPath(),
|
||||
IncludeRebaseCommits: false,
|
||||
RefName: self.c.Contexts().SubCommits.GetRef().FullRefName(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Model().SubCommits = commits
|
||||
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshCommitFilesContext() error {
|
||||
ref := self.c.Contexts().CommitFiles.GetRef()
|
||||
to := ref.RefName()
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
|
||||
files, err := self.c.Git().Loaders.CommitFileLoader.GetFilesInDiff(from, to, reverse)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
self.c.Model().CommitFiles = files
|
||||
self.c.Contexts().CommitFiles.CommitFileTreeViewModel.SetTree()
|
||||
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().CommitFiles)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshRebaseCommits() error {
|
||||
self.c.Mutexes().LocalCommitsMutex.Lock()
|
||||
defer self.c.Mutexes().LocalCommitsMutex.Unlock()
|
||||
|
||||
updatedCommits, err := self.c.Git().Loaders.CommitLoader.MergeRebasingCommits(self.c.Model().Commits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Model().Commits = updatedCommits
|
||||
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState()
|
||||
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshTags() error {
|
||||
tags, err := self.c.Git().Loaders.TagLoader.GetTags()
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.Model().Tags = tags
|
||||
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().Tags)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshStateSubmoduleConfigs() error {
|
||||
configs, err := self.c.Git().Submodule.GetConfigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Model().Submodules = configs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// self.refreshStatus is called at the end of this because that's when we can
|
||||
// be sure there is a State.Model.Branches array to pick the current branch from
|
||||
func (self *RefreshHelper) refreshBranches() {
|
||||
reflogCommits := self.c.Model().FilteredReflogCommits
|
||||
if self.c.Modes().Filtering.Active() {
|
||||
// in filter mode we filter our reflog commits to just those containing the path
|
||||
// however we need all the reflog entries to populate the recencies of our branches
|
||||
// which allows us to order them correctly. So if we're filtering we'll just
|
||||
// manually load all the reflog commits here
|
||||
var err error
|
||||
reflogCommits, _, err = self.c.Git().Loaders.ReflogCommitLoader.GetReflogCommits(nil, "")
|
||||
if err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
branches, err := self.c.Git().Loaders.BranchLoader.Load(reflogCommits)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.Model().Branches = branches
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().Branches); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
self.refreshStatus()
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshFilesAndSubmodules() error {
|
||||
self.c.Mutexes().RefreshingFilesMutex.Lock()
|
||||
self.c.State().SetIsRefreshingFiles(true)
|
||||
defer func() {
|
||||
self.c.State().SetIsRefreshingFiles(false)
|
||||
self.c.Mutexes().RefreshingFilesMutex.Unlock()
|
||||
}()
|
||||
|
||||
if err := self.refreshStateSubmoduleConfigs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.refreshStateFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.OnUIThread(func() error {
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().Submodules); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshStateFiles() error {
|
||||
fileTreeViewModel := self.c.Contexts().Files.FileTreeViewModel
|
||||
|
||||
// If git thinks any of our files have inline merge conflicts, but they actually don't,
|
||||
// we stage them.
|
||||
// Note that if files with merge conflicts have both arisen and have been resolved
|
||||
// between refreshes, we won't stage them here. This is super unlikely though,
|
||||
// and this approach spares us from having to call `git status` twice in a row.
|
||||
// Although this also means that at startup we won't be staging anything until
|
||||
// we call git status again.
|
||||
pathsToStage := []string{}
|
||||
prevConflictFileCount := 0
|
||||
for _, file := range self.c.Model().Files {
|
||||
if file.HasMergeConflicts {
|
||||
prevConflictFileCount++
|
||||
}
|
||||
if file.HasInlineMergeConflicts {
|
||||
hasConflicts, err := mergeconflicts.FileHasConflictMarkers(file.Name)
|
||||
if err != nil {
|
||||
self.c.Log.Error(err)
|
||||
} else if !hasConflicts {
|
||||
pathsToStage = append(pathsToStage, file.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(pathsToStage) > 0 {
|
||||
self.c.LogAction(self.c.Tr.Actions.StageResolvedFiles)
|
||||
if err := self.c.Git().WorkingTree.StageFiles(pathsToStage); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
files := self.c.Git().Loaders.FileLoader.
|
||||
GetStatusFiles(git_commands.GetStatusFileOptions{})
|
||||
|
||||
conflictFileCount := 0
|
||||
for _, file := range files {
|
||||
if file.HasMergeConflicts {
|
||||
conflictFileCount++
|
||||
}
|
||||
}
|
||||
|
||||
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE && conflictFileCount == 0 && prevConflictFileCount > 0 {
|
||||
self.c.OnUIThread(func() error { return self.mergeAndRebaseHelper.PromptToContinueRebase() })
|
||||
}
|
||||
|
||||
fileTreeViewModel.RWMutex.Lock()
|
||||
|
||||
// only taking over the filter if it hasn't already been set by the user.
|
||||
// Though this does make it impossible for the user to actually say they want to display all if
|
||||
// conflicts are currently being shown. Hmm. Worth it I reckon. If we need to add some
|
||||
// extra state here to see if the user's set the filter themselves we can do that, but
|
||||
// I'd prefer to maintain as little state as possible.
|
||||
if conflictFileCount > 0 {
|
||||
if fileTreeViewModel.GetFilter() == filetree.DisplayAll {
|
||||
fileTreeViewModel.SetFilter(filetree.DisplayConflicted)
|
||||
}
|
||||
} else if fileTreeViewModel.GetFilter() == filetree.DisplayConflicted {
|
||||
fileTreeViewModel.SetFilter(filetree.DisplayAll)
|
||||
}
|
||||
|
||||
self.c.Model().Files = files
|
||||
fileTreeViewModel.SetTree()
|
||||
fileTreeViewModel.RWMutex.Unlock()
|
||||
|
||||
if err := self.fileWatcher.AddFilesToFileWatcher(files); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// the reflogs panel is the only panel where we cache data, in that we only
|
||||
// load entries that have been created since we last ran the call. This means
|
||||
// we need to be more careful with how we use this, and to ensure we're emptying
|
||||
// the reflogs array when changing contexts.
|
||||
// This method also manages two things: ReflogCommits and FilteredReflogCommits.
|
||||
// FilteredReflogCommits are rendered in the reflogs panel, and ReflogCommits
|
||||
// are used by the branches panel to obtain recency values for sorting.
|
||||
func (self *RefreshHelper) refreshReflogCommits() error {
|
||||
// pulling state into its own variable incase it gets swapped out for another state
|
||||
// and we get an out of bounds exception
|
||||
model := self.c.Model()
|
||||
var lastReflogCommit *models.Commit
|
||||
if len(model.ReflogCommits) > 0 {
|
||||
lastReflogCommit = model.ReflogCommits[0]
|
||||
}
|
||||
|
||||
refresh := func(stateCommits *[]*models.Commit, filterPath string) error {
|
||||
commits, onlyObtainedNewReflogCommits, err := self.c.Git().Loaders.ReflogCommitLoader.
|
||||
GetReflogCommits(lastReflogCommit, filterPath)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if onlyObtainedNewReflogCommits {
|
||||
*stateCommits = append(commits, *stateCommits...)
|
||||
} else {
|
||||
*stateCommits = commits
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := refresh(&model.ReflogCommits, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if self.c.Modes().Filtering.Active() {
|
||||
if err := refresh(&model.FilteredReflogCommits, self.c.Modes().Filtering.GetPath()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
model.FilteredReflogCommits = model.ReflogCommits
|
||||
}
|
||||
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().ReflogCommits)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshRemotes() error {
|
||||
prevSelectedRemote := self.c.Contexts().Remotes.GetSelected()
|
||||
|
||||
remotes, err := self.c.Git().Loaders.RemoteLoader.GetRemotes()
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.Model().Remotes = remotes
|
||||
|
||||
// we need to ensure our selected remote branches aren't now outdated
|
||||
if prevSelectedRemote != nil && self.c.Model().RemoteBranches != nil {
|
||||
// find remote now
|
||||
for _, remote := range remotes {
|
||||
if remote.Name == prevSelectedRemote.Name {
|
||||
self.c.Model().RemoteBranches = remote.Branches
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().Remotes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().RemoteBranches); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshStashEntries() error {
|
||||
self.c.Model().StashEntries = self.c.Git().Loaders.StashLoader.
|
||||
GetStashEntries(self.c.Modes().Filtering.GetPath())
|
||||
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().Stash)
|
||||
}
|
||||
|
||||
// never call this on its own, it should only be called from within refreshCommits()
|
||||
func (self *RefreshHelper) refreshStatus() {
|
||||
self.c.Mutexes().RefreshingStatusMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingStatusMutex.Unlock()
|
||||
|
||||
currentBranch := self.refsHelper.GetCheckedOutRef()
|
||||
if currentBranch == nil {
|
||||
// need to wait for branches to refresh
|
||||
return
|
||||
}
|
||||
status := ""
|
||||
|
||||
if currentBranch.IsRealBranch() {
|
||||
status += presentation.ColoredBranchStatus(currentBranch, self.c.Tr) + " "
|
||||
}
|
||||
|
||||
workingTreeState := self.c.Git().Status.WorkingTreeState()
|
||||
if workingTreeState != enums.REBASE_MODE_NONE {
|
||||
status += style.FgYellow.Sprintf("(%s) ", presentation.FormatWorkingTreeState(workingTreeState))
|
||||
}
|
||||
|
||||
name := presentation.GetBranchTextStyle(currentBranch.Name).Sprint(currentBranch.Name)
|
||||
repoName := utils.GetCurrentRepoName()
|
||||
status += fmt.Sprintf("%s → %s ", repoName, name)
|
||||
|
||||
self.c.SetViewContent(self.c.Views().Status, status)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refForLog() string {
|
||||
bisectInfo := self.c.Git().Bisect.GetInfo()
|
||||
self.c.Model().BisectInfo = bisectInfo
|
||||
|
||||
if !bisectInfo.Started() {
|
||||
return "HEAD"
|
||||
}
|
||||
|
||||
// need to see if our bisect's current commit is reachable from our 'new' ref.
|
||||
if bisectInfo.Bisecting() && !self.c.Git().Bisect.ReachableFromStart(bisectInfo) {
|
||||
return bisectInfo.GetNewSha()
|
||||
}
|
||||
|
||||
return bisectInfo.GetStartSha()
|
||||
}
|
@ -5,10 +5,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
@ -23,23 +21,14 @@ type IRefsHelper interface {
|
||||
}
|
||||
|
||||
type RefsHelper struct {
|
||||
c *types.HelperCommon
|
||||
git *commands.GitCommand
|
||||
contexts *context.ContextTree
|
||||
model *types.Model
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewRefsHelper(
|
||||
c *types.HelperCommon,
|
||||
git *commands.GitCommand,
|
||||
contexts *context.ContextTree,
|
||||
model *types.Model,
|
||||
c *HelperCommon,
|
||||
) *RefsHelper {
|
||||
return &RefsHelper{
|
||||
c: c,
|
||||
git: git,
|
||||
contexts: contexts,
|
||||
model: model,
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,15 +43,15 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
|
||||
cmdOptions := git_commands.CheckoutOptions{Force: false, EnvVars: options.EnvVars}
|
||||
|
||||
onSuccess := func() {
|
||||
self.contexts.Branches.SetSelectedLineIdx(0)
|
||||
self.contexts.ReflogCommits.SetSelectedLineIdx(0)
|
||||
self.contexts.LocalCommits.SetSelectedLineIdx(0)
|
||||
self.c.Contexts().Branches.SetSelectedLineIdx(0)
|
||||
self.c.Contexts().ReflogCommits.SetSelectedLineIdx(0)
|
||||
self.c.Contexts().LocalCommits.SetSelectedLineIdx(0)
|
||||
// loading a heap of commits is slow so we limit them whenever doing a reset
|
||||
self.contexts.LocalCommits.SetLimitCommits(true)
|
||||
self.c.Contexts().LocalCommits.SetLimitCommits(true)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(waitingStatus, func() error {
|
||||
if err := self.git.Branch.Checkout(ref, cmdOptions); err != nil {
|
||||
if err := self.c.Git().Branch.Checkout(ref, cmdOptions); err != nil {
|
||||
// note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option
|
||||
|
||||
if options.OnRefNotFound != nil && strings.Contains(err.Error(), "did not match any file(s) known to git") {
|
||||
@ -75,15 +64,15 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
|
||||
Title: self.c.Tr.AutoStashTitle,
|
||||
Prompt: self.c.Tr.AutoStashPrompt,
|
||||
HandleConfirm: func() error {
|
||||
if err := self.git.Stash.Save(self.c.Tr.StashPrefix + ref); err != nil {
|
||||
if err := self.c.Git().Stash.Save(self.c.Tr.StashPrefix + ref); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.git.Branch.Checkout(ref, cmdOptions); err != nil {
|
||||
if err := self.c.Git().Branch.Checkout(ref, cmdOptions); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
onSuccess()
|
||||
if err := self.git.Stash.Pop(0); err != nil {
|
||||
if err := self.c.Git().Stash.Pop(0); err != nil {
|
||||
if err := self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI}); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -105,22 +94,22 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
|
||||
}
|
||||
|
||||
func (self *RefsHelper) GetCheckedOutRef() *models.Branch {
|
||||
if len(self.model.Branches) == 0 {
|
||||
if len(self.c.Model().Branches) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.model.Branches[0]
|
||||
return self.c.Model().Branches[0]
|
||||
}
|
||||
|
||||
func (self *RefsHelper) ResetToRef(ref string, strength string, envVars []string) error {
|
||||
if err := self.git.Commit.ResetToCommit(ref, strength, envVars); err != nil {
|
||||
if err := self.c.Git().Commit.ResetToCommit(ref, strength, envVars); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.contexts.LocalCommits.SetSelectedLineIdx(0)
|
||||
self.contexts.ReflogCommits.SetSelectedLineIdx(0)
|
||||
self.c.Contexts().LocalCommits.SetSelectedLineIdx(0)
|
||||
self.c.Contexts().ReflogCommits.SetSelectedLineIdx(0)
|
||||
// loading a heap of commits is slow so we limit them whenever doing a reset
|
||||
self.contexts.LocalCommits.SetLimitCommits(true)
|
||||
self.c.Contexts().LocalCommits.SetLimitCommits(true)
|
||||
|
||||
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.BRANCHES, types.REFLOG, types.COMMITS}}); err != nil {
|
||||
return err
|
||||
@ -173,18 +162,18 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
|
||||
InitialContent: suggestedBranchName,
|
||||
HandleConfirm: func(response string) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CreateBranch)
|
||||
if err := self.git.Branch.New(sanitizedBranchName(response), from); err != nil {
|
||||
if err := self.c.Git().Branch.New(sanitizedBranchName(response), from); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if self.c.CurrentContext() != self.contexts.Branches {
|
||||
if err := self.c.PushContext(self.contexts.Branches); err != nil {
|
||||
if self.c.CurrentContext() != self.c.Contexts().Branches {
|
||||
if err := self.c.PushContext(self.c.Contexts().Branches); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
self.contexts.LocalCommits.SetSelectedLineIdx(0)
|
||||
self.contexts.Branches.SetSelectedLineIdx(0)
|
||||
self.c.Contexts().LocalCommits.SetSelectedLineIdx(0)
|
||||
self.c.Contexts().Branches.SetSelectedLineIdx(0)
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
},
|
||||
|
175
pkg/gui/controllers/helpers/repos_helper.go
Normal file
175
pkg/gui/controllers/helpers/repos_helper.go
Normal file
@ -0,0 +1,175 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type onNewRepoFn func(startArgs appTypes.StartArgs, reuseState bool) error
|
||||
|
||||
// helps switch back and forth between repos
|
||||
type ReposHelper struct {
|
||||
c *HelperCommon
|
||||
recordDirectoryHelper *RecordDirectoryHelper
|
||||
onNewRepo onNewRepoFn
|
||||
}
|
||||
|
||||
func NewRecentReposHelper(
|
||||
c *HelperCommon,
|
||||
recordDirectoryHelper *RecordDirectoryHelper,
|
||||
onNewRepo onNewRepoFn,
|
||||
) *ReposHelper {
|
||||
return &ReposHelper{
|
||||
c: c,
|
||||
recordDirectoryHelper: recordDirectoryHelper,
|
||||
onNewRepo: onNewRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ReposHelper) EnterSubmodule(submodule *models.SubmoduleConfig) error {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.State().GetRepoPathStack().Push(wd)
|
||||
|
||||
return self.DispatchSwitchToRepo(submodule.Path, true)
|
||||
}
|
||||
|
||||
func (self *ReposHelper) getCurrentBranch(path string) string {
|
||||
readHeadFile := func(path string) (string, error) {
|
||||
headFile, err := os.ReadFile(filepath.Join(path, "HEAD"))
|
||||
if err == nil {
|
||||
content := strings.TrimSpace(string(headFile))
|
||||
refsPrefix := "ref: refs/heads/"
|
||||
var branchDisplay string
|
||||
if strings.HasPrefix(content, refsPrefix) {
|
||||
// is a branch
|
||||
branchDisplay = strings.TrimPrefix(content, refsPrefix)
|
||||
} else {
|
||||
// detached HEAD state, displaying short SHA
|
||||
branchDisplay = utils.ShortSha(content)
|
||||
}
|
||||
return branchDisplay, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
gitDirPath := filepath.Join(path, ".git")
|
||||
|
||||
if gitDir, err := os.Stat(gitDirPath); err == nil {
|
||||
if gitDir.IsDir() {
|
||||
// ordinary repo
|
||||
if branch, err := readHeadFile(gitDirPath); err == nil {
|
||||
return branch
|
||||
}
|
||||
} else {
|
||||
// worktree
|
||||
if worktreeGitDir, err := os.ReadFile(gitDirPath); err == nil {
|
||||
content := strings.TrimSpace(string(worktreeGitDir))
|
||||
worktreePath := strings.TrimPrefix(content, "gitdir: ")
|
||||
if branch, err := readHeadFile(worktreePath); err == nil {
|
||||
return branch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self.c.Tr.LcBranchUnknown
|
||||
}
|
||||
|
||||
func (self *ReposHelper) CreateRecentReposMenu() error {
|
||||
// we'll show an empty panel if there are no recent repos
|
||||
recentRepoPaths := []string{}
|
||||
if len(self.c.GetAppState().RecentRepos) > 0 {
|
||||
// we skip the first one because we're currently in it
|
||||
recentRepoPaths = self.c.GetAppState().RecentRepos[1:]
|
||||
}
|
||||
|
||||
currentBranches := sync.Map{}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(recentRepoPaths))
|
||||
|
||||
for _, path := range recentRepoPaths {
|
||||
go func(path string) {
|
||||
defer wg.Done()
|
||||
currentBranches.Store(path, self.getCurrentBranch(path))
|
||||
}(path)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
menuItems := slices.Map(recentRepoPaths, func(path string) *types.MenuItem {
|
||||
branchName, _ := currentBranches.Load(path)
|
||||
if icons.IsIconEnabled() {
|
||||
branchName = icons.BRANCH_ICON + " " + fmt.Sprintf("%v", branchName)
|
||||
}
|
||||
|
||||
return &types.MenuItem{
|
||||
LabelColumns: []string{
|
||||
filepath.Base(path),
|
||||
style.FgCyan.Sprint(branchName),
|
||||
style.FgMagenta.Sprint(path),
|
||||
},
|
||||
OnPress: func() error {
|
||||
// if we were in a submodule, we want to forget about that stack of repos
|
||||
// so that hitting escape in the new repo does nothing
|
||||
self.c.State().GetRepoPathStack().Clear()
|
||||
return self.DispatchSwitchToRepo(path, false)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.RecentRepos, Items: menuItems})
|
||||
}
|
||||
|
||||
func (self *ReposHelper) DispatchSwitchToRepo(path string, reuse bool) error {
|
||||
env.UnsetGitDirEnvs()
|
||||
originalPath, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.Chdir(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return self.c.ErrorMsg(self.c.Tr.ErrRepositoryMovedOrDeleted)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := commands.VerifyInGitRepo(self.c.OS()); err != nil {
|
||||
if err := os.Chdir(originalPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.recordDirectoryHelper.RecordCurrentDirectory(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// these two mutexes are used by our background goroutines (triggered via `self.goEvery`. We don't want to
|
||||
// switch to a repo while one of these goroutines is in the process of updating something
|
||||
self.c.Mutexes().SyncMutex.Lock()
|
||||
defer self.c.Mutexes().SyncMutex.Unlock()
|
||||
|
||||
self.c.Mutexes().RefreshingFilesMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
|
||||
|
||||
return self.onNewRepo(appTypes.StartArgs{}, reuse)
|
||||
}
|
75
pkg/gui/controllers/helpers/snake_helper.go
Normal file
75
pkg/gui/controllers/helpers/snake_helper.go
Normal file
@ -0,0 +1,75 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/snake"
|
||||
)
|
||||
|
||||
type SnakeHelper struct {
|
||||
c *HelperCommon
|
||||
game *snake.Game
|
||||
}
|
||||
|
||||
func NewSnakeHelper(c *HelperCommon) *SnakeHelper {
|
||||
return &SnakeHelper{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SnakeHelper) StartGame() {
|
||||
view := self.c.Views().Snake
|
||||
|
||||
game := snake.NewGame(view.Width(), view.Height(), self.renderSnakeGame, self.c.LogAction)
|
||||
self.game = game
|
||||
game.Start()
|
||||
}
|
||||
|
||||
func (self *SnakeHelper) ExitGame() {
|
||||
self.game.Exit()
|
||||
}
|
||||
|
||||
func (self *SnakeHelper) SetDirection(direction snake.Direction) {
|
||||
self.game.SetDirection(direction)
|
||||
}
|
||||
|
||||
func (self *SnakeHelper) renderSnakeGame(cells [][]snake.CellType, alive bool) {
|
||||
view := self.c.Views().Snake
|
||||
|
||||
if !alive {
|
||||
_ = self.c.ErrorMsg(self.c.Tr.YouDied)
|
||||
return
|
||||
}
|
||||
|
||||
output := self.drawSnakeGame(cells)
|
||||
|
||||
view.Clear()
|
||||
fmt.Fprint(view, output)
|
||||
self.c.Render()
|
||||
}
|
||||
|
||||
func (self *SnakeHelper) drawSnakeGame(cells [][]snake.CellType) string {
|
||||
writer := &strings.Builder{}
|
||||
|
||||
for i, row := range cells {
|
||||
for _, cell := range row {
|
||||
switch cell {
|
||||
case snake.None:
|
||||
writer.WriteString(" ")
|
||||
case snake.Snake:
|
||||
writer.WriteString("█")
|
||||
case snake.Food:
|
||||
writer.WriteString(style.FgMagenta.Sprint("█"))
|
||||
}
|
||||
}
|
||||
|
||||
if i < len(cells) {
|
||||
writer.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
output := writer.String()
|
||||
return output
|
||||
}
|
122
pkg/gui/controllers/helpers/staging_helper.go
Normal file
122
pkg/gui/controllers/helpers/staging_helper.go
Normal file
@ -0,0 +1,122 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type StagingHelper struct {
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewStagingHelper(
|
||||
c *HelperCommon,
|
||||
) *StagingHelper {
|
||||
return &StagingHelper{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: used from outside this file
|
||||
func (self *StagingHelper) RefreshStagingPanel(focusOpts types.OnFocusOpts) error {
|
||||
secondaryFocused := self.secondaryStagingFocused()
|
||||
mainFocused := self.mainStagingFocused()
|
||||
|
||||
// this method could be called when the staging panel is not being used,
|
||||
// in which case we don't want to do anything.
|
||||
if !mainFocused && !secondaryFocused {
|
||||
return nil
|
||||
}
|
||||
|
||||
mainSelectedLineIdx := -1
|
||||
secondarySelectedLineIdx := -1
|
||||
if focusOpts.ClickedViewLineIdx > 0 {
|
||||
if secondaryFocused {
|
||||
secondarySelectedLineIdx = focusOpts.ClickedViewLineIdx
|
||||
} else {
|
||||
mainSelectedLineIdx = focusOpts.ClickedViewLineIdx
|
||||
}
|
||||
}
|
||||
|
||||
mainContext := self.c.Contexts().Staging
|
||||
secondaryContext := self.c.Contexts().StagingSecondary
|
||||
|
||||
var file *models.File
|
||||
node := self.c.Contexts().Files.GetSelected()
|
||||
if node != nil {
|
||||
file = node.File
|
||||
}
|
||||
|
||||
if file == nil || (!file.HasUnstagedChanges && !file.HasStagedChanges) {
|
||||
return self.handleStagingEscape()
|
||||
}
|
||||
|
||||
mainDiff := self.c.Git().WorkingTree.WorktreeFileDiff(file, true, false, false)
|
||||
secondaryDiff := self.c.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(), self.c.Log),
|
||||
)
|
||||
|
||||
secondaryContext.SetState(
|
||||
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetState(), self.c.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 self.handleStagingEscape()
|
||||
}
|
||||
|
||||
if mainState == nil && !secondaryFocused {
|
||||
return self.c.PushContext(secondaryContext, focusOpts)
|
||||
}
|
||||
|
||||
if secondaryState == nil && secondaryFocused {
|
||||
return self.c.PushContext(mainContext, focusOpts)
|
||||
}
|
||||
|
||||
if secondaryFocused {
|
||||
self.c.Contexts().StagingSecondary.FocusSelection()
|
||||
} else {
|
||||
self.c.Contexts().Staging.FocusSelection()
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Staging,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(mainContent),
|
||||
Title: self.c.Tr.UnstagedChanges,
|
||||
},
|
||||
Secondary: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(secondaryContent),
|
||||
Title: self.c.Tr.StagedChanges,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *StagingHelper) handleStagingEscape() error {
|
||||
return self.c.PushContext(self.c.Contexts().Files)
|
||||
}
|
||||
|
||||
func (self *StagingHelper) secondaryStagingFocused() bool {
|
||||
return self.c.CurrentStaticContext().GetKey() == self.c.Contexts().StagingSecondary.GetKey()
|
||||
}
|
||||
|
||||
func (self *StagingHelper) mainStagingFocused() bool {
|
||||
return self.c.CurrentStaticContext().GetKey() == self.c.Contexts().Staging.GetKey()
|
||||
}
|
@ -33,28 +33,21 @@ type ISuggestionsHelper interface {
|
||||
}
|
||||
|
||||
type SuggestionsHelper struct {
|
||||
c *types.HelperCommon
|
||||
|
||||
model *types.Model
|
||||
refreshSuggestionsFn func()
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
var _ ISuggestionsHelper = &SuggestionsHelper{}
|
||||
|
||||
func NewSuggestionsHelper(
|
||||
c *types.HelperCommon,
|
||||
model *types.Model,
|
||||
refreshSuggestionsFn func(),
|
||||
c *HelperCommon,
|
||||
) *SuggestionsHelper {
|
||||
return &SuggestionsHelper{
|
||||
c: c,
|
||||
model: model,
|
||||
refreshSuggestionsFn: refreshSuggestionsFn,
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) getRemoteNames() []string {
|
||||
return slices.Map(self.model.Remotes, func(remote *models.Remote) string {
|
||||
return slices.Map(self.c.Model().Remotes, func(remote *models.Remote) string {
|
||||
return remote.Name
|
||||
})
|
||||
}
|
||||
@ -75,7 +68,7 @@ func (self *SuggestionsHelper) GetRemoteSuggestionsFunc() func(string) []*types.
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) getBranchNames() []string {
|
||||
return slices.Map(self.model.Branches, func(branch *models.Branch) string {
|
||||
return slices.Map(self.c.Model().Branches, func(branch *models.Branch) string {
|
||||
return branch.Name
|
||||
})
|
||||
}
|
||||
@ -101,8 +94,8 @@ func (self *SuggestionsHelper) GetBranchNameSuggestionsFunc() func(string) []*ty
|
||||
}
|
||||
|
||||
// here we asynchronously fetch the latest set of paths in the repo and store in
|
||||
// self.model.FilesTrie. On the main thread we'll be doing a fuzzy search via
|
||||
// self.model.FilesTrie. So if we've looked for a file previously, we'll start with
|
||||
// self.c.Model().FilesTrie. On the main thread we'll be doing a fuzzy search via
|
||||
// self.c.Model().FilesTrie. So if we've looked for a file previously, we'll start with
|
||||
// the old trie and eventually it'll be swapped out for the new one.
|
||||
// Notably, unlike other suggestion functions we're not showing all the options
|
||||
// if nothing has been typed because there'll be too much to display efficiently
|
||||
@ -125,16 +118,16 @@ func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*type
|
||||
})
|
||||
|
||||
// cache the trie for future use
|
||||
self.model.FilesTrie = trie
|
||||
self.c.Model().FilesTrie = trie
|
||||
|
||||
self.refreshSuggestionsFn()
|
||||
self.c.Contexts().Suggestions.RefreshSuggestions()
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
return func(input string) []*types.Suggestion {
|
||||
matchingNames := []string{}
|
||||
_ = self.model.FilesTrie.VisitFuzzy(patricia.Prefix(input), true, func(prefix patricia.Prefix, item patricia.Item, skipped int) error {
|
||||
_ = self.c.Model().FilesTrie.VisitFuzzy(patricia.Prefix(input), true, func(prefix patricia.Prefix, item patricia.Item, skipped int) error {
|
||||
matchingNames = append(matchingNames, item.(string))
|
||||
return nil
|
||||
})
|
||||
@ -147,7 +140,7 @@ func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*type
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) getRemoteBranchNames(separator string) []string {
|
||||
return slices.FlatMap(self.model.Remotes, func(remote *models.Remote) []string {
|
||||
return slices.FlatMap(self.c.Model().Remotes, func(remote *models.Remote) []string {
|
||||
return slices.Map(remote.Branches, func(branch *models.RemoteBranch) string {
|
||||
return fmt.Sprintf("%s%s%s", remote.Name, separator, branch.Name)
|
||||
})
|
||||
@ -159,7 +152,7 @@ func (self *SuggestionsHelper) GetRemoteBranchesSuggestionsFunc(separator string
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) getTagNames() []string {
|
||||
return slices.Map(self.model.Tags, func(tag *models.Tag) string {
|
||||
return slices.Map(self.c.Model().Tags, func(tag *models.Tag) string {
|
||||
return tag.Name
|
||||
})
|
||||
}
|
||||
@ -176,7 +169,7 @@ func (self *SuggestionsHelper) GetRefsSuggestionsFunc() func(string) []*types.Su
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) GetAuthorsSuggestionsFunc() func(string) []*types.Suggestion {
|
||||
authors := lo.Uniq(slices.Map(self.model.Commits, func(commit *models.Commit) string {
|
||||
authors := lo.Uniq(slices.Map(self.c.Model().Commits, func(commit *models.Commit) string {
|
||||
return fmt.Sprintf("%s <%s>", commit.AuthorName, commit.AuthorEmail)
|
||||
}))
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -10,14 +9,12 @@ import (
|
||||
// and the commits context.
|
||||
|
||||
type TagsHelper struct {
|
||||
c *types.HelperCommon
|
||||
git *commands.GitCommand
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewTagsHelper(c *types.HelperCommon, git *commands.GitCommand) *TagsHelper {
|
||||
func NewTagsHelper(c *HelperCommon) *TagsHelper {
|
||||
return &TagsHelper{
|
||||
c: c,
|
||||
git: git,
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,7 +53,7 @@ func (self *TagsHelper) handleCreateAnnotatedTag(ref string, onCreate func()) er
|
||||
Title: self.c.Tr.TagMessageTitle,
|
||||
HandleConfirm: func(msg string) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CreateAnnotatedTag)
|
||||
if err := self.git.Tag.CreateAnnotated(tagName, ref, msg); err != nil {
|
||||
if err := self.c.Git().Tag.CreateAnnotated(tagName, ref, msg); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return self.afterTagCreate(onCreate)
|
||||
@ -71,7 +68,7 @@ func (self *TagsHelper) handleCreateLightweightTag(ref string, onCreate func())
|
||||
Title: self.c.Tr.TagNameTitle,
|
||||
HandleConfirm: func(tagName string) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CreateLightweightTag)
|
||||
if err := self.git.Tag.CreateLightweight(tagName, ref); err != nil {
|
||||
if err := self.c.Git().Tag.CreateLightweight(tagName, ref); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return self.afterTagCreate(onCreate)
|
||||
|
96
pkg/gui/controllers/helpers/update_helper.go
Normal file
96
pkg/gui/controllers/helpers/update_helper.go
Normal file
@ -0,0 +1,96 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/updates"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type UpdateHelper struct {
|
||||
c *HelperCommon
|
||||
updater *updates.Updater
|
||||
}
|
||||
|
||||
func NewUpdateHelper(c *HelperCommon, updater *updates.Updater) *UpdateHelper {
|
||||
return &UpdateHelper{
|
||||
c: c,
|
||||
updater: updater,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *UpdateHelper) CheckForUpdateInBackground() {
|
||||
self.updater.CheckForNewUpdate(func(newVersion string, err error) error {
|
||||
if err != nil {
|
||||
// ignoring the error for now so that I'm not annoying users
|
||||
self.c.Log.Error(err.Error())
|
||||
return nil
|
||||
}
|
||||
if newVersion == "" {
|
||||
return nil
|
||||
}
|
||||
if self.c.UserConfig.Update.Method == "background" {
|
||||
self.startUpdating(newVersion)
|
||||
return nil
|
||||
}
|
||||
return self.showUpdatePrompt(newVersion)
|
||||
}, false)
|
||||
}
|
||||
|
||||
func (self *UpdateHelper) CheckForUpdateInForeground() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.CheckingForUpdates, func() error {
|
||||
self.updater.CheckForNewUpdate(func(newVersion string, err error) error {
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if newVersion == "" {
|
||||
return self.c.ErrorMsg(self.c.Tr.FailedToRetrieveLatestVersionErr)
|
||||
}
|
||||
return self.showUpdatePrompt(newVersion)
|
||||
}, true)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (self *UpdateHelper) startUpdating(newVersion string) {
|
||||
_ = self.c.WithWaitingStatus(self.c.Tr.UpdateInProgressWaitingStatus, func() error {
|
||||
self.c.State().SetUpdating(true)
|
||||
err := self.updater.Update(newVersion)
|
||||
return self.onUpdateFinish(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *UpdateHelper) onUpdateFinish(err error) error {
|
||||
self.c.State().SetUpdating(false)
|
||||
self.c.OnUIThread(func() error {
|
||||
self.c.SetViewContent(self.c.Views().AppStatus, "")
|
||||
if err != nil {
|
||||
errMessage := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.UpdateFailedErr, map[string]string{
|
||||
"errMessage": err.Error(),
|
||||
},
|
||||
)
|
||||
return self.c.ErrorMsg(errMessage)
|
||||
}
|
||||
return self.c.Alert(self.c.Tr.UpdateCompletedTitle, self.c.Tr.UpdateCompleted)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *UpdateHelper) showUpdatePrompt(newVersion string) error {
|
||||
message := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.UpdateAvailable, map[string]string{
|
||||
"newVersion": newVersion,
|
||||
},
|
||||
)
|
||||
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.UpdateAvailableTitle,
|
||||
Prompt: message,
|
||||
HandleConfirm: func() error {
|
||||
self.startUpdating(newVersion)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
@ -9,8 +9,7 @@ import (
|
||||
)
|
||||
|
||||
type UpstreamHelper struct {
|
||||
c *types.HelperCommon
|
||||
model *types.Model
|
||||
c *HelperCommon
|
||||
|
||||
getRemoteBranchesSuggestionsFunc func(string) func(string) []*types.Suggestion
|
||||
}
|
||||
@ -25,13 +24,11 @@ type IUpstreamHelper interface {
|
||||
var _ IUpstreamHelper = &UpstreamHelper{}
|
||||
|
||||
func NewUpstreamHelper(
|
||||
c *types.HelperCommon,
|
||||
model *types.Model,
|
||||
c *HelperCommon,
|
||||
getRemoteBranchesSuggestionsFunc func(string) func(string) []*types.Suggestion,
|
||||
) *UpstreamHelper {
|
||||
return &UpstreamHelper{
|
||||
c: c,
|
||||
model: model,
|
||||
getRemoteBranchesSuggestionsFunc: getRemoteBranchesSuggestionsFunc,
|
||||
}
|
||||
}
|
||||
@ -70,7 +67,7 @@ func (self *UpstreamHelper) PromptForUpstreamWithoutInitialContent(_ *models.Bra
|
||||
}
|
||||
|
||||
func (self *UpstreamHelper) GetSuggestedRemote() string {
|
||||
return getSuggestedRemote(self.model.Remotes)
|
||||
return getSuggestedRemote(self.c.Model().Remotes)
|
||||
}
|
||||
|
||||
func getSuggestedRemote(remotes []*models.Remote) string {
|
||||
|
31
pkg/gui/controllers/helpers/view_helper.go
Normal file
31
pkg/gui/controllers/helpers/view_helper.go
Normal file
@ -0,0 +1,31 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type ViewHelper struct {
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewViewHelper(c *HelperCommon, contexts *context.ContextTree) *ViewHelper {
|
||||
return &ViewHelper{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ViewHelper) ContextForView(viewName string) (types.Context, bool) {
|
||||
view, err := self.c.GocuiGui().View(viewName)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
for _, context := range self.c.Contexts().Flatten() {
|
||||
if context.GetViewName() == view.Name() {
|
||||
return context, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
package gui
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazycore/pkg/boxlayout"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/constants"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
@ -11,12 +12,33 @@ import (
|
||||
// In this file we use the boxlayout package, along with knowledge about the app's state,
|
||||
// to arrange the windows (i.e. panels) on the screen.
|
||||
|
||||
type WindowArrangementHelper struct {
|
||||
c *HelperCommon
|
||||
windowHelper *WindowHelper
|
||||
modeHelper *ModeHelper
|
||||
appStatusHelper *AppStatusHelper
|
||||
}
|
||||
|
||||
func NewWindowArrangementHelper(
|
||||
c *HelperCommon,
|
||||
windowHelper *WindowHelper,
|
||||
modeHelper *ModeHelper,
|
||||
appStatusHelper *AppStatusHelper,
|
||||
) *WindowArrangementHelper {
|
||||
return &WindowArrangementHelper{
|
||||
c: c,
|
||||
windowHelper: windowHelper,
|
||||
modeHelper: modeHelper,
|
||||
appStatusHelper: appStatusHelper,
|
||||
}
|
||||
}
|
||||
|
||||
const INFO_SECTION_PADDING = " "
|
||||
|
||||
func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
|
||||
width, height := gui.g.Size()
|
||||
func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
|
||||
width, height := self.c.GocuiGui().Size()
|
||||
|
||||
sideSectionWeight, mainSectionWeight := gui.getMidSectionWeights()
|
||||
sideSectionWeight, mainSectionWeight := self.getMidSectionWeights()
|
||||
|
||||
sidePanelsDirection := boxlayout.COLUMN
|
||||
portraitMode := width <= 84 && height > 45
|
||||
@ -25,13 +47,18 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
|
||||
}
|
||||
|
||||
mainPanelsDirection := boxlayout.ROW
|
||||
if gui.splitMainPanelSideBySide() {
|
||||
if self.splitMainPanelSideBySide() {
|
||||
mainPanelsDirection = boxlayout.COLUMN
|
||||
}
|
||||
|
||||
extrasWindowSize := gui.getExtrasWindowSize(height)
|
||||
extrasWindowSize := self.getExtrasWindowSize(height)
|
||||
|
||||
showInfoSection := gui.c.UserConfig.Gui.ShowBottomLine || gui.State.Searching.isSearching || gui.isAnyModeActive() || gui.statusManager.showStatus()
|
||||
self.c.Modes().Filtering.Active()
|
||||
|
||||
showInfoSection := self.c.UserConfig.Gui.ShowBottomLine ||
|
||||
self.c.State().GetRepoState().IsSearching() ||
|
||||
self.modeHelper.IsAnyModeActive() ||
|
||||
self.appStatusHelper.HasStatus()
|
||||
infoSectionSize := 0
|
||||
if showInfoSection {
|
||||
infoSectionSize = 1
|
||||
@ -47,7 +74,7 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
|
||||
{
|
||||
Direction: boxlayout.ROW,
|
||||
Weight: sideSectionWeight,
|
||||
ConditionalChildren: gui.sidePanelChildren,
|
||||
ConditionalChildren: self.sidePanelChildren,
|
||||
},
|
||||
{
|
||||
Direction: boxlayout.ROW,
|
||||
@ -55,7 +82,7 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
|
||||
Children: []*boxlayout.Box{
|
||||
{
|
||||
Direction: mainPanelsDirection,
|
||||
Children: gui.mainSectionChildren(),
|
||||
Children: self.mainSectionChildren(),
|
||||
Weight: 1,
|
||||
},
|
||||
{
|
||||
@ -69,7 +96,7 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
|
||||
{
|
||||
Direction: boxlayout.COLUMN,
|
||||
Size: infoSectionSize,
|
||||
Children: gui.infoSectionChildren(informationStr, appStatus),
|
||||
Children: self.infoSectionChildren(informationStr, appStatus),
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -91,12 +118,12 @@ func MergeMaps[K comparable, V any](maps ...map[K]V) map[K]V {
|
||||
return result
|
||||
}
|
||||
|
||||
func (gui *Gui) mainSectionChildren() []*boxlayout.Box {
|
||||
currentWindow := gui.currentWindow()
|
||||
func (self *WindowArrangementHelper) mainSectionChildren() []*boxlayout.Box {
|
||||
currentWindow := self.windowHelper.CurrentWindow()
|
||||
|
||||
// if we're not in split mode we can just show the one main panel. Likewise if
|
||||
// the main panel is focused and we're in full-screen mode
|
||||
if !gui.isMainPanelSplit() || (gui.State.ScreenMode == SCREEN_FULL && currentWindow == "main") {
|
||||
if !self.c.State().GetRepoState().GetSplitMainPanel() || (self.c.State().GetRepoState().GetScreenMode() == types.SCREEN_FULL && currentWindow == "main") {
|
||||
return []*boxlayout.Box{
|
||||
{
|
||||
Window: "main",
|
||||
@ -117,27 +144,29 @@ func (gui *Gui) mainSectionChildren() []*boxlayout.Box {
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) getMidSectionWeights() (int, int) {
|
||||
currentWindow := gui.currentWindow()
|
||||
func (self *WindowArrangementHelper) getMidSectionWeights() (int, int) {
|
||||
currentWindow := self.windowHelper.CurrentWindow()
|
||||
|
||||
// we originally specified this as a ratio i.e. .20 would correspond to a weight of 1 against 4
|
||||
sidePanelWidthRatio := gui.c.UserConfig.Gui.SidePanelWidth
|
||||
sidePanelWidthRatio := self.c.UserConfig.Gui.SidePanelWidth
|
||||
// we could make this better by creating ratios like 2:3 rather than always 1:something
|
||||
mainSectionWeight := int(1/sidePanelWidthRatio) - 1
|
||||
sideSectionWeight := 1
|
||||
|
||||
if gui.splitMainPanelSideBySide() {
|
||||
if self.splitMainPanelSideBySide() {
|
||||
mainSectionWeight = 5 // need to shrink side panel to make way for main panels if side-by-side
|
||||
}
|
||||
|
||||
screenMode := self.c.State().GetRepoState().GetScreenMode()
|
||||
|
||||
if currentWindow == "main" {
|
||||
if gui.State.ScreenMode == SCREEN_HALF || gui.State.ScreenMode == SCREEN_FULL {
|
||||
if screenMode == types.SCREEN_HALF || screenMode == types.SCREEN_FULL {
|
||||
sideSectionWeight = 0
|
||||
}
|
||||
} else {
|
||||
if gui.State.ScreenMode == SCREEN_HALF {
|
||||
if screenMode == types.SCREEN_HALF {
|
||||
mainSectionWeight = 1
|
||||
} else if gui.State.ScreenMode == SCREEN_FULL {
|
||||
} else if screenMode == types.SCREEN_FULL {
|
||||
mainSectionWeight = 0
|
||||
}
|
||||
}
|
||||
@ -145,12 +174,12 @@ func (gui *Gui) getMidSectionWeights() (int, int) {
|
||||
return sideSectionWeight, mainSectionWeight
|
||||
}
|
||||
|
||||
func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box {
|
||||
if gui.State.Searching.isSearching {
|
||||
func (self *WindowArrangementHelper) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box {
|
||||
if self.c.State().GetRepoState().IsSearching() {
|
||||
return []*boxlayout.Box{
|
||||
{
|
||||
Window: "searchPrefix",
|
||||
Size: runewidth.StringWidth(SEARCH_PREFIX),
|
||||
Size: runewidth.StringWidth(constants.SEARCH_PREFIX),
|
||||
},
|
||||
{
|
||||
Window: "search",
|
||||
@ -162,7 +191,7 @@ func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*
|
||||
appStatusBox := &boxlayout.Box{Window: "appStatus"}
|
||||
optionsBox := &boxlayout.Box{Window: "options"}
|
||||
|
||||
if !gui.c.UserConfig.Gui.ShowBottomLine {
|
||||
if !self.c.UserConfig.Gui.ShowBottomLine {
|
||||
optionsBox.Weight = 0
|
||||
appStatusBox.Weight = 1
|
||||
} else {
|
||||
@ -172,7 +201,7 @@ func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*
|
||||
|
||||
result := []*boxlayout.Box{appStatusBox, optionsBox}
|
||||
|
||||
if gui.c.UserConfig.Gui.ShowBottomLine || gui.isAnyModeActive() {
|
||||
if self.c.UserConfig.Gui.ShowBottomLine || self.modeHelper.IsAnyModeActive() {
|
||||
result = append(result, &boxlayout.Box{
|
||||
Window: "information",
|
||||
// unlike appStatus, informationStr has various colors so we need to decolorise before taking the length
|
||||
@ -183,13 +212,13 @@ func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*
|
||||
return result
|
||||
}
|
||||
|
||||
func (gui *Gui) splitMainPanelSideBySide() bool {
|
||||
if !gui.isMainPanelSplit() {
|
||||
func (self *WindowArrangementHelper) splitMainPanelSideBySide() bool {
|
||||
if !self.c.State().GetRepoState().GetSplitMainPanel() {
|
||||
return false
|
||||
}
|
||||
|
||||
mainPanelSplitMode := gui.c.UserConfig.Gui.MainPanelSplitMode
|
||||
width, height := gui.g.Size()
|
||||
mainPanelSplitMode := self.c.UserConfig.Gui.MainPanelSplitMode
|
||||
width, height := self.c.GocuiGui().Size()
|
||||
|
||||
switch mainPanelSplitMode {
|
||||
case "vertical":
|
||||
@ -205,18 +234,18 @@ func (gui *Gui) splitMainPanelSideBySide() bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) getExtrasWindowSize(screenHeight int) int {
|
||||
if !gui.ShowExtrasWindow {
|
||||
func (self *WindowArrangementHelper) getExtrasWindowSize(screenHeight int) int {
|
||||
if !self.c.State().GetShowExtrasWindow() {
|
||||
return 0
|
||||
}
|
||||
|
||||
var baseSize int
|
||||
if gui.currentStaticContext().GetKey() == context.COMMAND_LOG_CONTEXT_KEY {
|
||||
if self.c.CurrentStaticContext().GetKey() == context.COMMAND_LOG_CONTEXT_KEY {
|
||||
baseSize = 1000 // my way of saying 'fill the available space'
|
||||
} else if screenHeight < 40 {
|
||||
baseSize = 1
|
||||
} else {
|
||||
baseSize = gui.c.UserConfig.Gui.CommandLogSize
|
||||
baseSize = self.c.UserConfig.Gui.CommandLogSize
|
||||
}
|
||||
|
||||
frameSize := 2
|
||||
@ -227,17 +256,15 @@ func (gui *Gui) getExtrasWindowSize(screenHeight int) int {
|
||||
// too much space, but if you access it it should take up some space. This is
|
||||
// the default behaviour when accordion mode is NOT in effect. If it is in effect
|
||||
// then when it's accessed it will have weight 2, not 1.
|
||||
func (gui *Gui) getDefaultStashWindowBox() *boxlayout.Box {
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
|
||||
box := &boxlayout.Box{Window: "stash"}
|
||||
func (self *WindowArrangementHelper) getDefaultStashWindowBox() *boxlayout.Box {
|
||||
stashWindowAccessed := false
|
||||
for _, context := range gui.State.ContextManager.ContextStack {
|
||||
self.c.Context().ForEach(func(context types.Context) {
|
||||
if context.GetWindowName() == "stash" {
|
||||
stashWindowAccessed = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
box := &boxlayout.Box{Window: "stash"}
|
||||
// if the stash window is anywhere in our stack we should enlargen it
|
||||
if stashWindowAccessed {
|
||||
box.Weight = 1
|
||||
@ -248,10 +275,11 @@ func (gui *Gui) getDefaultStashWindowBox() *boxlayout.Box {
|
||||
return box
|
||||
}
|
||||
|
||||
func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
||||
currentWindow := gui.currentSideWindowName()
|
||||
func (self *WindowArrangementHelper) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
||||
currentWindow := self.c.CurrentSideContext().GetWindowName()
|
||||
|
||||
if gui.State.ScreenMode == SCREEN_FULL || gui.State.ScreenMode == SCREEN_HALF {
|
||||
screenMode := self.c.State().GetRepoState().GetScreenMode()
|
||||
if screenMode == types.SCREEN_FULL || screenMode == types.SCREEN_HALF {
|
||||
fullHeightBox := func(window string) *boxlayout.Box {
|
||||
if window == currentWindow {
|
||||
return &boxlayout.Box{
|
||||
@ -274,7 +302,7 @@ func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
||||
fullHeightBox("stash"),
|
||||
}
|
||||
} else if height >= 28 {
|
||||
accordionMode := gui.c.UserConfig.Gui.ExpandFocusedSidePanel
|
||||
accordionMode := self.c.UserConfig.Gui.ExpandFocusedSidePanel
|
||||
accordionBox := func(defaultBox *boxlayout.Box) *boxlayout.Box {
|
||||
if accordionMode && defaultBox.Window == currentWindow {
|
||||
return &boxlayout.Box{
|
||||
@ -294,7 +322,7 @@ func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
||||
accordionBox(&boxlayout.Box{Window: "files", Weight: 1}),
|
||||
accordionBox(&boxlayout.Box{Window: "branches", Weight: 1}),
|
||||
accordionBox(&boxlayout.Box{Window: "commits", Weight: 1}),
|
||||
accordionBox(gui.getDefaultStashWindowBox()),
|
||||
accordionBox(self.getDefaultStashWindowBox()),
|
||||
}
|
||||
} else {
|
||||
squashedHeight := 1
|
||||
@ -325,24 +353,3 @@ func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) getCyclableWindows() []string {
|
||||
return []string{"status", "files", "branches", "commits", "stash"}
|
||||
}
|
||||
|
||||
func (gui *Gui) currentSideWindowName() string {
|
||||
// there is always one and only one cyclable context in the context stack. We'll look from top to bottom
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
|
||||
for idx := range gui.State.ContextManager.ContextStack {
|
||||
reversedIdx := len(gui.State.ContextManager.ContextStack) - 1 - idx
|
||||
context := gui.State.ContextManager.ContextStack[reversedIdx]
|
||||
|
||||
if context.GetKind() == types.SIDE_CONTEXT {
|
||||
return context.GetWindowName()
|
||||
}
|
||||
}
|
||||
|
||||
return "files" // default
|
||||
}
|
139
pkg/gui/controllers/helpers/window_helper.go
Normal file
139
pkg/gui/controllers/helpers/window_helper.go
Normal file
@ -0,0 +1,139 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type WindowHelper struct {
|
||||
c *HelperCommon
|
||||
viewHelper *ViewHelper
|
||||
}
|
||||
|
||||
func NewWindowHelper(c *HelperCommon, viewHelper *ViewHelper) *WindowHelper {
|
||||
return &WindowHelper{
|
||||
c: c,
|
||||
viewHelper: viewHelper,
|
||||
}
|
||||
}
|
||||
|
||||
// A window refers to a place on the screen which can hold one or more views.
|
||||
// A view is a box that renders content, and within a window only one view will
|
||||
// appear at a time. When a view appears within a window, it occupies the whole
|
||||
// space. Right now most windows are 1:1 with views, except for commitFiles which
|
||||
// is a view that moves between windows
|
||||
|
||||
func (self *WindowHelper) GetViewNameForWindow(window string) string {
|
||||
viewName, ok := self.windowViewNameMap().Get(window)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("Viewname not found for window: %s", window))
|
||||
}
|
||||
|
||||
return viewName
|
||||
}
|
||||
|
||||
func (self *WindowHelper) GetContextForWindow(window string) types.Context {
|
||||
viewName := self.GetViewNameForWindow(window)
|
||||
|
||||
context, ok := self.viewHelper.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 (self *WindowHelper) SetWindowContext(c types.Context) {
|
||||
if c.IsTransient() {
|
||||
self.resetWindowContext(c)
|
||||
}
|
||||
|
||||
self.windowViewNameMap().Set(c.GetWindowName(), c.GetViewName())
|
||||
}
|
||||
|
||||
func (self *WindowHelper) windowViewNameMap() *utils.ThreadSafeMap[string, string] {
|
||||
return self.c.State().GetRepoState().GetWindowViewNameMap()
|
||||
}
|
||||
|
||||
func (self *WindowHelper) CurrentWindow() string {
|
||||
return self.c.CurrentContext().GetWindowName()
|
||||
}
|
||||
|
||||
// assumes the context's windowName has been set to the new window if necessary
|
||||
func (self *WindowHelper) resetWindowContext(c types.Context) {
|
||||
for _, windowName := range self.windowViewNameMap().Keys() {
|
||||
viewName, ok := self.windowViewNameMap().Get(windowName)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if viewName == c.GetViewName() && windowName != c.GetWindowName() {
|
||||
for _, context := range self.c.Contexts().Flatten() {
|
||||
if context.GetKey() != c.GetKey() && context.GetWindowName() == windowName {
|
||||
self.windowViewNameMap().Set(windowName, context.GetViewName())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// moves given context's view to the top of the window
|
||||
func (self *WindowHelper) MoveToTopOfWindow(context types.Context) {
|
||||
view := context.GetView()
|
||||
if view == nil {
|
||||
return
|
||||
}
|
||||
|
||||
window := context.GetWindowName()
|
||||
|
||||
topView := self.TopViewInWindow(window)
|
||||
|
||||
if view.Name() != topView.Name() {
|
||||
if err := self.c.GocuiGui().SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *WindowHelper) TopViewInWindow(windowName string) *gocui.View {
|
||||
// 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 := self.viewNamesInWindow(windowName)
|
||||
|
||||
// The views list is ordered highest-last, so we're grabbing the last view of the window
|
||||
var topView *gocui.View
|
||||
for _, currentView := range self.c.GocuiGui().Views() {
|
||||
if lo.Contains(viewNamesInWindow, currentView.Name()) {
|
||||
topView = currentView
|
||||
}
|
||||
}
|
||||
|
||||
return topView
|
||||
}
|
||||
|
||||
func (self *WindowHelper) viewNamesInWindow(windowName string) []string {
|
||||
result := []string{}
|
||||
for _, context := range self.c.Contexts().Flatten() {
|
||||
if context.GetWindowName() == windowName {
|
||||
result = append(result, context.GetViewName())
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (self *WindowHelper) WindowForView(viewName string) string {
|
||||
context, ok := self.viewHelper.ContextForView(viewName)
|
||||
if !ok {
|
||||
panic("todo: deal with this")
|
||||
}
|
||||
|
||||
return context.GetWindowName()
|
||||
}
|
||||
|
||||
func (self *WindowHelper) SideWindows() []string {
|
||||
return []string{"status", "files", "branches", "commits", "stash"}
|
||||
}
|
@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
@ -20,40 +19,28 @@ type IWorkingTreeHelper interface {
|
||||
}
|
||||
|
||||
type WorkingTreeHelper struct {
|
||||
c *types.HelperCommon
|
||||
git *commands.GitCommand
|
||||
contexts *context.ContextTree
|
||||
refHelper *RefsHelper
|
||||
model *types.Model
|
||||
setCommitMessage func(message string)
|
||||
commitsHelper *CommitsHelper
|
||||
gpgHelper *GpgHelper
|
||||
c *HelperCommon
|
||||
refHelper *RefsHelper
|
||||
commitsHelper *CommitsHelper
|
||||
gpgHelper *GpgHelper
|
||||
}
|
||||
|
||||
func NewWorkingTreeHelper(
|
||||
c *types.HelperCommon,
|
||||
git *commands.GitCommand,
|
||||
contexts *context.ContextTree,
|
||||
c *HelperCommon,
|
||||
refHelper *RefsHelper,
|
||||
model *types.Model,
|
||||
setCommitMessage func(message string),
|
||||
commitsHelper *CommitsHelper,
|
||||
gpgHelper *GpgHelper,
|
||||
) *WorkingTreeHelper {
|
||||
return &WorkingTreeHelper{
|
||||
c: c,
|
||||
git: git,
|
||||
contexts: contexts,
|
||||
refHelper: refHelper,
|
||||
model: model,
|
||||
setCommitMessage: setCommitMessage,
|
||||
commitsHelper: commitsHelper,
|
||||
gpgHelper: gpgHelper,
|
||||
c: c,
|
||||
refHelper: refHelper,
|
||||
commitsHelper: commitsHelper,
|
||||
gpgHelper: gpgHelper,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *WorkingTreeHelper) AnyStagedFiles() bool {
|
||||
for _, file := range self.model.Files {
|
||||
for _, file := range self.c.Model().Files {
|
||||
if file.HasStagedChanges {
|
||||
return true
|
||||
}
|
||||
@ -62,7 +49,7 @@ func (self *WorkingTreeHelper) AnyStagedFiles() bool {
|
||||
}
|
||||
|
||||
func (self *WorkingTreeHelper) AnyTrackedFiles() bool {
|
||||
for _, file := range self.model.Files {
|
||||
for _, file := range self.c.Model().Files {
|
||||
if file.Tracked {
|
||||
return true
|
||||
}
|
||||
@ -75,7 +62,7 @@ func (self *WorkingTreeHelper) IsWorkingTreeDirty() bool {
|
||||
}
|
||||
|
||||
func (self *WorkingTreeHelper) FileForSubmodule(submodule *models.SubmoduleConfig) *models.File {
|
||||
for _, file := range self.model.Files {
|
||||
for _, file := range self.c.Model().Files {
|
||||
if file.IsSubmodule([]*models.SubmoduleConfig{submodule}) {
|
||||
return file
|
||||
}
|
||||
@ -91,7 +78,7 @@ func (self *WorkingTreeHelper) OpenMergeTool() error {
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.OpenMergeTool)
|
||||
return self.c.RunSubprocessAndRefresh(
|
||||
self.git.WorkingTree.OpenMergeToolCmdObj(),
|
||||
self.c.Git().WorkingTree.OpenMergeToolCmdObj(),
|
||||
)
|
||||
},
|
||||
})
|
||||
@ -102,7 +89,7 @@ func (self *WorkingTreeHelper) HandleCommitPressWithMessage(initialMessage strin
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if len(self.model.Files) == 0 {
|
||||
if len(self.c.Model().Files) == 0 {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
|
||||
}
|
||||
|
||||
@ -122,7 +109,7 @@ func (self *WorkingTreeHelper) HandleCommitPressWithMessage(initialMessage strin
|
||||
}
|
||||
|
||||
func (self *WorkingTreeHelper) handleCommit(message string) error {
|
||||
cmdObj := self.git.Commit.CommitCmdObj(message)
|
||||
cmdObj := self.c.Git().Commit.CommitCmdObj(message)
|
||||
self.c.LogAction(self.c.Tr.Actions.Commit)
|
||||
_ = self.commitsHelper.PopCommitMessageContexts()
|
||||
return self.gpgHelper.WithGpgHandling(cmdObj, self.c.Tr.CommittingStatus, func() error {
|
||||
@ -134,7 +121,7 @@ func (self *WorkingTreeHelper) handleCommit(message string) error {
|
||||
// HandleCommitEditorPress - handle when the user wants to commit changes via
|
||||
// their editor rather than via the popup panel
|
||||
func (self *WorkingTreeHelper) HandleCommitEditorPress() error {
|
||||
if len(self.model.Files) == 0 {
|
||||
if len(self.c.Model().Files) == 0 {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
|
||||
}
|
||||
|
||||
@ -144,7 +131,7 @@ func (self *WorkingTreeHelper) HandleCommitEditorPress() error {
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.Commit)
|
||||
return self.c.RunSubprocessAndRefresh(
|
||||
self.git.Commit.CommitEditorCmdObj(),
|
||||
self.c.Git().Commit.CommitEditorCmdObj(),
|
||||
)
|
||||
}
|
||||
|
||||
@ -158,7 +145,7 @@ func (self *WorkingTreeHelper) HandleWIPCommitPress() error {
|
||||
}
|
||||
|
||||
func (self *WorkingTreeHelper) HandleCommitPress() error {
|
||||
message := self.contexts.CommitMessage.GetPreservedMessage()
|
||||
message := self.c.Contexts().CommitMessage.GetPreservedMessage()
|
||||
|
||||
if message != "" {
|
||||
commitPrefixConfig := self.commitPrefixConfigForRepo()
|
||||
@ -183,7 +170,7 @@ func (self *WorkingTreeHelper) PromptToStageAllAndRetry(retry func() error) erro
|
||||
Prompt: self.c.Tr.NoFilesStagedPrompt,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.StageAllFiles)
|
||||
if err := self.git.WorkingTree.StageAll(); err != nil {
|
||||
if err := self.c.Git().WorkingTree.StageAll(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.syncRefresh(); err != nil {
|
||||
@ -204,7 +191,7 @@ func (self *WorkingTreeHelper) prepareFilesForCommit() error {
|
||||
noStagedFiles := !self.AnyStagedFiles()
|
||||
if noStagedFiles && self.c.UserConfig.Gui.SkipNoStagedFilesWarning {
|
||||
self.c.LogAction(self.c.Tr.Actions.StageAllFiles)
|
||||
err := self.git.WorkingTree.StageAll()
|
||||
err := self.c.Git().WorkingTree.StageAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
53
pkg/gui/controllers/jump_to_side_window_controller.go
Normal file
53
pkg/gui/controllers/jump_to_side_window_controller.go
Normal file
@ -0,0 +1,53 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type JumpToSideWindowController struct {
|
||||
baseController
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func NewJumpToSideWindowController(
|
||||
common *ControllerCommon,
|
||||
) *JumpToSideWindowController {
|
||||
return &JumpToSideWindowController{
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *JumpToSideWindowController) Context() types.Context {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *JumpToSideWindowController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
windows := self.c.Helpers().Window.SideWindows()
|
||||
|
||||
if len(opts.Config.Universal.JumpToBlock) != len(windows) {
|
||||
log.Fatal("Jump to block keybindings cannot be set. Exactly 5 keybindings must be supplied.")
|
||||
}
|
||||
|
||||
return lo.Map(windows, func(window string, index int) *types.Binding {
|
||||
return &types.Binding{
|
||||
ViewName: "",
|
||||
// by default the keys are 1, 2, 3, etc
|
||||
Key: opts.GetKey(opts.Config.Universal.JumpToBlock[index]),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.goToSideWindow(window),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (self *JumpToSideWindowController) goToSideWindow(window string) func() error {
|
||||
return func() error {
|
||||
context := self.c.Helpers().Window.GetContextForWindow(window)
|
||||
|
||||
return self.c.PushContext(context)
|
||||
}
|
||||
}
|
@ -6,10 +6,10 @@ import (
|
||||
)
|
||||
|
||||
type ListControllerFactory struct {
|
||||
c *types.HelperCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func NewListControllerFactory(c *types.HelperCommon) *ListControllerFactory {
|
||||
func NewListControllerFactory(c *ControllerCommon) *ListControllerFactory {
|
||||
return &ListControllerFactory{
|
||||
c: c,
|
||||
}
|
||||
@ -25,7 +25,7 @@ func (self *ListControllerFactory) Create(context types.IListContext) *ListContr
|
||||
|
||||
type ListController struct {
|
||||
baseController
|
||||
c *types.HelperCommon
|
||||
c *ControllerCommon
|
||||
|
||||
context types.IListContext
|
||||
}
|
||||
|
@ -13,26 +13,30 @@ import (
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// after selecting the 200th commit, we'll load in all the rest
|
||||
const COMMIT_THRESHOLD = 200
|
||||
|
||||
type (
|
||||
PullFilesFn func() error
|
||||
)
|
||||
|
||||
type LocalCommitsController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
|
||||
pullFiles PullFilesFn
|
||||
}
|
||||
|
||||
var _ types.IController = &LocalCommitsController{}
|
||||
|
||||
func NewLocalCommitsController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
pullFiles PullFilesFn,
|
||||
) *LocalCommitsController {
|
||||
return &LocalCommitsController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
pullFiles: pullFiles,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
pullFiles: pullFiles,
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,8 +154,52 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.c.Helpers().Diff.WithDiffModeCheck(func() error {
|
||||
var task types.UpdateTask
|
||||
commit := self.context().GetSelected()
|
||||
if commit == nil {
|
||||
task = types.NewRenderStringTask(self.c.Tr.NoCommitsThisBranch)
|
||||
} else if commit.Action == todo.UpdateRef {
|
||||
task = types.NewRenderStringTask(
|
||||
utils.ResolvePlaceholderString(
|
||||
self.c.Tr.UpdateRefHere,
|
||||
map[string]string{
|
||||
"ref": commit.Name,
|
||||
}))
|
||||
} else {
|
||||
cmdObj := self.c.Git().Commit.ShowCmdObj(commit.Sha, self.c.Modes().Filtering.GetPath(), self.c.State().GetIgnoreWhitespaceInDiffView())
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Patch",
|
||||
Task: task,
|
||||
},
|
||||
Secondary: secondaryPatchPanelUpdateOpts(self.c),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func secondaryPatchPanelUpdateOpts(c *ControllerCommon) *types.ViewUpdateOpts {
|
||||
if c.Git().Patch.PatchBuilder.Active() {
|
||||
patch := c.Git().Patch.PatchBuilder.RenderAggregatedPatch(false)
|
||||
|
||||
return &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(patch),
|
||||
Title: c.Tr.CustomPatch,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) squashDown(commit *models.Commit) error {
|
||||
if self.context().GetSelectedLineIdx() >= len(self.model.Commits)-1 {
|
||||
if self.context().GetSelectedLineIdx() >= len(self.c.Model().Commits)-1 {
|
||||
return self.c.ErrorMsg(self.c.Tr.CannotSquashOrFixupFirstCommit)
|
||||
}
|
||||
|
||||
@ -176,7 +224,7 @@ func (self *LocalCommitsController) squashDown(commit *models.Commit) error {
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) fixup(commit *models.Commit) error {
|
||||
if self.context().GetSelectedLineIdx() >= len(self.model.Commits)-1 {
|
||||
if self.context().GetSelectedLineIdx() >= len(self.c.Model().Commits)-1 {
|
||||
return self.c.ErrorMsg(self.c.Tr.CannotSquashOrFixupFirstCommit)
|
||||
}
|
||||
|
||||
@ -209,12 +257,12 @@ func (self *LocalCommitsController) reword(commit *models.Commit) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
commitMessage, err := self.git.Commit.GetCommitMessage(commit.Sha)
|
||||
commitMessage, err := self.c.Git().Commit.GetCommitMessage(commit.Sha)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.helpers.Commits.OpenCommitMessagePanel(
|
||||
return self.c.Helpers().Commits.OpenCommitMessagePanel(
|
||||
&helpers.OpenCommitMessagePanelOpts{
|
||||
CommitIndex: self.context().GetSelectedLineIdx(),
|
||||
InitialMessage: commitMessage,
|
||||
@ -226,12 +274,12 @@ func (self *LocalCommitsController) reword(commit *models.Commit) error {
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) handleReword(message string) error {
|
||||
err := self.git.Rebase.RewordCommit(self.model.Commits, self.contexts.LocalCommits.GetSelectedLineIdx(), message)
|
||||
err := self.c.Git().Rebase.RewordCommit(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), message)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
self.helpers.Commits.OnCommitSuccess()
|
||||
_ = self.helpers.Commits.PopCommitMessageContexts()
|
||||
self.c.Helpers().Commits.OnCommitSuccess()
|
||||
_ = self.c.Helpers().Commits.PopCommitMessageContexts()
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
}
|
||||
|
||||
@ -239,11 +287,11 @@ func (self *LocalCommitsController) doRewordEditor() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.RewordCommit)
|
||||
|
||||
if self.isHeadCommit() {
|
||||
return self.c.RunSubprocessAndRefresh(self.os.Cmd.New("git commit --allow-empty --amend --only"))
|
||||
return self.c.RunSubprocessAndRefresh(self.c.OS().Cmd.New("git commit --allow-empty --amend --only"))
|
||||
}
|
||||
|
||||
subProcess, err := self.git.Rebase.RewordCommitInEditor(
|
||||
self.model.Commits, self.context().GetSelectedLineIdx(),
|
||||
subProcess, err := self.c.Git().Rebase.RewordCommitInEditor(
|
||||
self.c.Model().Commits, self.context().GetSelectedLineIdx(),
|
||||
)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
@ -307,8 +355,8 @@ func (self *LocalCommitsController) edit(commit *models.Commit) error {
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.EditCommit)
|
||||
err := self.git.Rebase.InteractiveRebaseBreakAfter(self.model.Commits, self.context().GetSelectedLineIdx())
|
||||
return self.helpers.MergeAndRebase.CheckMergeOrRebase(err)
|
||||
err := self.c.Git().Rebase.InteractiveRebaseBreakAfter(self.c.Model().Commits, self.context().GetSelectedLineIdx())
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -327,8 +375,8 @@ func (self *LocalCommitsController) pick(commit *models.Commit) error {
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) interactiveRebase(action string) error {
|
||||
err := self.git.Rebase.InteractiveRebase(self.model.Commits, self.context().GetSelectedLineIdx(), action)
|
||||
return self.helpers.MergeAndRebase.CheckMergeOrRebase(err)
|
||||
err := self.c.Git().Rebase.InteractiveRebase(self.c.Model().Commits, self.context().GetSelectedLineIdx(), action)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
}
|
||||
|
||||
// handleMidRebaseCommand sees if the selected commit is in fact a rebasing
|
||||
@ -336,7 +384,7 @@ func (self *LocalCommitsController) interactiveRebase(action string) error {
|
||||
// begin a rebase. It then updates the todo file with that action
|
||||
func (self *LocalCommitsController) handleMidRebaseCommand(action todo.TodoCommand, commit *models.Commit) (bool, error) {
|
||||
if !commit.IsTODO() {
|
||||
if self.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
// If we are in a rebase, the only action that is allowed for
|
||||
// non-todo commits is rewording the current head commit
|
||||
if !(action == todo.Reword && self.isHeadCommit()) {
|
||||
@ -365,7 +413,7 @@ func (self *LocalCommitsController) handleMidRebaseCommand(action todo.TodoComma
|
||||
false,
|
||||
)
|
||||
|
||||
if err := self.git.Rebase.EditRebaseTodo(commit, action); err != nil {
|
||||
if err := self.c.Git().Rebase.EditRebaseTodo(commit, action); err != nil {
|
||||
return false, self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -376,7 +424,7 @@ func (self *LocalCommitsController) handleMidRebaseCommand(action todo.TodoComma
|
||||
|
||||
func (self *LocalCommitsController) moveDown(commit *models.Commit) error {
|
||||
index := self.context().GetSelectedLineIdx()
|
||||
commits := self.model.Commits
|
||||
commits := self.c.Model().Commits
|
||||
|
||||
// can't move past the initial commit
|
||||
if index >= len(commits)-1 {
|
||||
@ -393,7 +441,7 @@ func (self *LocalCommitsController) moveDown(commit *models.Commit) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.MoveCommitDown)
|
||||
self.c.LogCommand(fmt.Sprintf("Moving commit %s down", commit.ShortSha()), false)
|
||||
|
||||
if err := self.git.Rebase.MoveTodoDown(commit); err != nil {
|
||||
if err := self.c.Git().Rebase.MoveTodoDown(commit); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
self.context().MoveSelectedLine(1)
|
||||
@ -402,17 +450,17 @@ func (self *LocalCommitsController) moveDown(commit *models.Commit) error {
|
||||
})
|
||||
}
|
||||
|
||||
if self.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.MoveCommitDown)
|
||||
err := self.git.Rebase.MoveCommitDown(self.model.Commits, index)
|
||||
err := self.c.Git().Rebase.MoveCommitDown(self.c.Model().Commits, index)
|
||||
if err == nil {
|
||||
self.context().MoveSelectedLine(1)
|
||||
}
|
||||
return self.helpers.MergeAndRebase.CheckMergeOrRebase(err)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -431,7 +479,7 @@ func (self *LocalCommitsController) moveUp(commit *models.Commit) error {
|
||||
false,
|
||||
)
|
||||
|
||||
if err := self.git.Rebase.MoveTodoUp(self.model.Commits[index]); err != nil {
|
||||
if err := self.c.Git().Rebase.MoveTodoUp(self.c.Model().Commits[index]); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
self.context().MoveSelectedLine(-1)
|
||||
@ -440,29 +488,29 @@ func (self *LocalCommitsController) moveUp(commit *models.Commit) error {
|
||||
})
|
||||
}
|
||||
|
||||
if self.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.MoveCommitUp)
|
||||
err := self.git.Rebase.MoveCommitDown(self.model.Commits, index-1)
|
||||
err := self.c.Git().Rebase.MoveCommitDown(self.c.Model().Commits, index-1)
|
||||
if err == nil {
|
||||
self.context().MoveSelectedLine(-1)
|
||||
}
|
||||
return self.helpers.MergeAndRebase.CheckMergeOrRebase(err)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
|
||||
if self.isHeadCommit() {
|
||||
if err := self.helpers.AmendHelper.AmendHead(); err != nil {
|
||||
if err := self.c.Helpers().AmendHelper.AmendHead(); err != nil {
|
||||
return err
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
}
|
||||
|
||||
if self.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing)
|
||||
}
|
||||
|
||||
@ -472,15 +520,15 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.AmendCommit)
|
||||
err := self.git.Rebase.AmendTo(commit)
|
||||
return self.helpers.MergeAndRebase.CheckMergeOrRebase(err)
|
||||
err := self.c.Git().Rebase.AmendTo(commit)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) amendAttribute(commit *models.Commit) error {
|
||||
if self.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE && !self.isHeadCommit() {
|
||||
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE && !self.isHeadCommit() {
|
||||
return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing)
|
||||
}
|
||||
|
||||
@ -506,7 +554,7 @@ func (self *LocalCommitsController) amendAttribute(commit *models.Commit) error
|
||||
func (self *LocalCommitsController) resetAuthor() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ResetCommitAuthor)
|
||||
if err := self.git.Rebase.ResetCommitAuthor(self.model.Commits, self.context().GetSelectedLineIdx()); err != nil {
|
||||
if err := self.c.Git().Rebase.ResetCommitAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -517,11 +565,11 @@ func (self *LocalCommitsController) resetAuthor() error {
|
||||
func (self *LocalCommitsController) setAuthor() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.SetAuthorPromptTitle,
|
||||
FindSuggestionsFunc: self.helpers.Suggestions.GetAuthorsSuggestionsFunc(),
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(),
|
||||
HandleConfirm: func(value string) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SetCommitAuthor)
|
||||
if err := self.git.Rebase.SetCommitAuthor(self.model.Commits, self.context().GetSelectedLineIdx(), value); err != nil {
|
||||
if err := self.c.Git().Rebase.SetCommitAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx(), value); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -544,7 +592,7 @@ func (self *LocalCommitsController) revert(commit *models.Commit) error {
|
||||
}),
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.RevertCommit)
|
||||
if err := self.git.Commit.Revert(commit.Sha); err != nil {
|
||||
if err := self.c.Git().Commit.Revert(commit.Sha); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return self.afterRevertCommit()
|
||||
@ -557,7 +605,7 @@ func (self *LocalCommitsController) createRevertMergeCommitMenu(commit *models.C
|
||||
menuItems := make([]*types.MenuItem, len(commit.Parents))
|
||||
for i, parentSha := range commit.Parents {
|
||||
i := i
|
||||
message, err := self.git.Commit.GetCommitMessageFirstLine(parentSha)
|
||||
message, err := self.c.Git().Commit.GetCommitMessageFirstLine(parentSha)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
@ -567,7 +615,7 @@ func (self *LocalCommitsController) createRevertMergeCommitMenu(commit *models.C
|
||||
OnPress: func() error {
|
||||
parentNumber := i + 1
|
||||
self.c.LogAction(self.c.Tr.Actions.RevertCommit)
|
||||
if err := self.git.Commit.RevertMerge(commit.Sha, parentNumber); err != nil {
|
||||
if err := self.c.Git().Commit.RevertMerge(commit.Sha, parentNumber); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return self.afterRevertCommit()
|
||||
@ -598,7 +646,7 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CreateFixupCommit)
|
||||
if err := self.git.Commit.CreateFixupCommit(commit.Sha); err != nil {
|
||||
if err := self.c.Git().Commit.CreateFixupCommit(commit.Sha); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@ -619,15 +667,15 @@ func (self *LocalCommitsController) squashAllAboveFixupCommits(commit *models.Co
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits)
|
||||
err := self.git.Rebase.SquashAllAboveFixupCommits(commit)
|
||||
return self.helpers.MergeAndRebase.CheckMergeOrRebase(err)
|
||||
err := self.c.Git().Rebase.SquashAllAboveFixupCommits(commit)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) createTag(commit *models.Commit) error {
|
||||
return self.helpers.Tags.CreateTagMenu(commit.Sha, func() {})
|
||||
return self.c.Helpers().Tags.CreateTagMenu(commit.Sha, func() {})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) openSearch() error {
|
||||
@ -759,20 +807,36 @@ func (self *LocalCommitsController) checkSelected(callback func(*models.Commit)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
return func(types.OnFocusOpts) error {
|
||||
context := self.context()
|
||||
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
|
||||
context.SetLimitCommits(false)
|
||||
go utils.Safe(func() {
|
||||
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) context() *context.LocalCommitsContext {
|
||||
return self.contexts.LocalCommits
|
||||
return self.c.Contexts().LocalCommits
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) paste() error {
|
||||
return self.helpers.CherryPick.Paste()
|
||||
return self.c.Helpers().CherryPick.Paste()
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) isHeadCommit() bool {
|
||||
return models.IsHeadCommit(self.model.Commits, self.context().GetSelectedLineIdx())
|
||||
return models.IsHeadCommit(self.c.Model().Commits, self.context().GetSelectedLineIdx())
|
||||
}
|
||||
|
||||
func isChangeOfRebaseTodoAllowed(action todo.TodoCommand) bool {
|
||||
|
@ -7,17 +7,17 @@ import (
|
||||
|
||||
type MenuController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &MenuController{}
|
||||
|
||||
func NewMenuController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *MenuController {
|
||||
return &MenuController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,12 +28,16 @@ func (self *MenuController) GetKeybindings(opts types.KeybindingsOpts) []*types.
|
||||
Handler: self.press,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Confirm),
|
||||
Handler: self.press,
|
||||
Key: opts.GetKey(opts.Config.Universal.Confirm),
|
||||
Handler: self.press,
|
||||
Description: self.c.Tr.LcExecute,
|
||||
Display: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: self.close,
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: self.close,
|
||||
Description: self.c.Tr.LcClose,
|
||||
Display: true,
|
||||
},
|
||||
}
|
||||
|
||||
@ -44,6 +48,14 @@ func (self *MenuController) GetOnClick() func() error {
|
||||
return self.press
|
||||
}
|
||||
|
||||
func (self *MenuController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
return func(types.OnFocusOpts) error {
|
||||
selectedMenuItem := self.context().GetSelected()
|
||||
self.c.Views().Tooltip.SetContent(selectedMenuItem.Tooltip)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MenuController) press() error {
|
||||
return self.context().OnMenuPress(self.context().GetSelected())
|
||||
}
|
||||
@ -57,5 +69,5 @@ func (self *MenuController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *MenuController) context() *context.MenuContext {
|
||||
return self.contexts.Menu
|
||||
return self.c.Contexts().Menu
|
||||
}
|
||||
|
@ -11,17 +11,17 @@ import (
|
||||
|
||||
type MergeConflictsController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &MergeConflictsController{}
|
||||
|
||||
func NewMergeConflictsController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *MergeConflictsController {
|
||||
return &MergeConflictsController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,21 +41,25 @@ func (self *MergeConflictsController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevBlock),
|
||||
Handler: self.withRenderAndFocus(self.PrevConflict),
|
||||
Description: self.c.Tr.PrevConflict,
|
||||
Display: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.NextBlock),
|
||||
Handler: self.withRenderAndFocus(self.NextConflict),
|
||||
Description: self.c.Tr.NextConflict,
|
||||
Display: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevItem),
|
||||
Handler: self.withRenderAndFocus(self.PrevConflictHunk),
|
||||
Description: self.c.Tr.SelectPrevHunk,
|
||||
Display: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.NextItem),
|
||||
Handler: self.withRenderAndFocus(self.NextConflictHunk),
|
||||
Description: self.c.Tr.SelectNextHunk,
|
||||
Display: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevBlockAlt),
|
||||
@ -89,21 +93,24 @@ func (self *MergeConflictsController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
Key: opts.GetKey(opts.Config.Universal.Undo),
|
||||
Handler: self.withRenderAndFocus(self.HandleUndo),
|
||||
Description: self.c.Tr.LcUndo,
|
||||
Display: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
|
||||
Handler: self.helpers.WorkingTree.OpenMergeTool,
|
||||
Handler: self.c.Helpers().WorkingTree.OpenMergeTool,
|
||||
Description: self.c.Tr.LcOpenMergeTool,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Select),
|
||||
Handler: self.withRenderAndFocus(self.HandlePickHunk),
|
||||
Description: self.c.Tr.PickHunk,
|
||||
Display: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Main.PickBothHunks),
|
||||
Handler: self.withRenderAndFocus(self.HandlePickAllHunks),
|
||||
Description: self.c.Tr.PickAllHunks,
|
||||
Display: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
@ -134,6 +141,24 @@ func (self *MergeConflictsController) GetMouseKeybindings(opts types.Keybindings
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
return func(types.OnFocusOpts) error {
|
||||
self.c.Views().MergeConflicts.Wrap = false
|
||||
|
||||
return self.c.Helpers().MergeConflicts.Render(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return func(types.OnFocusLostOpts) error {
|
||||
self.context().SetUserScrolling(false)
|
||||
self.context().GetState().ResetConflictSelection()
|
||||
self.c.Views().MergeConflicts.Wrap = true
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) HandleScrollUp() error {
|
||||
self.context().SetUserScrolling(true)
|
||||
self.context().GetViewTrait().ScrollUp(self.c.UserConfig.Gui.ScrollHeight)
|
||||
@ -153,7 +178,7 @@ func (self *MergeConflictsController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) context() *context.MergeConflictsContext {
|
||||
return self.contexts.MergeConflicts
|
||||
return self.c.Contexts().MergeConflicts
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) Escape() error {
|
||||
@ -162,11 +187,11 @@ func (self *MergeConflictsController) Escape() error {
|
||||
|
||||
func (self *MergeConflictsController) HandleEditFile() error {
|
||||
lineNumber := self.context().GetState().GetSelectedLine()
|
||||
return self.helpers.Files.EditFileAtLine(self.context().GetState().GetPath(), lineNumber)
|
||||
return self.c.Helpers().Files.EditFileAtLine(self.context().GetState().GetPath(), lineNumber)
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) HandleOpenFile() error {
|
||||
return self.helpers.Files.OpenFile(self.context().GetState().GetPath())
|
||||
return self.c.Helpers().Files.OpenFile(self.context().GetState().GetPath())
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) HandleScrollLeft() error {
|
||||
|
@ -1,23 +1,52 @@
|
||||
package gui
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func (gui *Gui) getBindings(context types.Context) []*types.Binding {
|
||||
type OptionsMenuAction struct {
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func (self *OptionsMenuAction) Call() error {
|
||||
ctx := self.c.CurrentContext()
|
||||
// Don't show menu while displaying popup.
|
||||
if ctx.GetKind() == types.PERSISTENT_POPUP || ctx.GetKind() == types.TEMPORARY_POPUP {
|
||||
return nil
|
||||
}
|
||||
|
||||
bindings := self.getBindings(ctx)
|
||||
|
||||
menuItems := slices.Map(bindings, func(binding *types.Binding) *types.MenuItem {
|
||||
return &types.MenuItem{
|
||||
OpensMenu: binding.OpensMenu,
|
||||
Label: binding.Description,
|
||||
OnPress: func() error {
|
||||
if binding.Handler == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return binding.Handler()
|
||||
},
|
||||
Key: binding.Key,
|
||||
Tooltip: binding.Tooltip,
|
||||
}
|
||||
})
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.MenuTitle,
|
||||
Items: menuItems,
|
||||
HideCancel: true,
|
||||
})
|
||||
}
|
||||
|
||||
func (self *OptionsMenuAction) getBindings(context types.Context) []*types.Binding {
|
||||
var bindingsGlobal, bindingsPanel, bindingsNavigation []*types.Binding
|
||||
|
||||
bindings, _ := gui.GetInitialKeybindings()
|
||||
customBindings, err := gui.CustomCommandsClient.GetCustomCommandKeybindings()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
bindings = append(customBindings, bindings...)
|
||||
bindings, _ := self.c.GetInitialKeybindingsWithCustomCommands()
|
||||
|
||||
for _, binding := range bindings {
|
||||
if keybindings.LabelFromKey(binding.Key) != "" && binding.Description != "" {
|
||||
@ -48,35 +77,3 @@ func uniqueBindings(bindings []*types.Binding) []*types.Binding {
|
||||
return binding.Description
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateOptionsMenu() error {
|
||||
ctx := gui.currentContext()
|
||||
// Don't show menu while displaying popup.
|
||||
if ctx.GetKind() == types.PERSISTENT_POPUP || ctx.GetKind() == types.TEMPORARY_POPUP {
|
||||
return nil
|
||||
}
|
||||
|
||||
bindings := gui.getBindings(ctx)
|
||||
|
||||
menuItems := slices.Map(bindings, func(binding *types.Binding) *types.MenuItem {
|
||||
return &types.MenuItem{
|
||||
OpensMenu: binding.OpensMenu,
|
||||
Label: binding.Description,
|
||||
OnPress: func() error {
|
||||
if binding.Handler == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return binding.Handler()
|
||||
},
|
||||
Key: binding.Key,
|
||||
Tooltip: binding.Tooltip,
|
||||
}
|
||||
})
|
||||
|
||||
return gui.c.Menu(types.CreateMenuOptions{
|
||||
Title: gui.c.Tr.MenuTitle,
|
||||
Items: menuItems,
|
||||
HideCancel: true,
|
||||
})
|
||||
}
|
@ -8,17 +8,17 @@ import (
|
||||
|
||||
type PatchBuildingController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &PatchBuildingController{}
|
||||
|
||||
func NewPatchBuildingController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *PatchBuildingController {
|
||||
return &PatchBuildingController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,42 +48,63 @@ func (self *PatchBuildingController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) Context() types.Context {
|
||||
return self.contexts.CustomPatchBuilder
|
||||
return self.c.Contexts().CustomPatchBuilder
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) context() types.IPatchExplorerContext {
|
||||
return self.contexts.CustomPatchBuilder
|
||||
return self.c.Contexts().CustomPatchBuilder
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
|
||||
return []*gocui.ViewMouseBinding{}
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
return func(opts types.OnFocusOpts) error {
|
||||
// no need to change wrap on the secondary view because it can't be interacted with
|
||||
self.c.Views().PatchBuilding.Wrap = false
|
||||
|
||||
return self.c.Helpers().PatchBuilding.RefreshPatchBuildingPanel(opts)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return func(opts types.OnFocusLostOpts) error {
|
||||
self.c.Views().PatchBuilding.Wrap = true
|
||||
|
||||
if self.c.Git().Patch.PatchBuilder.IsEmpty() {
|
||||
self.c.Git().Patch.PatchBuilder.Reset()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) OpenFile() error {
|
||||
self.context().GetMutex().Lock()
|
||||
defer self.context().GetMutex().Unlock()
|
||||
|
||||
path := self.contexts.CommitFiles.GetSelectedPath()
|
||||
path := self.c.Contexts().CommitFiles.GetSelectedPath()
|
||||
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.helpers.Files.OpenFile(path)
|
||||
return self.c.Helpers().Files.OpenFile(path)
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) EditFile() error {
|
||||
self.context().GetMutex().Lock()
|
||||
defer self.context().GetMutex().Unlock()
|
||||
|
||||
path := self.contexts.CommitFiles.GetSelectedPath()
|
||||
path := self.c.Contexts().CommitFiles.GetSelectedPath()
|
||||
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
lineNumber := self.context().GetState().CurrentLineNumber()
|
||||
return self.helpers.Files.EditFileAtLine(path, lineNumber)
|
||||
return self.c.Helpers().Files.EditFileAtLine(path, lineNumber)
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) ToggleSelectionAndRefresh() error {
|
||||
@ -100,21 +121,21 @@ func (self *PatchBuildingController) toggleSelection() error {
|
||||
self.context().GetMutex().Lock()
|
||||
defer self.context().GetMutex().Unlock()
|
||||
|
||||
toggleFunc := self.git.Patch.PatchBuilder.AddFileLineRange
|
||||
filename := self.contexts.CommitFiles.GetSelectedPath()
|
||||
toggleFunc := self.c.Git().Patch.PatchBuilder.AddFileLineRange
|
||||
filename := self.c.Contexts().CommitFiles.GetSelectedPath()
|
||||
if filename == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
state := self.context().GetState()
|
||||
|
||||
includedLineIndices, err := self.git.Patch.PatchBuilder.GetFileIncLineIndices(filename)
|
||||
includedLineIndices, err := self.c.Git().Patch.PatchBuilder.GetFileIncLineIndices(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentLineIsStaged := lo.Contains(includedLineIndices, state.GetSelectedLineIdx())
|
||||
if currentLineIsStaged {
|
||||
toggleFunc = self.git.Patch.PatchBuilder.RemoveFileLineRange
|
||||
toggleFunc = self.c.Git().Patch.PatchBuilder.RemoveFileLineRange
|
||||
}
|
||||
|
||||
// add range of lines to those set for the file
|
||||
@ -133,5 +154,5 @@ func (self *PatchBuildingController) toggleSelection() error {
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) Escape() error {
|
||||
return self.helpers.PatchBuilding.Escape()
|
||||
return self.c.Helpers().PatchBuilding.Escape()
|
||||
}
|
||||
|
@ -6,26 +6,26 @@ import (
|
||||
)
|
||||
|
||||
type PatchExplorerControllerFactory struct {
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func NewPatchExplorerControllerFactory(c *controllerCommon) *PatchExplorerControllerFactory {
|
||||
func NewPatchExplorerControllerFactory(c *ControllerCommon) *PatchExplorerControllerFactory {
|
||||
return &PatchExplorerControllerFactory{
|
||||
controllerCommon: c,
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *PatchExplorerControllerFactory) Create(context types.IPatchExplorerContext) *PatchExplorerController {
|
||||
return &PatchExplorerController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: self.controllerCommon,
|
||||
context: context,
|
||||
baseController: baseController{},
|
||||
c: self.c,
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
type PatchExplorerController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
|
||||
context types.IPatchExplorerContext
|
||||
}
|
||||
@ -254,7 +254,7 @@ 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 {
|
||||
if err := self.c.OS().CopyToClipboard(selected); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
|
75
pkg/gui/controllers/quit_actions.go
Normal file
75
pkg/gui/controllers/quit_actions.go
Normal file
@ -0,0 +1,75 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type QuitActions struct {
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func (self *QuitActions) Quit() error {
|
||||
self.c.State().SetRetainOriginalDir(false)
|
||||
return self.quitAux()
|
||||
}
|
||||
|
||||
func (self *QuitActions) QuitWithoutChangingDirectory() error {
|
||||
self.c.State().SetRetainOriginalDir(true)
|
||||
return self.quitAux()
|
||||
}
|
||||
|
||||
func (self *QuitActions) quitAux() error {
|
||||
if self.c.State().GetUpdating() {
|
||||
return self.confirmQuitDuringUpdate()
|
||||
}
|
||||
|
||||
if self.c.UserConfig.ConfirmOnQuit {
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: "",
|
||||
Prompt: self.c.Tr.ConfirmQuit,
|
||||
HandleConfirm: func() error {
|
||||
return gocui.ErrQuit
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func (self *QuitActions) confirmQuitDuringUpdate() error {
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.ConfirmQuitDuringUpdateTitle,
|
||||
Prompt: self.c.Tr.ConfirmQuitDuringUpdate,
|
||||
HandleConfirm: func() error {
|
||||
return gocui.ErrQuit
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *QuitActions) Escape() error {
|
||||
currentContext := self.c.CurrentContext()
|
||||
|
||||
parentContext, hasParent := currentContext.GetParentContext()
|
||||
if hasParent && currentContext != nil && parentContext != nil {
|
||||
// TODO: think about whether this should be marked as a return rather than adding to the stack
|
||||
return self.c.PushContext(parentContext)
|
||||
}
|
||||
|
||||
for _, mode := range self.c.Helpers().Mode.Statuses() {
|
||||
if mode.IsActive() {
|
||||
return mode.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
repoPathStack := self.c.State().GetRepoPathStack()
|
||||
if !repoPathStack.IsEmpty() {
|
||||
return self.c.Helpers().Repos.DispatchSwitchToRepo(repoPathStack.Pop(), true)
|
||||
}
|
||||
|
||||
if self.c.UserConfig.QuitOnTopLevelReturn {
|
||||
return self.Quit()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user