mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-03-21 21:47:32 +02:00
View filtering (#2680)
This commit is contained in:
commit
1a36cb9f3f
@ -36,6 +36,9 @@ gui:
|
||||
- bold
|
||||
inactiveBorderColor:
|
||||
- white
|
||||
searchingActiveBorderColor:
|
||||
- cyan
|
||||
- bold
|
||||
optionsTextColor:
|
||||
- blue
|
||||
selectedLineBgColor:
|
||||
|
@ -1,7 +1,8 @@
|
||||
# Documentation Overview
|
||||
# Documentation Overview
|
||||
|
||||
* [Configuration](./Config.md).
|
||||
* [Custom Commands](./Custom_Command_Keybindings.md)
|
||||
* [Custom Pagers](./Custom_Pagers.md)
|
||||
* [Keybindings](./keybindings)
|
||||
* [Undo/Redo](./Undoing.md)
|
||||
* [Searching/Filtering](./Searching.md)
|
||||
|
21
docs/Searching.md
Normal file
21
docs/Searching.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Searching/Filtering
|
||||
|
||||
## View searching/filtering
|
||||
|
||||
Depending on the currently focused view, hitting '/' will bring up a filter or search prompt. When filtering, the contents of the view will be filtered down to only those lines which match the query string. When searching, the contents of the view are not filtered, but matching lines are highlighted and you can iterate through matches with `n`/`N`.
|
||||
|
||||
We intend to support filtering for the files view soon, but at the moment it uses searching. We intend to continue using search for the commits view because you typically care about the commits that come before/after a matching commit.
|
||||
|
||||
If you would like both filtering and searching to be enabled on a given view, please raise an issue for this.
|
||||
|
||||
## Filtering files by status
|
||||
|
||||
You can filter the files view to only show staged/unstaged files by pressing `<c-b>` in the files view.
|
||||
|
||||
## Filtering commits by file path
|
||||
|
||||
You can filter the commits view to only show commits which contain changes to a given file path.
|
||||
|
||||
You can do this in a couple of ways:
|
||||
1) Start lazygit with the -f flag e.g. `lazygit -f my/path`
|
||||
2) From within lazygit, press `<c-s>` and then enter the path of the file you want to filter by
|
@ -36,8 +36,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>,</kbd>: Previous page
|
||||
<kbd>.</kbd>: Next page
|
||||
<kbd><</kbd>: Scroll to top
|
||||
<kbd>/</kbd>: Start search
|
||||
<kbd>></kbd>: Scroll to bottom
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
<kbd>H</kbd>: Scroll left
|
||||
<kbd>L</kbd>: Scroll right
|
||||
<kbd>]</kbd>: Next tab
|
||||
@ -56,6 +56,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>a</kbd>: Toggle all files included in patch
|
||||
<kbd><enter></kbd>: Enter file to add selected lines to the patch (or toggle directory collapsed)
|
||||
<kbd>`</kbd>: Toggle file tree view
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Commit summary
|
||||
@ -96,6 +97,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: Copy commit (cherry-pick)
|
||||
<kbd>C</kbd>: Copy commit range (cherry-pick)
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Confirmation panel
|
||||
@ -111,7 +113,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-o></kbd>: Copy the file name to the clipboard
|
||||
<kbd>d</kbd>: View 'discard changes' options
|
||||
<kbd><space></kbd>: Toggle staged
|
||||
<kbd><c-b></kbd>: Filter files (staged/unstaged)
|
||||
<kbd><c-b></kbd>: Filter files by status
|
||||
<kbd>c</kbd>: Commit changes
|
||||
<kbd>w</kbd>: Commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: Amend last commit
|
||||
@ -129,6 +131,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>`</kbd>: Toggle file tree view
|
||||
<kbd>M</kbd>: Open external merge tool (git mergetool)
|
||||
<kbd>f</kbd>: Fetch
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Local branches
|
||||
@ -152,6 +155,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>R</kbd>: Rename branch
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Main panel (merging)
|
||||
@ -190,6 +194,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: Edit file
|
||||
<kbd><space></kbd>: Add/Remove line(s) to patch
|
||||
<kbd><esc></kbd>: Exit custom patch builder
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Main panel (staging)
|
||||
@ -211,6 +216,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: Commit changes
|
||||
<kbd>w</kbd>: Commit changes without pre-commit hook
|
||||
<kbd>C</kbd>: Commit changes using git editor
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Menu
|
||||
@ -218,6 +224,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd><enter></kbd>: Execute
|
||||
<kbd><esc></kbd>: Close
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Reflog
|
||||
@ -233,6 +240,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: Copy commit range (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Remote branches
|
||||
@ -245,9 +253,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>r</kbd>: Rebase checked-out branch onto this branch
|
||||
<kbd>d</kbd>: Delete branch
|
||||
<kbd>u</kbd>: Set as upstream of checked-out branch
|
||||
<kbd><esc></kbd>: Return to remotes list
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Remotes
|
||||
@ -257,6 +265,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Add new remote
|
||||
<kbd>d</kbd>: Remove remote
|
||||
<kbd>e</kbd>: Edit remote
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Stash
|
||||
@ -268,6 +277,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: New branch
|
||||
<kbd>r</kbd>: Rename stash
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
@ -293,6 +303,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: Copy commit range (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Submodules
|
||||
@ -306,6 +317,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: Update submodule URL
|
||||
<kbd>i</kbd>: Initialize submodule
|
||||
<kbd>b</kbd>: View bulk submodule options
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Tags
|
||||
@ -317,4 +329,5 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Create tag
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
@ -36,8 +36,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>,</kbd>: 前のページ
|
||||
<kbd>.</kbd>: 次のページ
|
||||
<kbd><</kbd>: 最上部までスクロール
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
<kbd>></kbd>: 最下部までスクロール
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
<kbd>H</kbd>: 左スクロール
|
||||
<kbd>L</kbd>: 右スクロール
|
||||
<kbd>]</kbd>: 次のタブ
|
||||
@ -53,6 +53,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 新しいブランチを作成
|
||||
<kbd>r</kbd>: Stashを変更
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Sub-commits
|
||||
@ -68,6 +69,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: コミットを範囲コピー (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
</pre>
|
||||
|
||||
## コミット
|
||||
@ -101,6 +103,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: コミットをコピー (cherry-pick)
|
||||
<kbd>C</kbd>: コミットを範囲コピー (cherry-pick)
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
</pre>
|
||||
|
||||
## コミットファイル
|
||||
@ -115,6 +118,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>a</kbd>: Toggle all files included in patch
|
||||
<kbd><enter></kbd>: Enter file to add selected lines to the patch (or toggle directory collapsed)
|
||||
<kbd>`</kbd>: ファイルツリーの表示を切り替え
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
</pre>
|
||||
|
||||
## コミットメッセージ
|
||||
@ -135,6 +139,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: サブモジュールのURLを更新
|
||||
<kbd>i</kbd>: サブモジュールを初期化
|
||||
<kbd>b</kbd>: View bulk submodule options
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## ステータス
|
||||
@ -156,6 +161,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: タグを作成
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd><enter></kbd>: コミットを閲覧
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## ファイル
|
||||
@ -182,6 +188,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>`</kbd>: ファイルツリーの表示を切り替え
|
||||
<kbd>M</kbd>: Git mergetoolを開く
|
||||
<kbd>f</kbd>: Fetch
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
</pre>
|
||||
|
||||
## ブランチ
|
||||
@ -205,6 +212,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>R</kbd>: ブランチ名を変更
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd><enter></kbd>: コミットを閲覧
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## メインパネル (Merging)
|
||||
@ -243,6 +251,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: ファイルを編集
|
||||
<kbd><space></kbd>: 行をパッチに追加/削除
|
||||
<kbd><esc></kbd>: Exit custom patch builder
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
</pre>
|
||||
|
||||
## メインパネル (Staging)
|
||||
@ -264,6 +273,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: 変更をコミット
|
||||
<kbd>w</kbd>: pre-commitフックを実行せずに変更をコミット
|
||||
<kbd>C</kbd>: gitエディタを使用して変更をコミット
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
</pre>
|
||||
|
||||
## メニュー
|
||||
@ -271,6 +281,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 実行
|
||||
<kbd><esc></kbd>: 閉じる
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## リモート
|
||||
@ -280,6 +291,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: リモートを新規追加
|
||||
<kbd>d</kbd>: リモートを削除
|
||||
<kbd>e</kbd>: リモートを編集
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## リモートブランチ
|
||||
@ -292,9 +304,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>r</kbd>: Rebase checked-out branch onto this branch
|
||||
<kbd>d</kbd>: ブランチを削除
|
||||
<kbd>u</kbd>: Set as upstream of checked-out branch
|
||||
<kbd><esc></kbd>: リモート一覧に戻る
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd><enter></kbd>: コミットを閲覧
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 参照ログ
|
||||
@ -310,6 +322,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: コミットを範囲コピー (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: コミットを閲覧
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 確認パネル
|
||||
|
@ -36,8 +36,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>,</kbd>: 이전 페이지
|
||||
<kbd>.</kbd>: 다음 페이지
|
||||
<kbd><</kbd>: 맨 위로 스크롤
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
<kbd>></kbd>: 맨 아래로 스크롤
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
<kbd>H</kbd>: 우 스크롤
|
||||
<kbd>L</kbd>: 좌 스크롤
|
||||
<kbd>]</kbd>: 이전 탭
|
||||
@ -57,6 +57,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: 커밋을 범위로 복사 (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: 커밋 보기
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Stash
|
||||
@ -68,6 +69,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 새 브랜치 생성
|
||||
<kbd>r</kbd>: Rename stash
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Sub-commits
|
||||
@ -83,6 +85,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: 커밋을 범위로 복사 (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
</pre>
|
||||
|
||||
## 메뉴
|
||||
@ -90,6 +93,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 실행
|
||||
<kbd><esc></kbd>: 닫기
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 메인 패널 (Merging)
|
||||
@ -128,6 +132,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: 파일 편집
|
||||
<kbd><space></kbd>: Line(s)을 패치에 추가/삭제
|
||||
<kbd><esc></kbd>: Exit custom patch builder
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
</pre>
|
||||
|
||||
## 메인 패널 (Staging)
|
||||
@ -149,6 +154,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: 커밋 변경내용
|
||||
<kbd>w</kbd>: Commit changes without pre-commit hook
|
||||
<kbd>C</kbd>: Git 편집기를 사용하여 변경 내용을 커밋합니다.
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
</pre>
|
||||
|
||||
## 브랜치
|
||||
@ -172,6 +178,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>R</kbd>: 브랜치 이름 변경
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd><enter></kbd>: 커밋 보기
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 상태
|
||||
@ -195,6 +202,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: 서브모듈의 URL을 수정
|
||||
<kbd>i</kbd>: 서브모듈 초기화
|
||||
<kbd>b</kbd>: View bulk submodule options
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 원격
|
||||
@ -204,6 +212,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 새로운 Remote 추가
|
||||
<kbd>d</kbd>: Remote를 삭제
|
||||
<kbd>e</kbd>: Remote를 수정
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 원격 브랜치
|
||||
@ -216,9 +225,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>r</kbd>: 체크아웃된 브랜치를 이 브랜치에 리베이스
|
||||
<kbd>d</kbd>: 브랜치 삭제
|
||||
<kbd>u</kbd>: Set as upstream of checked-out branch
|
||||
<kbd><esc></kbd>: 원격목록으로 돌아가기
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd><enter></kbd>: 커밋 보기
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 커밋
|
||||
@ -252,6 +261,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: 커밋을 복사 (cherry-pick)
|
||||
<kbd>C</kbd>: 커밋을 범위로 복사 (cherry-pick)
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
</pre>
|
||||
|
||||
## 커밋 파일
|
||||
@ -266,6 +276,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>a</kbd>: Toggle all files included in patch
|
||||
<kbd><enter></kbd>: Enter file to add selected lines to the patch (or toggle directory collapsed)
|
||||
<kbd>`</kbd>: 파일 트리뷰로 전환
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
</pre>
|
||||
|
||||
## 커밋메시지
|
||||
@ -284,6 +295,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 태그를 생성
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd><enter></kbd>: 커밋 보기
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 파일
|
||||
@ -310,6 +322,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>`</kbd>: 파일 트리뷰로 전환
|
||||
<kbd>M</kbd>: Git mergetool를 열기
|
||||
<kbd>f</kbd>: Fetch
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
</pre>
|
||||
|
||||
## 확인 패널
|
||||
|
@ -36,8 +36,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>,</kbd>: Vorige pagina
|
||||
<kbd>.</kbd>: Volgende pagina
|
||||
<kbd><</kbd>: Scroll naar boven
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
<kbd>></kbd>: Scroll naar beneden
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
<kbd>H</kbd>: Scroll left
|
||||
<kbd>L</kbd>: Scroll right
|
||||
<kbd>]</kbd>: Volgende tabblad
|
||||
@ -50,7 +50,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-o></kbd>: Kopieer de bestandsnaam naar het klembord
|
||||
<kbd>d</kbd>: Bekijk 'veranderingen ongedaan maken' opties
|
||||
<kbd><space></kbd>: Toggle staged
|
||||
<kbd><c-b></kbd>: Filter files (staged/unstaged)
|
||||
<kbd><c-b></kbd>: Filter files by status
|
||||
<kbd>c</kbd>: Commit veranderingen
|
||||
<kbd>w</kbd>: Commit veranderingen zonder pre-commit hook
|
||||
<kbd>A</kbd>: Wijzig laatste commit
|
||||
@ -68,6 +68,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>`</kbd>: Toggle bestandsboom weergave
|
||||
<kbd>M</kbd>: Open external merge tool (git mergetool)
|
||||
<kbd>f</kbd>: Fetch
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
</pre>
|
||||
|
||||
## Bevestigingspaneel
|
||||
@ -98,6 +99,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>R</kbd>: Hernoem branch
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd><enter></kbd>: Bekijk commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Commit bericht
|
||||
@ -119,6 +121,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>a</kbd>: Toggle all files included in patch
|
||||
<kbd><enter></kbd>: Enter bestand om geselecteerde regels toe te voegen aan de patch
|
||||
<kbd>`</kbd>: Toggle bestandsboom weergave
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
</pre>
|
||||
|
||||
## Commits
|
||||
@ -152,6 +155,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: Kopieer commit (cherry-pick)
|
||||
<kbd>C</kbd>: Kopieer commit reeks (cherry-pick)
|
||||
<kbd><enter></kbd>: Bekijk gecommite bestanden
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
</pre>
|
||||
|
||||
## Menu
|
||||
@ -159,6 +163,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd><enter></kbd>: Uitvoeren
|
||||
<kbd><esc></kbd>: Sluiten
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Mergen
|
||||
@ -197,6 +202,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: Verander bestand
|
||||
<kbd><space></kbd>: Voeg toe/verwijder lijn(en) in patch
|
||||
<kbd><esc></kbd>: Sluit lijn-bij-lijn modus
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
</pre>
|
||||
|
||||
## Reflog
|
||||
@ -212,6 +218,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: Kopieer commit reeks (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (gekopieerde) commits selectie
|
||||
<kbd><enter></kbd>: Bekijk commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Remote branches
|
||||
@ -224,9 +231,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>r</kbd>: Rebase branch
|
||||
<kbd>d</kbd>: Verwijder branch
|
||||
<kbd>u</kbd>: Stel in als upstream van uitgecheckte branch
|
||||
<kbd><esc></kbd>: Ga terug naar remotes lijst
|
||||
<kbd>g</kbd>: Bekijk reset opties
|
||||
<kbd><enter></kbd>: Bekijk commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Remotes
|
||||
@ -236,6 +243,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Voeg een nieuwe remote toe
|
||||
<kbd>d</kbd>: Verwijder remote
|
||||
<kbd>e</kbd>: Wijzig remote
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Staging
|
||||
@ -257,6 +265,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: Commit veranderingen
|
||||
<kbd>w</kbd>: Commit veranderingen zonder pre-commit hook
|
||||
<kbd>C</kbd>: Commit veranderingen met de git editor
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
</pre>
|
||||
|
||||
## Stash
|
||||
@ -268,6 +277,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Nieuwe branch
|
||||
<kbd>r</kbd>: Rename stash
|
||||
<kbd><enter></kbd>: Bekijk gecommite bestanden
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
@ -293,6 +303,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: Kopieer commit reeks (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (gekopieerde) commits selectie
|
||||
<kbd><enter></kbd>: Bekijk gecommite bestanden
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
</pre>
|
||||
|
||||
## Submodules
|
||||
@ -306,6 +317,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: Update submodule URL
|
||||
<kbd>i</kbd>: Initialiseer submodule
|
||||
<kbd>b</kbd>: Bekijk bulk submodule opties
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Tags
|
||||
@ -317,4 +329,5 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Creëer tag
|
||||
<kbd>g</kbd>: Bekijk reset opties
|
||||
<kbd><enter></kbd>: Bekijk commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
@ -36,8 +36,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>,</kbd>: Previous page
|
||||
<kbd>.</kbd>: Next page
|
||||
<kbd><</kbd>: Scroll to top
|
||||
<kbd>/</kbd>: Start search
|
||||
<kbd>></kbd>: Scroll to bottom
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
<kbd>H</kbd>: Scroll left
|
||||
<kbd>L</kbd>: Scroll right
|
||||
<kbd>]</kbd>: Next tab
|
||||
@ -82,6 +82,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: Kopiuj commit (przebieranie)
|
||||
<kbd>C</kbd>: Kopiuj zakres commitów (przebieranie)
|
||||
<kbd><enter></kbd>: Przeglądaj pliki commita
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Confirmation panel
|
||||
@ -112,6 +113,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>R</kbd>: Rename branch
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Main panel (patch building)
|
||||
@ -127,6 +129,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: Edytuj plik
|
||||
<kbd><space></kbd>: Add/Remove line(s) to patch
|
||||
<kbd><esc></kbd>: Wyście z trybu "linia po linii"
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Menu
|
||||
@ -134,6 +137,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd><enter></kbd>: Wykonaj
|
||||
<kbd><esc></kbd>: Zamknij
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Pliki
|
||||
@ -142,7 +146,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-o></kbd>: Copy the file name to the clipboard
|
||||
<kbd>d</kbd>: Pokaż opcje porzucania zmian
|
||||
<kbd><space></kbd>: Przełącz stan poczekalni
|
||||
<kbd><c-b></kbd>: Filter files (staged/unstaged)
|
||||
<kbd><c-b></kbd>: Filter files by status
|
||||
<kbd>c</kbd>: Zatwierdź zmiany
|
||||
<kbd>w</kbd>: Zatwierdź zmiany bez skryptu pre-commit
|
||||
<kbd>A</kbd>: Zmień ostatni commit
|
||||
@ -160,6 +164,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>`</kbd>: Toggle file tree view
|
||||
<kbd>M</kbd>: Open external merge tool (git mergetool)
|
||||
<kbd>f</kbd>: Pobierz
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Pliki commita
|
||||
@ -174,6 +179,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>a</kbd>: Toggle all files included in patch
|
||||
<kbd><enter></kbd>: Enter file to add selected lines to the patch (or toggle directory collapsed)
|
||||
<kbd>`</kbd>: Toggle file tree view
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Poczekalnia
|
||||
@ -195,6 +201,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: Zatwierdź zmiany
|
||||
<kbd>w</kbd>: Zatwierdź zmiany bez skryptu pre-commit
|
||||
<kbd>C</kbd>: Zatwierdź zmiany używając edytora
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Reflog
|
||||
@ -210,6 +217,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: Kopiuj zakres commitów (przebieranie)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Remote branches
|
||||
@ -222,9 +230,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>r</kbd>: Zmiana bazy gałęzi
|
||||
<kbd>d</kbd>: Usuń gałąź
|
||||
<kbd>u</kbd>: Set as upstream of checked-out branch
|
||||
<kbd><esc></kbd>: Wróć do listy repozytoriów zdalnych
|
||||
<kbd>g</kbd>: Wyświetl opcje resetu
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Remotes
|
||||
@ -234,6 +242,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Add new remote
|
||||
<kbd>d</kbd>: Remove remote
|
||||
<kbd>e</kbd>: Edit remote
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Scalanie
|
||||
@ -261,6 +270,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Nowa gałąź
|
||||
<kbd>r</kbd>: Rename stash
|
||||
<kbd><enter></kbd>: Przeglądaj pliki commita
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
@ -286,6 +296,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: Kopiuj zakres commitów (przebieranie)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: Przeglądaj pliki commita
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Submodules
|
||||
@ -299,6 +310,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: Update submodule URL
|
||||
<kbd>i</kbd>: Initialize submodule
|
||||
<kbd>b</kbd>: View bulk submodule options
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Tags
|
||||
@ -310,6 +322,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Create tag
|
||||
<kbd>g</kbd>: Wyświetl opcje resetu
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Zwykłe
|
||||
|
@ -36,8 +36,8 @@ _Связки клавиш_
|
||||
<kbd>,</kbd>: Предыдущая страница
|
||||
<kbd>.</kbd>: Следующая страница
|
||||
<kbd><</kbd>: Пролистать наверх
|
||||
<kbd>/</kbd>: Найти
|
||||
<kbd>></kbd>: Прокрутить вниз
|
||||
<kbd>/</kbd>: Найти
|
||||
<kbd>H</kbd>: Прокрутить влево
|
||||
<kbd>L</kbd>: Прокрутить вправо
|
||||
<kbd>]</kbd>: Следующая вкладка
|
||||
@ -63,6 +63,7 @@ _Связки клавиш_
|
||||
<kbd>c</kbd>: Сохранить изменения
|
||||
<kbd>w</kbd>: Закоммитить изменения без предварительного хука коммита
|
||||
<kbd>C</kbd>: Сохранить изменения с помощью редактора git
|
||||
<kbd>/</kbd>: Найти
|
||||
</pre>
|
||||
|
||||
## Главная панель (Обычный)
|
||||
@ -101,6 +102,7 @@ _Связки клавиш_
|
||||
<kbd>e</kbd>: Редактировать файл
|
||||
<kbd><space></kbd>: Добавить/удалить строку(и) для патча
|
||||
<kbd><esc></kbd>: Выйти из сборщика пользовательских патчей
|
||||
<kbd>/</kbd>: Найти
|
||||
</pre>
|
||||
|
||||
## Журнал ссылок (Reflog)
|
||||
@ -116,6 +118,7 @@ _Связки клавиш_
|
||||
<kbd>C</kbd>: Скопировать несколько отобранных коммитов (cherry-pick)
|
||||
<kbd><c-r></kbd>: Сбросить отобранную (скопированную | cherry-picked) выборку коммитов
|
||||
<kbd><enter></kbd>: Просмотреть коммиты
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Коммиты
|
||||
@ -149,6 +152,7 @@ _Связки клавиш_
|
||||
<kbd>c</kbd>: Скопировать отобранные коммит (cherry-pick)
|
||||
<kbd>C</kbd>: Скопировать несколько отобранных коммитов (cherry-pick)
|
||||
<kbd><enter></kbd>: Просмотреть файлы выбранного элемента
|
||||
<kbd>/</kbd>: Найти
|
||||
</pre>
|
||||
|
||||
## Локальные Ветки
|
||||
@ -172,6 +176,7 @@ _Связки клавиш_
|
||||
<kbd>R</kbd>: Переименовать ветку
|
||||
<kbd>u</kbd>: Установить/убрать upstream-ветку
|
||||
<kbd><enter></kbd>: Просмотреть коммиты
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Меню
|
||||
@ -179,6 +184,7 @@ _Связки клавиш_
|
||||
<pre>
|
||||
<kbd><enter></kbd>: Выполнить
|
||||
<kbd><esc></kbd>: Закрыть
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Панель Подтверждения
|
||||
@ -201,6 +207,7 @@ _Связки клавиш_
|
||||
<kbd>C</kbd>: Скопировать несколько отобранных коммитов (cherry-pick)
|
||||
<kbd><c-r></kbd>: Сбросить отобранную (скопированную | cherry-picked) выборку коммитов
|
||||
<kbd><enter></kbd>: Просмотреть файлы выбранного элемента
|
||||
<kbd>/</kbd>: Найти
|
||||
</pre>
|
||||
|
||||
## Подмодули
|
||||
@ -214,6 +221,7 @@ _Связки клавиш_
|
||||
<kbd>e</kbd>: Обновить URL подмодуля
|
||||
<kbd>i</kbd>: Инициализировать подмодуль
|
||||
<kbd>b</kbd>: Просмотреть параметры массового подмодуля
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Сводка коммита
|
||||
@ -235,6 +243,7 @@ _Связки клавиш_
|
||||
<kbd>a</kbd>: Переключить все файлы, включённые в патч
|
||||
<kbd><enter></kbd>: Введите файл, чтобы добавить выбранные строки в патч (или свернуть каталог переключения)
|
||||
<kbd>`</kbd>: Переключить вид дерева файлов
|
||||
<kbd>/</kbd>: Найти
|
||||
</pre>
|
||||
|
||||
## Статус
|
||||
@ -256,6 +265,7 @@ _Связки клавиш_
|
||||
<kbd>n</kbd>: Создать тег
|
||||
<kbd>g</kbd>: Просмотреть параметры сброса
|
||||
<kbd><enter></kbd>: Просмотреть коммиты
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Удалённые ветки
|
||||
@ -268,9 +278,9 @@ _Связки клавиш_
|
||||
<kbd>r</kbd>: Перебазировать переключённую ветку на эту ветку
|
||||
<kbd>d</kbd>: Удалить ветку
|
||||
<kbd>u</kbd>: Установить как upstream-ветку переключённую ветку
|
||||
<kbd><esc></kbd>: Вернуться к списку удалённых репозитории
|
||||
<kbd>g</kbd>: Просмотреть параметры сброса
|
||||
<kbd><enter></kbd>: Просмотреть коммиты
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Удалённые репозитории
|
||||
@ -280,6 +290,7 @@ _Связки клавиш_
|
||||
<kbd>n</kbd>: Добавить новую удалённую ветку
|
||||
<kbd>d</kbd>: Удалить удалённую ветку
|
||||
<kbd>e</kbd>: Редактировать удалённый репозитории
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Файлы
|
||||
@ -306,6 +317,7 @@ _Связки клавиш_
|
||||
<kbd>`</kbd>: Переключить вид дерева файлов
|
||||
<kbd>M</kbd>: Открыть внешний инструмент слияния (git mergetool)
|
||||
<kbd>f</kbd>: Получить изменения
|
||||
<kbd>/</kbd>: Найти
|
||||
</pre>
|
||||
|
||||
## Хранилище
|
||||
@ -317,4 +329,5 @@ _Связки клавиш_
|
||||
<kbd>n</kbd>: Новая ветка
|
||||
<kbd>r</kbd>: Переименовать хранилище
|
||||
<kbd><enter></kbd>: Просмотреть файлы выбранного элемента
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
@ -36,8 +36,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>,</kbd>: 上一页
|
||||
<kbd>.</kbd>: 下一页
|
||||
<kbd><</kbd>: 滚动到顶部
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
<kbd>></kbd>: 滚动到底部
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
<kbd>H</kbd>: 向左滚动
|
||||
<kbd>L</kbd>: 向右滚动
|
||||
<kbd>]</kbd>: 下一个标签
|
||||
@ -57,6 +57,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: 复制提交范围(拣选)
|
||||
<kbd><c-r></kbd>: 重置已拣选(复制)的提交
|
||||
<kbd><enter></kbd>: 查看提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 分支页面
|
||||
@ -80,6 +81,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>R</kbd>: 重命名分支
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd><enter></kbd>: 查看提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 子提交
|
||||
@ -95,6 +97,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: 复制提交范围(拣选)
|
||||
<kbd><c-r></kbd>: 重置已拣选(复制)的提交
|
||||
<kbd><enter></kbd>: 查看提交的文件
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
</pre>
|
||||
|
||||
## 子模块
|
||||
@ -108,6 +111,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: 更新子模块 URL
|
||||
<kbd>i</kbd>: 初始化子模块
|
||||
<kbd>b</kbd>: 查看批量子模块选项
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 提交
|
||||
@ -141,6 +145,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: 复制提交(拣选)
|
||||
<kbd>C</kbd>: 复制提交范围(拣选)
|
||||
<kbd><enter></kbd>: 查看提交的文件
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
</pre>
|
||||
|
||||
## 提交文件
|
||||
@ -155,6 +160,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>a</kbd>: Toggle all files included in patch
|
||||
<kbd><enter></kbd>: 输入文件以将所选行添加到补丁中(或切换目录折叠)
|
||||
<kbd>`</kbd>: 切换文件树视图
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
</pre>
|
||||
|
||||
## 提交讯息
|
||||
@ -170,7 +176,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-o></kbd>: 将文件名复制到剪贴板
|
||||
<kbd>d</kbd>: 查看'放弃更改'选项
|
||||
<kbd><space></kbd>: 切换暂存状态
|
||||
<kbd><c-b></kbd>: Filter files (staged/unstaged)
|
||||
<kbd><c-b></kbd>: Filter files by status
|
||||
<kbd>c</kbd>: 提交更改
|
||||
<kbd>w</kbd>: 提交更改而无需预先提交钩子
|
||||
<kbd>A</kbd>: 修补最后一次提交
|
||||
@ -188,6 +194,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>`</kbd>: 切换文件树视图
|
||||
<kbd>M</kbd>: 打开外部合并工具 (git mergetool)
|
||||
<kbd>f</kbd>: 抓取
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
</pre>
|
||||
|
||||
## 构建补丁中
|
||||
@ -203,6 +210,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: 编辑文件
|
||||
<kbd><space></kbd>: 添加/移除 行到补丁
|
||||
<kbd><esc></kbd>: 退出逐行模式
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
</pre>
|
||||
|
||||
## 标签页面
|
||||
@ -214,6 +222,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 创建标签
|
||||
<kbd>g</kbd>: 查看重置选项
|
||||
<kbd><enter></kbd>: 查看提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 正在合并
|
||||
@ -251,6 +260,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: 提交更改
|
||||
<kbd>w</kbd>: 提交更改而无需预先提交钩子
|
||||
<kbd>C</kbd>: 提交更改(使用编辑器编辑提交信息)
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
</pre>
|
||||
|
||||
## 正常
|
||||
@ -282,6 +292,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 执行
|
||||
<kbd><esc></kbd>: 关闭
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 贮藏
|
||||
@ -293,6 +304,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 新分支
|
||||
<kbd>r</kbd>: Rename stash
|
||||
<kbd><enter></kbd>: 查看提交的文件
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 远程分支
|
||||
@ -305,9 +317,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>r</kbd>: 将已检出的分支变基到该分支
|
||||
<kbd>d</kbd>: 删除分支
|
||||
<kbd>u</kbd>: 设置为检出分支的上游
|
||||
<kbd><esc></kbd>: 返回远程仓库列表
|
||||
<kbd>g</kbd>: 查看重置选项
|
||||
<kbd><enter></kbd>: 查看提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 远程页面
|
||||
@ -317,4 +329,5 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 添加新的远程仓库
|
||||
<kbd>d</kbd>: 删除远程
|
||||
<kbd>e</kbd>: 编辑远程仓库
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
@ -36,8 +36,8 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>,</kbd>: 上一頁
|
||||
<kbd>.</kbd>: 下一頁
|
||||
<kbd><</kbd>: 捲動到頂部
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
<kbd>></kbd>: 捲動到底部
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
<kbd>H</kbd>: 向左捲動
|
||||
<kbd>L</kbd>: 向右捲動
|
||||
<kbd>]</kbd>: 下一個索引標籤
|
||||
@ -57,6 +57,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>C</kbd>: 複製提交範圍 (揀選)
|
||||
<kbd><c-r></kbd>: 重設選定的揀選 (複製) 提交
|
||||
<kbd><enter></kbd>: 檢視提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 主視窗 (一般)
|
||||
@ -101,6 +102,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>c</kbd>: 提交變更
|
||||
<kbd>w</kbd>: 沒有預提交 hook 就提交更改
|
||||
<kbd>C</kbd>: 使用 git 編輯器提交變更
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
</pre>
|
||||
|
||||
## 主面板 (補丁生成)
|
||||
@ -116,6 +118,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>e</kbd>: 編輯檔案
|
||||
<kbd><space></kbd>: 向 (或從) 補丁中添加/刪除行
|
||||
<kbd><esc></kbd>: 退出自訂補丁建立器
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
</pre>
|
||||
|
||||
## 功能表
|
||||
@ -123,6 +126,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 執行
|
||||
<kbd><esc></kbd>: 關閉
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 子提交
|
||||
@ -138,6 +142,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>C</kbd>: 複製提交範圍 (揀選)
|
||||
<kbd><c-r></kbd>: 重設選定的揀選 (複製) 提交
|
||||
<kbd><enter></kbd>: 檢視所選項目的檔案
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
</pre>
|
||||
|
||||
## 子模組
|
||||
@ -151,6 +156,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>e</kbd>: 更新子模組 URL
|
||||
<kbd>i</kbd>: 初始化子模組
|
||||
<kbd>b</kbd>: 查看批量子模組選項
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 提交
|
||||
@ -184,6 +190,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>c</kbd>: 複製提交 (揀選)
|
||||
<kbd>C</kbd>: 複製提交範圍 (揀選)
|
||||
<kbd><enter></kbd>: 檢視所選項目的檔案
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
</pre>
|
||||
|
||||
## 提交摘要
|
||||
@ -205,6 +212,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>a</kbd>: 切換所有檔案是否包含在補丁中
|
||||
<kbd><enter></kbd>: 輸入檔案以將選定的行添加至補丁(或切換目錄折疊)
|
||||
<kbd>`</kbd>: 切換檔案樹狀視圖
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
</pre>
|
||||
|
||||
## 收藏 (Stash)
|
||||
@ -216,6 +224,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>n</kbd>: 新分支
|
||||
<kbd>r</kbd>: 重新命名收藏
|
||||
<kbd><enter></kbd>: 檢視所選項目的檔案
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 本地分支
|
||||
@ -239,6 +248,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>R</kbd>: 重新命名分支
|
||||
<kbd>u</kbd>: 設定/取消設定上游
|
||||
<kbd><enter></kbd>: 檢視提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 標籤
|
||||
@ -250,6 +260,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>n</kbd>: 建立標籤
|
||||
<kbd>g</kbd>: 檢視重設選項
|
||||
<kbd><enter></kbd>: 檢視提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 檔案
|
||||
@ -276,6 +287,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>`</kbd>: 切換檔案樹狀視圖
|
||||
<kbd>M</kbd>: 開啟外部合併工具 (git mergetool)
|
||||
<kbd>f</kbd>: 擷取
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
</pre>
|
||||
|
||||
## 狀態
|
||||
@ -302,6 +314,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>n</kbd>: 新增遠端
|
||||
<kbd>d</kbd>: 移除遠端
|
||||
<kbd>e</kbd>: 編輯遠端
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 遠端分支
|
||||
@ -314,7 +327,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>r</kbd>: 將已檢出的分支變基至此分支
|
||||
<kbd>d</kbd>: 刪除分支
|
||||
<kbd>u</kbd>: 將此分支設為當前分支之上游
|
||||
<kbd><esc></kbd>: 返回遠端列表
|
||||
<kbd>g</kbd>: 檢視重設選項
|
||||
<kbd><enter></kbd>: 檢視提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
8
go.mod
8
go.mod
@ -18,7 +18,7 @@ require (
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230601121845-cb89273fdd4e
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230702054502-d6c452fc12ce
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
||||
@ -67,8 +67,8 @@ require (
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
|
||||
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/term v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/term v0.9.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
15
go.sum
15
go.sum
@ -72,8 +72,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230601121845-cb89273fdd4e h1:NpsrRAbYUmMkxDgNAVSlu3LxtfwdDe140vWVo/VldgA=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230601121845-cb89273fdd4e/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230702054502-d6c452fc12ce h1:Xgm21B1an/outcRxnkDfMT6wKb6SKBR05jXOyfPA8WQ=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230702054502-d6c452fc12ce/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
||||
@ -206,21 +206,22 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
||||
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
@ -23,3 +23,7 @@ func (f *CommitFile) Added() bool {
|
||||
func (f *CommitFile) Deleted() bool {
|
||||
return f.ChangeStatus == "D"
|
||||
}
|
||||
|
||||
func (f *CommitFile) GetPath() string {
|
||||
return f.Name
|
||||
}
|
||||
|
@ -60,15 +60,16 @@ type GuiConfig struct {
|
||||
}
|
||||
|
||||
type ThemeConfig struct {
|
||||
ActiveBorderColor []string `yaml:"activeBorderColor"`
|
||||
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
|
||||
OptionsTextColor []string `yaml:"optionsTextColor"`
|
||||
SelectedLineBgColor []string `yaml:"selectedLineBgColor"`
|
||||
SelectedRangeBgColor []string `yaml:"selectedRangeBgColor"`
|
||||
CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
|
||||
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
|
||||
UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
|
||||
DefaultFgColor []string `yaml:"defaultFgColor"`
|
||||
ActiveBorderColor []string `yaml:"activeBorderColor"`
|
||||
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
|
||||
SearchingActiveBorderColor []string `yaml:"searchingActiveBorderColor"`
|
||||
OptionsTextColor []string `yaml:"optionsTextColor"`
|
||||
SelectedLineBgColor []string `yaml:"selectedLineBgColor"`
|
||||
SelectedRangeBgColor []string `yaml:"selectedRangeBgColor"`
|
||||
CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
|
||||
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
|
||||
UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
|
||||
DefaultFgColor []string `yaml:"defaultFgColor"`
|
||||
}
|
||||
|
||||
type CommitLengthConfig struct {
|
||||
@ -409,15 +410,16 @@ func GetDefaultConfig() *UserConfig {
|
||||
TimeFormat: "02 Jan 06",
|
||||
ShortTimeFormat: time.Kitchen,
|
||||
Theme: ThemeConfig{
|
||||
ActiveBorderColor: []string{"green", "bold"},
|
||||
InactiveBorderColor: []string{"default"},
|
||||
OptionsTextColor: []string{"blue"},
|
||||
SelectedLineBgColor: []string{"blue"},
|
||||
SelectedRangeBgColor: []string{"blue"},
|
||||
CherryPickedCommitBgColor: []string{"cyan"},
|
||||
CherryPickedCommitFgColor: []string{"blue"},
|
||||
UnstagedChangesColor: []string{"red"},
|
||||
DefaultFgColor: []string{"default"},
|
||||
ActiveBorderColor: []string{"green", "bold"},
|
||||
SearchingActiveBorderColor: []string{"cyan", "bold"},
|
||||
InactiveBorderColor: []string{"default"},
|
||||
OptionsTextColor: []string{"blue"},
|
||||
SelectedLineBgColor: []string{"blue"},
|
||||
SelectedRangeBgColor: []string{"blue"},
|
||||
CherryPickedCommitBgColor: []string{"cyan"},
|
||||
CherryPickedCommitFgColor: []string{"blue"},
|
||||
UnstagedChangesColor: []string{"red"},
|
||||
DefaultFgColor: []string{"default"},
|
||||
},
|
||||
CommitLength: CommitLengthConfig{Show: true},
|
||||
SkipNoStagedFilesWarning: false,
|
||||
|
@ -135,10 +135,6 @@ func (gui *Gui) getRandomTip() string {
|
||||
"To escape a mode, for example cherry-picking, patch-building, diffing, or filtering mode, you can just spam the '%s' button. Unless of course you have `quitOnTopLevelReturn` enabled in your config",
|
||||
formattedKey(config.Universal.Return),
|
||||
),
|
||||
fmt.Sprintf(
|
||||
"To search for a string in your panel, press '%s'",
|
||||
formattedKey(config.Universal.StartSearch),
|
||||
),
|
||||
fmt.Sprintf(
|
||||
"You can page through the items of a panel using '%s' and '%s'",
|
||||
formattedKey(config.Universal.PrevPage),
|
||||
|
@ -200,9 +200,9 @@ func (self *ContextMgr) RemoveContexts(contextsToRemove []types.Context) error {
|
||||
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 := self.gui.onSearchEscape(); err != nil {
|
||||
return err
|
||||
if opts.NewContextKey != context.SEARCH_CONTEXT_KEY {
|
||||
if c.GetKind() == types.MAIN_CONTEXT || c.GetKind() == types.TEMPORARY_POPUP {
|
||||
self.gui.helpers.Search.CancelSearchIfSearching(c)
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,6 +234,8 @@ func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts)
|
||||
return err
|
||||
}
|
||||
|
||||
self.gui.helpers.Search.RenderSearchStatus(c)
|
||||
|
||||
desiredTitle := c.Title()
|
||||
if desiredTitle != "" {
|
||||
v.Title = desiredTitle
|
||||
@ -326,6 +328,30 @@ func (self *ContextMgr) IsCurrent(c types.Context) bool {
|
||||
return self.Current().GetKey() == c.GetKey()
|
||||
}
|
||||
|
||||
func (self *ContextMgr) AllFilterable() []types.IFilterableContext {
|
||||
var result []types.IFilterableContext
|
||||
|
||||
for _, context := range self.allContexts.Flatten() {
|
||||
if ctx, ok := context.(types.IFilterableContext); ok {
|
||||
result = append(result, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (self *ContextMgr) AllSearchable() []types.ISearchableContext {
|
||||
var result []types.ISearchableContext
|
||||
|
||||
for _, context := range self.allContexts.Flatten() {
|
||||
if ctx, ok := context.(types.ISearchableContext); ok {
|
||||
result = append(result, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// all list contexts
|
||||
func (self *ContextMgr) AllList() []types.IListContext {
|
||||
var listContexts []types.IListContext
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type BranchesContext struct {
|
||||
*BasicViewModel[*models.Branch]
|
||||
*FilteredListViewModel[*models.Branch]
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
@ -17,11 +17,16 @@ var (
|
||||
)
|
||||
|
||||
func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Branch { return c.Model().Branches })
|
||||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.Branch { return c.Model().Branches },
|
||||
func(branch *models.Branch) []string {
|
||||
return []string{branch.Name}
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetBranchListDisplayStrings(
|
||||
c.Model().Branches,
|
||||
viewModel.GetItems(),
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().Diffing.Ref,
|
||||
c.Tr,
|
||||
@ -30,7 +35,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
}
|
||||
|
||||
self := &BranchesContext{
|
||||
BasicViewModel: viewModel,
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Branches,
|
||||
|
@ -13,6 +13,7 @@ type CommitFilesContext struct {
|
||||
*filetree.CommitFileTreeViewModel
|
||||
*ListContextTrait
|
||||
*DynamicTitleBuilder
|
||||
*SearchTrait
|
||||
}
|
||||
|
||||
var (
|
||||
@ -38,9 +39,10 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
})
|
||||
}
|
||||
|
||||
return &CommitFilesContext{
|
||||
ctx := &CommitFilesContext{
|
||||
CommitFileTreeViewModel: viewModel,
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.CommitFilesDynamicTitle),
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
@ -57,6 +59,13 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
|
||||
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(selectedLineIdx int) error {
|
||||
ctx.GetList().SetSelectedLineIdx(selectedLineIdx)
|
||||
return ctx.HandleFocus(types.OnFocusOpts{})
|
||||
}))
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) GetSelectedItemId() string {
|
||||
|
93
pkg/gui/context/filtered_list.go
Normal file
93
pkg/gui/context/filtered_list.go
Normal file
@ -0,0 +1,93 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type FilteredList[T any] struct {
|
||||
filteredIndices []int // if nil, we are not filtering
|
||||
|
||||
getList func() []T
|
||||
getFilterFields func(T) []string
|
||||
filter string
|
||||
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
func NewFilteredList[T any](getList func() []T, getFilterFields func(T) []string) *FilteredList[T] {
|
||||
return &FilteredList[T]{
|
||||
getList: getList,
|
||||
getFilterFields: getFilterFields,
|
||||
mutex: &deadlock.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) GetFilter() string {
|
||||
return self.filter
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) SetFilter(filter string) {
|
||||
self.filter = filter
|
||||
|
||||
self.applyFilter()
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) ClearFilter() {
|
||||
self.SetFilter("")
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) IsFiltering() bool {
|
||||
return self.filter != ""
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) GetFilteredList() []T {
|
||||
if self.filteredIndices == nil {
|
||||
return self.getList()
|
||||
}
|
||||
return utils.ValuesAtIndices(self.getList(), self.filteredIndices)
|
||||
}
|
||||
|
||||
// TODO: update to just 'Len'
|
||||
func (self *FilteredList[T]) UnfilteredLen() int {
|
||||
return len(self.getList())
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) applyFilter() {
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
if self.filter == "" {
|
||||
self.filteredIndices = nil
|
||||
} else {
|
||||
self.filteredIndices = []int{}
|
||||
for i, item := range self.getList() {
|
||||
for _, field := range self.getFilterFields(item) {
|
||||
if self.match(field, self.filter) {
|
||||
self.filteredIndices = append(self.filteredIndices, i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) match(haystack string, needle string) bool {
|
||||
return utils.CaseAwareContains(haystack, needle)
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) UnfilteredIndex(index int) int {
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
if self.filteredIndices == nil {
|
||||
return index
|
||||
}
|
||||
|
||||
// we use -1 when there are no items
|
||||
if index == -1 {
|
||||
return -1
|
||||
}
|
||||
|
||||
return self.filteredIndices[index]
|
||||
}
|
33
pkg/gui/context/filtered_list_view_model.go
Normal file
33
pkg/gui/context/filtered_list_view_model.go
Normal file
@ -0,0 +1,33 @@
|
||||
package context
|
||||
|
||||
type FilteredListViewModel[T any] struct {
|
||||
*FilteredList[T]
|
||||
*ListViewModel[T]
|
||||
}
|
||||
|
||||
func NewFilteredListViewModel[T any](getList func() []T, getFilterFields func(T) []string) *FilteredListViewModel[T] {
|
||||
filteredList := NewFilteredList(getList, getFilterFields)
|
||||
|
||||
self := &FilteredListViewModel[T]{
|
||||
FilteredList: filteredList,
|
||||
}
|
||||
|
||||
listViewModel := NewListViewModel(filteredList.GetFilteredList)
|
||||
|
||||
self.ListViewModel = listViewModel
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// used for type switch
|
||||
func (self *FilteredListViewModel[T]) IsFilterableContext() {}
|
||||
|
||||
func (self *FilteredListViewModel[T]) ClearFilter() {
|
||||
// Set the selected line index to the unfiltered index of the currently selected line,
|
||||
// so that the current item is still selected after the filter is cleared.
|
||||
unfilteredIndex := self.FilteredList.UnfilteredIndex(self.GetSelectedLineIdx())
|
||||
|
||||
self.FilteredList.ClearFilter()
|
||||
|
||||
self.SetSelectedLineIdx(unfilteredIndex)
|
||||
}
|
@ -2,13 +2,13 @@ package context
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/context/traits"
|
||||
|
||||
type BasicViewModel[T any] struct {
|
||||
type ListViewModel[T any] struct {
|
||||
*traits.ListCursor
|
||||
getModel func() []T
|
||||
}
|
||||
|
||||
func NewBasicViewModel[T any](getModel func() []T) *BasicViewModel[T] {
|
||||
self := &BasicViewModel[T]{
|
||||
func NewListViewModel[T any](getModel func() []T) *ListViewModel[T] {
|
||||
self := &ListViewModel[T]{
|
||||
getModel: getModel,
|
||||
}
|
||||
|
||||
@ -17,11 +17,11 @@ func NewBasicViewModel[T any](getModel func() []T) *BasicViewModel[T] {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *BasicViewModel[T]) Len() int {
|
||||
func (self *ListViewModel[T]) Len() int {
|
||||
return len(self.getModel())
|
||||
}
|
||||
|
||||
func (self *BasicViewModel[T]) GetSelected() T {
|
||||
func (self *ListViewModel[T]) GetSelected() T {
|
||||
if self.Len() == 0 {
|
||||
return Zero[T]()
|
||||
}
|
||||
@ -29,6 +29,10 @@ func (self *BasicViewModel[T]) GetSelected() T {
|
||||
return self.getModel()[self.GetSelectedLineIdx()]
|
||||
}
|
||||
|
||||
func (self *ListViewModel[T]) GetItems() []T {
|
||||
return self.getModel()
|
||||
}
|
||||
|
||||
func Zero[T any]() T {
|
||||
return *new(T)
|
||||
}
|
@ -13,6 +13,7 @@ import (
|
||||
type LocalCommitsContext struct {
|
||||
*LocalCommitsViewModel
|
||||
*ListContextTrait
|
||||
*SearchTrait
|
||||
}
|
||||
|
||||
var (
|
||||
@ -57,8 +58,9 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
||||
)
|
||||
}
|
||||
|
||||
return &LocalCommitsContext{
|
||||
ctx := &LocalCommitsContext{
|
||||
LocalCommitsViewModel: viewModel,
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Commits,
|
||||
@ -73,6 +75,13 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
||||
refreshViewportOnChange: true,
|
||||
},
|
||||
}
|
||||
|
||||
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(selectedLineIdx int) error {
|
||||
ctx.GetList().SetSelectedLineIdx(selectedLineIdx)
|
||||
return ctx.HandleFocus(types.OnFocusOpts{})
|
||||
}))
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (self *LocalCommitsContext) GetSelectedItemId() string {
|
||||
@ -85,7 +94,7 @@ func (self *LocalCommitsContext) GetSelectedItemId() string {
|
||||
}
|
||||
|
||||
type LocalCommitsViewModel struct {
|
||||
*BasicViewModel[*models.Commit]
|
||||
*ListViewModel[*models.Commit]
|
||||
|
||||
// If this is true we limit the amount of commits we load, for the sake of keeping things fast.
|
||||
// If the user attempts to scroll past the end of the list, we will load more commits.
|
||||
@ -97,7 +106,7 @@ type LocalCommitsViewModel struct {
|
||||
|
||||
func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *ContextCommon) *LocalCommitsViewModel {
|
||||
self := &LocalCommitsViewModel{
|
||||
BasicViewModel: NewBasicViewModel(getModel),
|
||||
ListViewModel: NewListViewModel(getModel),
|
||||
limitCommits: true,
|
||||
showWholeGitGraph: c.UserConfig.Git.Log.ShowWholeGraph,
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ func (self *MenuContext) GetSelectedItemId() string {
|
||||
type MenuViewModel struct {
|
||||
c *ContextCommon
|
||||
menuItems []*types.MenuItem
|
||||
*BasicViewModel[*types.MenuItem]
|
||||
*FilteredListViewModel[*types.MenuItem]
|
||||
}
|
||||
|
||||
func NewMenuViewModel(c *ContextCommon) *MenuViewModel {
|
||||
@ -65,7 +65,10 @@ func NewMenuViewModel(c *ContextCommon) *MenuViewModel {
|
||||
c: c,
|
||||
}
|
||||
|
||||
self.BasicViewModel = NewBasicViewModel(func() []*types.MenuItem { return self.menuItems })
|
||||
self.FilteredListViewModel = NewFilteredListViewModel(
|
||||
func() []*types.MenuItem { return self.menuItems },
|
||||
func(item *types.MenuItem) []string { return item.LabelColumns },
|
||||
)
|
||||
|
||||
return self
|
||||
}
|
||||
@ -76,11 +79,12 @@ func (self *MenuViewModel) SetMenuItems(items []*types.MenuItem) {
|
||||
|
||||
// TODO: move into presentation package
|
||||
func (self *MenuViewModel) GetDisplayStrings(_startIdx int, _length int) [][]string {
|
||||
showKeys := slices.Some(self.menuItems, func(item *types.MenuItem) bool {
|
||||
menuItems := self.FilteredListViewModel.GetItems()
|
||||
showKeys := slices.Some(menuItems, func(item *types.MenuItem) bool {
|
||||
return item.Key != nil
|
||||
})
|
||||
|
||||
return slices.Map(self.menuItems, func(item *types.MenuItem) []string {
|
||||
return slices.Map(menuItems, func(item *types.MenuItem) []string {
|
||||
displayStrings := item.LabelColumns
|
||||
|
||||
if !showKeys {
|
||||
@ -93,6 +97,7 @@ func (self *MenuViewModel) GetDisplayStrings(_startIdx int, _length int) [][]str
|
||||
self.c.UserConfig.Keybinding.Universal.Confirm,
|
||||
self.c.UserConfig.Keybinding.Universal.Select,
|
||||
self.c.UserConfig.Keybinding.Universal.Return,
|
||||
self.c.UserConfig.Keybinding.Universal.StartSearch,
|
||||
}
|
||||
keyLabel := keybindings.LabelFromKey(item.Key)
|
||||
keyStyle := style.FgCyan
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
type PatchExplorerContext struct {
|
||||
*SimpleContext
|
||||
*SearchTrait
|
||||
|
||||
state *patch_exploring.State
|
||||
viewTrait *ViewTrait
|
||||
@ -28,7 +29,7 @@ func NewPatchExplorerContext(
|
||||
|
||||
c *ContextCommon,
|
||||
) *PatchExplorerContext {
|
||||
return &PatchExplorerContext{
|
||||
ctx := &PatchExplorerContext{
|
||||
state: nil,
|
||||
viewTrait: NewViewTrait(view),
|
||||
c: c,
|
||||
@ -42,7 +43,18 @@ func NewPatchExplorerContext(
|
||||
Focusable: true,
|
||||
HighlightOnFocus: true,
|
||||
})),
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
}
|
||||
|
||||
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(
|
||||
func(selectedLineIdx int) error {
|
||||
ctx.GetMutex().Lock()
|
||||
defer ctx.GetMutex().Unlock()
|
||||
return ctx.NavigateTo(ctx.c.IsCurrentContext(ctx), selectedLineIdx)
|
||||
}),
|
||||
)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) IsPatchExplorerContext() {}
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
type ReflogCommitsContext struct {
|
||||
*BasicViewModel[*models.Commit]
|
||||
*FilteredListViewModel[*models.Commit]
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
@ -19,11 +19,16 @@ var (
|
||||
)
|
||||
|
||||
func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Commit { return c.Model().FilteredReflogCommits })
|
||||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.Commit { return c.Model().FilteredReflogCommits },
|
||||
func(commit *models.Commit) []string {
|
||||
return []string{commit.ShortSha(), commit.Name}
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetReflogCommitListDisplayStrings(
|
||||
c.Model().FilteredReflogCommits,
|
||||
viewModel.GetItems(),
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().CherryPicking.SelectedShaSet(),
|
||||
c.Modes().Diffing.Ref,
|
||||
@ -35,7 +40,7 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
|
||||
}
|
||||
|
||||
return &ReflogCommitsContext{
|
||||
BasicViewModel: viewModel,
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().ReflogCommits,
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type RemoteBranchesContext struct {
|
||||
*BasicViewModel[*models.RemoteBranch]
|
||||
*FilteredListViewModel[*models.RemoteBranch]
|
||||
*ListContextTrait
|
||||
*DynamicTitleBuilder
|
||||
}
|
||||
@ -20,15 +20,20 @@ var (
|
||||
func NewRemoteBranchesContext(
|
||||
c *ContextCommon,
|
||||
) *RemoteBranchesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.RemoteBranch { return c.Model().RemoteBranches })
|
||||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.RemoteBranch { return c.Model().RemoteBranches },
|
||||
func(remoteBranch *models.RemoteBranch) []string {
|
||||
return []string{remoteBranch.Name}
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetRemoteBranchListDisplayStrings(c.Model().RemoteBranches, c.Modes().Diffing.Ref)
|
||||
return presentation.GetRemoteBranchListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &RemoteBranchesContext{
|
||||
BasicViewModel: viewModel,
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle),
|
||||
FilteredListViewModel: viewModel,
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().RemoteBranches,
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type RemotesContext struct {
|
||||
*BasicViewModel[*models.Remote]
|
||||
*FilteredListViewModel[*models.Remote]
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
@ -17,14 +17,19 @@ var (
|
||||
)
|
||||
|
||||
func NewRemotesContext(c *ContextCommon) *RemotesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Remote { return c.Model().Remotes })
|
||||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.Remote { return c.Model().Remotes },
|
||||
func(remote *models.Remote) []string {
|
||||
return []string{remote.Name}
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetRemoteListDisplayStrings(c.Model().Remotes, c.Modes().Diffing.Ref)
|
||||
return presentation.GetRemoteListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &RemotesContext{
|
||||
BasicViewModel: viewModel,
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Remotes,
|
||||
|
74
pkg/gui/context/search_trait.go
Normal file
74
pkg/gui/context/search_trait.go
Normal file
@ -0,0 +1,74 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
||||
type SearchTrait struct {
|
||||
c *ContextCommon
|
||||
|
||||
searchString string
|
||||
}
|
||||
|
||||
func NewSearchTrait(c *ContextCommon) *SearchTrait {
|
||||
return &SearchTrait{c: c}
|
||||
}
|
||||
|
||||
func (self *SearchTrait) GetSearchString() string {
|
||||
return self.searchString
|
||||
}
|
||||
|
||||
func (self *SearchTrait) SetSearchString(searchString string) {
|
||||
self.searchString = searchString
|
||||
}
|
||||
|
||||
func (self *SearchTrait) ClearSearchString() {
|
||||
self.SetSearchString("")
|
||||
}
|
||||
|
||||
// used for type switch
|
||||
func (self *SearchTrait) IsSearchableContext() {}
|
||||
|
||||
func (self *SearchTrait) onSelectItemWrapper(innerFunc func(int) error) func(int, int, int) error {
|
||||
keybindingConfig := self.c.UserConfig.Keybinding
|
||||
|
||||
return func(y int, index int, total int) error {
|
||||
if total == 0 {
|
||||
self.c.SetViewContent(
|
||||
self.c.Views().Search,
|
||||
fmt.Sprintf(
|
||||
self.c.Tr.NoMatchesFor,
|
||||
self.searchString,
|
||||
theme.OptionsFgColor.Sprintf(self.c.Tr.ExitSearchMode, keybindings.Label(keybindingConfig.Universal.Return)),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
self.c.SetViewContent(
|
||||
self.c.Views().Search,
|
||||
fmt.Sprintf(
|
||||
self.c.Tr.MatchesFor,
|
||||
self.searchString,
|
||||
index+1,
|
||||
total,
|
||||
theme.OptionsFgColor.Sprintf(
|
||||
self.c.Tr.SearchKeybindings,
|
||||
keybindings.Label(keybindingConfig.Universal.NextMatch),
|
||||
keybindings.Label(keybindingConfig.Universal.PrevMatch),
|
||||
keybindings.Label(keybindingConfig.Universal.Return),
|
||||
),
|
||||
),
|
||||
)
|
||||
if err := innerFunc(y); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchTrait) IsSearching() bool {
|
||||
return self.searchString != ""
|
||||
}
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type StashContext struct {
|
||||
*BasicViewModel[*models.StashEntry]
|
||||
*FilteredListViewModel[*models.StashEntry]
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
@ -19,14 +19,19 @@ var (
|
||||
func NewStashContext(
|
||||
c *ContextCommon,
|
||||
) *StashContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.StashEntry { return c.Model().StashEntries })
|
||||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.StashEntry { return c.Model().StashEntries },
|
||||
func(stashEntry *models.StashEntry) []string {
|
||||
return []string{stashEntry.Name}
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetStashEntryListDisplayStrings(c.Model().StashEntries, c.Modes().Diffing.Ref)
|
||||
return presentation.GetStashEntryListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &StashContext{
|
||||
BasicViewModel: viewModel,
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Stash,
|
||||
|
@ -12,9 +12,12 @@ import (
|
||||
)
|
||||
|
||||
type SubCommitsContext struct {
|
||||
c *ContextCommon
|
||||
|
||||
*SubCommitsViewModel
|
||||
*ListContextTrait
|
||||
*DynamicTitleBuilder
|
||||
*SearchTrait
|
||||
}
|
||||
|
||||
var (
|
||||
@ -26,7 +29,7 @@ func NewSubCommitsContext(
|
||||
c *ContextCommon,
|
||||
) *SubCommitsContext {
|
||||
viewModel := &SubCommitsViewModel{
|
||||
BasicViewModel: NewBasicViewModel(
|
||||
ListViewModel: NewListViewModel(
|
||||
func() []*models.Commit { return c.Model().SubCommits },
|
||||
),
|
||||
ref: nil,
|
||||
@ -60,8 +63,10 @@ func NewSubCommitsContext(
|
||||
)
|
||||
}
|
||||
|
||||
return &SubCommitsContext{
|
||||
ctx := &SubCommitsContext{
|
||||
c: c,
|
||||
SubCommitsViewModel: viewModel,
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.SubCommitsDynamicTitle),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
@ -78,12 +83,19 @@ func NewSubCommitsContext(
|
||||
refreshViewportOnChange: true,
|
||||
},
|
||||
}
|
||||
|
||||
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(selectedLineIdx int) error {
|
||||
ctx.GetList().SetSelectedLineIdx(selectedLineIdx)
|
||||
return ctx.HandleFocus(types.OnFocusOpts{})
|
||||
}))
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
type SubCommitsViewModel struct {
|
||||
// name of the ref that the sub-commits are shown for
|
||||
ref types.Ref
|
||||
*BasicViewModel[*models.Commit]
|
||||
*ListViewModel[*models.Commit]
|
||||
|
||||
limitCommits bool
|
||||
}
|
||||
|
@ -7,21 +7,26 @@ import (
|
||||
)
|
||||
|
||||
type SubmodulesContext struct {
|
||||
*BasicViewModel[*models.SubmoduleConfig]
|
||||
*FilteredListViewModel[*models.SubmoduleConfig]
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*SubmodulesContext)(nil)
|
||||
|
||||
func NewSubmodulesContext(c *ContextCommon) *SubmodulesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.SubmoduleConfig { return c.Model().Submodules })
|
||||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.SubmoduleConfig { return c.Model().Submodules },
|
||||
func(submodule *models.SubmoduleConfig) []string {
|
||||
return []string{submodule.Name}
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetSubmoduleListDisplayStrings(c.Model().Submodules)
|
||||
return presentation.GetSubmoduleListDisplayStrings(viewModel.GetItems())
|
||||
}
|
||||
|
||||
return &SubmodulesContext{
|
||||
BasicViewModel: viewModel,
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Submodules,
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type SuggestionsContext struct {
|
||||
*BasicViewModel[*types.Suggestion]
|
||||
*ListViewModel[*types.Suggestion]
|
||||
*ListContextTrait
|
||||
|
||||
State *SuggestionsContextState
|
||||
@ -40,11 +40,11 @@ func NewSuggestionsContext(
|
||||
return presentation.GetSuggestionListDisplayStrings(state.Suggestions)
|
||||
}
|
||||
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
viewModel := NewListViewModel(getModel)
|
||||
|
||||
return &SuggestionsContext{
|
||||
State: state,
|
||||
BasicViewModel: viewModel,
|
||||
State: state,
|
||||
ListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Suggestions,
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type TagsContext struct {
|
||||
*BasicViewModel[*models.Tag]
|
||||
*FilteredListViewModel[*models.Tag]
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
@ -19,14 +19,19 @@ var (
|
||||
func NewTagsContext(
|
||||
c *ContextCommon,
|
||||
) *TagsContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Tag { return c.Model().Tags })
|
||||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.Tag { return c.Model().Tags },
|
||||
func(tag *models.Tag) []string {
|
||||
return []string{tag.Name, tag.Message}
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetTagListDisplayStrings(c.Model().Tags, c.Modes().Diffing.Ref)
|
||||
return presentation.GetTagListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &TagsContext{
|
||||
BasicViewModel: viewModel,
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Tags,
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
type WorkingTreeContext struct {
|
||||
*filetree.FileTreeViewModel
|
||||
*ListContextTrait
|
||||
*SearchTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*WorkingTreeContext)(nil)
|
||||
@ -29,7 +30,8 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
})
|
||||
}
|
||||
|
||||
return &WorkingTreeContext{
|
||||
ctx := &WorkingTreeContext{
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
FileTreeViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
@ -44,6 +46,13 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
|
||||
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(selectedLineIdx int) error {
|
||||
ctx.GetList().SetSelectedLineIdx(selectedLineIdx)
|
||||
return ctx.HandleFocus(types.OnFocusOpts{})
|
||||
}))
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (self *WorkingTreeContext) GetSelectedItemId() string {
|
||||
|
@ -99,6 +99,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
modeHelper,
|
||||
appStatusHelper,
|
||||
),
|
||||
Search: helpers.NewSearchHelper(helperCommon),
|
||||
}
|
||||
|
||||
gui.CustomCommandsClient = custom_commands.NewClient(
|
||||
@ -162,6 +163,16 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
|
||||
sideWindowControllerFactory := controllers.NewSideWindowControllerFactory(common)
|
||||
|
||||
filterControllerFactory := controllers.NewFilterControllerFactory(common)
|
||||
for _, context := range gui.c.Context().AllFilterable() {
|
||||
controllers.AttachControllers(context, filterControllerFactory.Create(context))
|
||||
}
|
||||
|
||||
searchControllerFactory := controllers.NewSearchControllerFactory(common)
|
||||
for _, context := range gui.c.Context().AllSearchable() {
|
||||
controllers.AttachControllers(context, searchControllerFactory.Create(context))
|
||||
}
|
||||
|
||||
// allow for navigating between side window contexts
|
||||
for _, context := range []types.Context{
|
||||
gui.State.Contexts.Status,
|
||||
@ -323,6 +334,10 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
suggestionsController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.Search,
|
||||
controllers.NewSearchPromptController(common),
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.Global,
|
||||
syncController,
|
||||
undoController,
|
||||
|
@ -648,7 +648,7 @@ func (self *FilesController) handleStatusFilterPressed() error {
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.ResetCommitFilterState,
|
||||
Label: self.c.Tr.ResetFilter,
|
||||
OnPress: func() error {
|
||||
return self.setStatusFiltering(filetree.DisplayAll)
|
||||
},
|
||||
@ -658,7 +658,7 @@ func (self *FilesController) handleStatusFilterPressed() error {
|
||||
}
|
||||
|
||||
func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error {
|
||||
self.context().FileTreeViewModel.SetFilter(filter)
|
||||
self.context().FileTreeViewModel.SetStatusFilter(filter)
|
||||
return self.c.PostRefreshUpdate(self.context())
|
||||
}
|
||||
|
||||
|
48
pkg/gui/controllers/filter_controller.go
Normal file
48
pkg/gui/controllers/filter_controller.go
Normal file
@ -0,0 +1,48 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type FilterControllerFactory struct {
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func NewFilterControllerFactory(c *ControllerCommon) *FilterControllerFactory {
|
||||
return &FilterControllerFactory{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilterControllerFactory) Create(context types.IFilterableContext) *FilterController {
|
||||
return &FilterController{
|
||||
baseController: baseController{},
|
||||
c: self.c,
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
type FilterController struct {
|
||||
baseController
|
||||
c *ControllerCommon
|
||||
|
||||
context types.IFilterableContext
|
||||
}
|
||||
|
||||
func (self *FilterController) Context() types.Context {
|
||||
return self.context
|
||||
}
|
||||
|
||||
func (self *FilterController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
return []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.StartSearch),
|
||||
Handler: self.OpenFilterPrompt,
|
||||
Description: self.c.Tr.StartFilter,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilterController) OpenFilterPrompt() error {
|
||||
return self.c.Helpers().Search.OpenFilterPrompt(self.context)
|
||||
}
|
@ -292,7 +292,9 @@ func (self *ConfirmationHelper) ResizePopupPanel(v *gocui.View, content string)
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) resizeMenu() {
|
||||
itemCount := self.c.Contexts().Menu.GetList().Len()
|
||||
// we want the unfiltered length here so that if we're filtering we don't
|
||||
// resize the window
|
||||
itemCount := self.c.Contexts().Menu.UnfilteredLen()
|
||||
offset := 3
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
x0, y0, x1, y1 := self.getPopupPanelDimensionsForContentHeight(panelWidth, itemCount+offset)
|
||||
|
@ -46,6 +46,7 @@ type Helpers struct {
|
||||
Mode *ModeHelper
|
||||
AppStatus *AppStatusHelper
|
||||
WindowArrangement *WindowArrangementHelper
|
||||
Search *SearchHelper
|
||||
}
|
||||
|
||||
func NewStubHelpers() *Helpers {
|
||||
@ -78,5 +79,6 @@ func NewStubHelpers() *Helpers {
|
||||
Mode: &ModeHelper{},
|
||||
AppStatus: &AppStatusHelper{},
|
||||
WindowArrangement: &WindowArrangementHelper{},
|
||||
Search: &SearchHelper{},
|
||||
}
|
||||
}
|
||||
|
@ -464,10 +464,10 @@ func (self *RefreshHelper) refreshStateFiles() error {
|
||||
// I'd prefer to maintain as little state as possible.
|
||||
if conflictFileCount > 0 {
|
||||
if fileTreeViewModel.GetFilter() == filetree.DisplayAll {
|
||||
fileTreeViewModel.SetFilter(filetree.DisplayConflicted)
|
||||
fileTreeViewModel.SetStatusFilter(filetree.DisplayConflicted)
|
||||
}
|
||||
} else if fileTreeViewModel.GetFilter() == filetree.DisplayConflicted {
|
||||
fileTreeViewModel.SetFilter(filetree.DisplayAll)
|
||||
fileTreeViewModel.SetStatusFilter(filetree.DisplayAll)
|
||||
}
|
||||
|
||||
self.c.Model().Files = files
|
||||
|
260
pkg/gui/controllers/helpers/search_helper.go
Normal file
260
pkg/gui/controllers/helpers/search_helper.go
Normal file
@ -0,0 +1,260 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
||||
// NOTE: this helper supports both filtering and searching. Filtering is when
|
||||
// the contents of the list are filtered, whereas searching does not actually
|
||||
// change the contents of the list but instead just highlights the search.
|
||||
// The general term we use to capture both searching and filtering is...
|
||||
// 'searching', which is unfortunate but I can't think of a better name.
|
||||
|
||||
type SearchHelper struct {
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewSearchHelper(
|
||||
c *HelperCommon,
|
||||
) *SearchHelper {
|
||||
return &SearchHelper{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchHelper) OpenFilterPrompt(context types.IFilterableContext) error {
|
||||
state := self.searchState()
|
||||
|
||||
state.Context = context
|
||||
|
||||
self.searchPrefixView().SetContent(self.c.Tr.FilterPrefix)
|
||||
promptView := self.promptView()
|
||||
promptView.ClearTextArea()
|
||||
promptView.TextArea.TypeString(context.GetFilter())
|
||||
promptView.RenderTextArea()
|
||||
|
||||
if err := self.c.PushContext(self.c.Contexts().Search); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SearchHelper) OpenSearchPrompt(context types.ISearchableContext) error {
|
||||
state := self.searchState()
|
||||
|
||||
state.Context = context
|
||||
searchString := context.GetSearchString()
|
||||
|
||||
self.searchPrefixView().SetContent(self.c.Tr.SearchPrefix)
|
||||
promptView := self.promptView()
|
||||
promptView.ClearTextArea()
|
||||
promptView.TextArea.TypeString(searchString)
|
||||
promptView.RenderTextArea()
|
||||
|
||||
if err := self.c.PushContext(self.c.Contexts().Search); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SearchHelper) DisplayFilterStatus(context types.IFilterableContext) {
|
||||
state := self.searchState()
|
||||
|
||||
state.Context = context
|
||||
searchString := context.GetFilter()
|
||||
|
||||
self.searchPrefixView().SetContent(self.c.Tr.FilterPrefix)
|
||||
|
||||
promptView := self.promptView()
|
||||
keybindingConfig := self.c.UserConfig.Keybinding
|
||||
promptView.SetContent(fmt.Sprintf("matches for '%s' ", searchString) + theme.OptionsFgColor.Sprintf(self.c.Tr.ExitTextFilterMode, keybindings.Label(keybindingConfig.Universal.Return)))
|
||||
}
|
||||
|
||||
func (self *SearchHelper) DisplaySearchStatus(context types.ISearchableContext) {
|
||||
state := self.searchState()
|
||||
|
||||
state.Context = context
|
||||
|
||||
self.searchPrefixView().SetContent(self.c.Tr.SearchPrefix)
|
||||
_ = context.GetView().SelectCurrentSearchResult()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) searchState() *types.SearchState {
|
||||
return self.c.State().GetRepoState().GetSearchState()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) searchPrefixView() *gocui.View {
|
||||
return self.c.Views().SearchPrefix
|
||||
}
|
||||
|
||||
func (self *SearchHelper) promptView() *gocui.View {
|
||||
return self.c.Contexts().Search.GetView()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) promptContent() string {
|
||||
return self.c.Contexts().Search.GetView().TextArea.GetContent()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) Confirm() error {
|
||||
state := self.searchState()
|
||||
if self.promptContent() == "" {
|
||||
return self.CancelPrompt()
|
||||
}
|
||||
|
||||
switch state.SearchType() {
|
||||
case types.SearchTypeFilter:
|
||||
return self.ConfirmFilter()
|
||||
case types.SearchTypeSearch:
|
||||
return self.ConfirmSearch()
|
||||
case types.SearchTypeNone:
|
||||
return self.c.PopContext()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SearchHelper) ConfirmFilter() error {
|
||||
// We also do this on each keypress but we do it here again just in case
|
||||
state := self.searchState()
|
||||
|
||||
_, ok := state.Context.(types.IFilterableContext)
|
||||
if !ok {
|
||||
self.c.Log.Warnf("Context %s is not filterable", state.Context.GetKey())
|
||||
return nil
|
||||
}
|
||||
|
||||
self.OnPromptContentChanged(self.promptContent())
|
||||
|
||||
return self.c.PopContext()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) ConfirmSearch() error {
|
||||
state := self.searchState()
|
||||
|
||||
context, ok := state.Context.(types.ISearchableContext)
|
||||
if !ok {
|
||||
self.c.Log.Warnf("Context %s is searchable", state.Context.GetKey())
|
||||
return nil
|
||||
}
|
||||
|
||||
searchString := self.promptContent()
|
||||
context.SetSearchString(searchString)
|
||||
|
||||
view := context.GetView()
|
||||
|
||||
if err := self.c.PopContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := view.Search(searchString); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SearchHelper) CancelPrompt() error {
|
||||
self.Cancel()
|
||||
|
||||
return self.c.PopContext()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) Cancel() {
|
||||
state := self.searchState()
|
||||
|
||||
switch context := state.Context.(type) {
|
||||
case types.IFilterableContext:
|
||||
context.ClearFilter()
|
||||
_ = self.c.PostRefreshUpdate(context)
|
||||
case types.ISearchableContext:
|
||||
context.ClearSearchString()
|
||||
context.GetView().ClearSearch()
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
|
||||
self.HidePrompt()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) OnPromptContentChanged(searchString string) {
|
||||
state := self.searchState()
|
||||
switch context := state.Context.(type) {
|
||||
case types.IFilterableContext:
|
||||
context.SetSelectedLineIdx(0)
|
||||
_ = context.GetView().SetOriginY(0)
|
||||
context.SetFilter(searchString)
|
||||
_ = self.c.PostRefreshUpdate(context)
|
||||
case types.ISearchableContext:
|
||||
// do nothing
|
||||
default:
|
||||
// do nothing (shouldn't land here)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchHelper) RenderSearchStatus(c types.Context) {
|
||||
if c.GetKey() == context.SEARCH_CONTEXT_KEY {
|
||||
return
|
||||
}
|
||||
|
||||
if searchableContext, ok := c.(types.ISearchableContext); ok {
|
||||
if searchableContext.IsSearching() {
|
||||
self.setSearchingFrameColor()
|
||||
self.DisplaySearchStatus(searchableContext)
|
||||
return
|
||||
}
|
||||
}
|
||||
if filterableContext, ok := c.(types.IFilterableContext); ok {
|
||||
if filterableContext.IsFiltering() {
|
||||
self.setSearchingFrameColor()
|
||||
self.DisplayFilterStatus(filterableContext)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
self.HidePrompt()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) CancelSearchIfSearching(c types.Context) {
|
||||
if searchableContext, ok := c.(types.ISearchableContext); ok {
|
||||
view := searchableContext.GetView()
|
||||
if view != nil && view.IsSearching() {
|
||||
view.ClearSearch()
|
||||
searchableContext.ClearSearchString()
|
||||
self.Cancel()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if filterableContext, ok := c.(types.IFilterableContext); ok {
|
||||
if filterableContext.IsFiltering() {
|
||||
filterableContext.ClearFilter()
|
||||
self.Cancel()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchHelper) HidePrompt() {
|
||||
self.setNonSearchingFrameColor()
|
||||
|
||||
state := self.searchState()
|
||||
state.Context = nil
|
||||
}
|
||||
|
||||
func (self *SearchHelper) setSearchingFrameColor() {
|
||||
self.c.GocuiGui().SelFgColor = theme.SearchingActiveBorderColor
|
||||
self.c.GocuiGui().SelFrameColor = theme.SearchingActiveBorderColor
|
||||
}
|
||||
|
||||
func (self *SearchHelper) setNonSearchingFrameColor() {
|
||||
self.c.GocuiGui().SelFgColor = theme.ActiveBorderColor
|
||||
self.c.GocuiGui().SelFrameColor = theme.ActiveBorderColor
|
||||
}
|
@ -55,7 +55,7 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
|
||||
self.c.Modes().Filtering.Active()
|
||||
|
||||
showInfoSection := self.c.UserConfig.Gui.ShowBottomLine ||
|
||||
self.c.State().GetRepoState().IsSearching() ||
|
||||
self.c.State().GetRepoState().InSearchPrompt() ||
|
||||
self.modeHelper.IsAnyModeActive() ||
|
||||
self.appStatusHelper.HasStatus()
|
||||
infoSectionSize := 0
|
||||
@ -174,11 +174,17 @@ func (self *WindowArrangementHelper) getMidSectionWeights() (int, int) {
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box {
|
||||
if self.c.State().GetRepoState().IsSearching() {
|
||||
if self.c.State().GetRepoState().InSearchPrompt() {
|
||||
var prefix string
|
||||
if self.c.State().GetRepoState().GetSearchState().SearchType() == types.SearchTypeSearch {
|
||||
prefix = self.c.Tr.SearchPrefix
|
||||
} else {
|
||||
prefix = self.c.Tr.FilterPrefix
|
||||
}
|
||||
return []*boxlayout.Box{
|
||||
{
|
||||
Window: "searchPrefix",
|
||||
Size: runewidth.StringWidth(self.c.Tr.SearchPrefix),
|
||||
Size: runewidth.StringWidth(prefix),
|
||||
},
|
||||
{
|
||||
Window: "search",
|
||||
|
@ -150,18 +150,7 @@ func (self *ListController) GetKeybindings(opts types.KeybindingsOpts) []*types.
|
||||
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoTop), Handler: self.HandleGotoTop, Description: self.c.Tr.GotoTop},
|
||||
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollLeft), Handler: self.HandleScrollLeft},
|
||||
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollRight), Handler: self.HandleScrollRight},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.StartSearch),
|
||||
Handler: func() error { self.c.OpenSearch(); return nil },
|
||||
Description: self.c.Tr.StartSearch,
|
||||
Tag: "navigation",
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.GotoBottom),
|
||||
Description: self.c.Tr.GotoBottom,
|
||||
Handler: self.HandleGotoBottom,
|
||||
Tag: "navigation",
|
||||
},
|
||||
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoBottom), Handler: self.HandleGotoBottom, Description: self.c.Tr.GotoBottom},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -693,9 +693,7 @@ func (self *LocalCommitsController) openSearch() error {
|
||||
}
|
||||
}
|
||||
|
||||
self.c.OpenSearch()
|
||||
|
||||
return nil
|
||||
return self.c.Helpers().Search.OpenSearchPrompt(self.context())
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) gotoBottom() error {
|
||||
|
@ -123,12 +123,6 @@ func (self *PatchExplorerController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
Key: opts.GetKey(opts.Config.Universal.ScrollRight),
|
||||
Handler: self.withRenderAndFocus(self.HandleScrollRight),
|
||||
},
|
||||
{
|
||||
Tag: "navigation",
|
||||
Key: opts.GetKey(opts.Config.Universal.StartSearch),
|
||||
Handler: func() error { self.c.OpenSearch(); return nil },
|
||||
Description: self.c.Tr.StartSearch,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
|
||||
Handler: self.withLock(self.CopySelectedToClipboard),
|
||||
|
@ -50,6 +50,19 @@ func (self *QuitActions) confirmQuitDuringUpdate() error {
|
||||
func (self *QuitActions) Escape() error {
|
||||
currentContext := self.c.CurrentContext()
|
||||
|
||||
switch ctx := currentContext.(type) {
|
||||
case types.IFilterableContext:
|
||||
if ctx.IsFiltering() {
|
||||
self.c.Helpers().Search.Cancel()
|
||||
return nil
|
||||
}
|
||||
case types.ISearchableContext:
|
||||
if ctx.IsSearching() {
|
||||
self.c.Helpers().Search.Cancel()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -59,11 +59,6 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
Handler: self.checkSelected(self.setAsUpstream),
|
||||
Description: self.c.Tr.SetAsUpstream,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: self.escape,
|
||||
Description: self.c.Tr.ReturnToRemotesList,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
|
||||
Handler: self.checkSelected(self.createResetMenu),
|
||||
@ -115,10 +110,6 @@ func (self *RemoteBranchesController) checkSelected(callback func(*models.Remote
|
||||
}
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesController) escape() error {
|
||||
return self.c.PushContext(self.c.Contexts().Remotes)
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesController) delete(selectedBranch *models.RemoteBranch) error {
|
||||
message := fmt.Sprintf("%s '%s'?", self.c.Tr.DeleteRemoteBranchMessage, selectedBranch.FullName())
|
||||
|
||||
|
@ -104,14 +104,16 @@ func (self *RemotesController) enter(remote *models.Remote) error {
|
||||
if len(remote.Branches) == 0 {
|
||||
newSelectedLine = -1
|
||||
}
|
||||
self.c.Contexts().RemoteBranches.SetSelectedLineIdx(newSelectedLine)
|
||||
self.c.Contexts().RemoteBranches.SetTitleRef(remote.Name)
|
||||
remoteBranchesContext := self.c.Contexts().RemoteBranches
|
||||
remoteBranchesContext.SetSelectedLineIdx(newSelectedLine)
|
||||
remoteBranchesContext.SetTitleRef(remote.Name)
|
||||
remoteBranchesContext.SetParentContext(self.Context())
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().RemoteBranches); err != nil {
|
||||
if err := self.c.PostRefreshUpdate(remoteBranchesContext); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.PushContext(self.c.Contexts().RemoteBranches)
|
||||
return self.c.PushContext(remoteBranchesContext)
|
||||
}
|
||||
|
||||
func (self *RemotesController) add() error {
|
||||
|
48
pkg/gui/controllers/search_controller.go
Normal file
48
pkg/gui/controllers/search_controller.go
Normal file
@ -0,0 +1,48 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type SearchControllerFactory struct {
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func NewSearchControllerFactory(c *ControllerCommon) *SearchControllerFactory {
|
||||
return &SearchControllerFactory{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchControllerFactory) Create(context types.ISearchableContext) *SearchController {
|
||||
return &SearchController{
|
||||
baseController: baseController{},
|
||||
c: self.c,
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
type SearchController struct {
|
||||
baseController
|
||||
c *ControllerCommon
|
||||
|
||||
context types.ISearchableContext
|
||||
}
|
||||
|
||||
func (self *SearchController) Context() types.Context {
|
||||
return self.context
|
||||
}
|
||||
|
||||
func (self *SearchController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
return []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.StartSearch),
|
||||
Handler: self.OpenSearchPrompt,
|
||||
Description: self.c.Tr.StartSearch,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchController) OpenSearchPrompt() error {
|
||||
return self.c.Helpers().Search.OpenSearchPrompt(self.context)
|
||||
}
|
53
pkg/gui/controllers/search_prompt_controller.go
Normal file
53
pkg/gui/controllers/search_prompt_controller.go
Normal file
@ -0,0 +1,53 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type SearchPromptController struct {
|
||||
baseController
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &SearchPromptController{}
|
||||
|
||||
func NewSearchPromptController(
|
||||
common *ControllerCommon,
|
||||
) *SearchPromptController {
|
||||
return &SearchPromptController{
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchPromptController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
return []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Confirm),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.confirm,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.cancel,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchPromptController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
||||
func (self *SearchPromptController) context() types.Context {
|
||||
return self.c.Contexts().Search
|
||||
}
|
||||
|
||||
func (self *SearchPromptController) confirm() error {
|
||||
return self.c.Helpers().Search.Confirm()
|
||||
}
|
||||
|
||||
func (self *SearchPromptController) cancel() error {
|
||||
return self.c.Helpers().Search.CancelPrompt()
|
||||
}
|
@ -83,6 +83,7 @@ func (self *SwitchToDiffFilesController) viewFiles(opts SwitchToCommitFilesConte
|
||||
diffFilesContext.SetCanRebase(opts.CanRebase)
|
||||
diffFilesContext.SetParentContext(opts.Context)
|
||||
diffFilesContext.SetWindowName(opts.Context.GetWindowName())
|
||||
diffFilesContext.ClearSearchString()
|
||||
|
||||
if err := self.c.Refresh(types.RefreshOptions{
|
||||
Scope: []types.RefreshableView{types.COMMIT_FILES},
|
||||
|
@ -71,12 +71,15 @@ func (self *SwitchToSubCommitsController) viewCommits() error {
|
||||
|
||||
self.setSubCommits(commits)
|
||||
|
||||
self.c.Contexts().SubCommits.SetSelectedLineIdx(0)
|
||||
self.c.Contexts().SubCommits.SetParentContext(self.context)
|
||||
self.c.Contexts().SubCommits.SetWindowName(self.context.GetWindowName())
|
||||
self.c.Contexts().SubCommits.SetTitleRef(ref.Description())
|
||||
self.c.Contexts().SubCommits.SetRef(ref)
|
||||
self.c.Contexts().SubCommits.SetLimitCommits(true)
|
||||
subCommitsContext := self.c.Contexts().SubCommits
|
||||
subCommitsContext.SetSelectedLineIdx(0)
|
||||
subCommitsContext.SetParentContext(self.context)
|
||||
subCommitsContext.SetWindowName(self.context.GetWindowName())
|
||||
subCommitsContext.SetTitleRef(ref.Description())
|
||||
subCommitsContext.SetRef(ref)
|
||||
subCommitsContext.SetLimitCommits(true)
|
||||
subCommitsContext.ClearSearchString()
|
||||
subCommitsContext.GetView().ClearSearch()
|
||||
|
||||
err = self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
|
||||
if err != nil {
|
||||
|
@ -89,3 +89,14 @@ func (gui *Gui) promptEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Mo
|
||||
|
||||
return matched
|
||||
}
|
||||
|
||||
func (gui *Gui) searchEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
|
||||
matched := gui.handleEditorKeypress(v.TextArea, key, ch, mod, false)
|
||||
v.RenderTextArea()
|
||||
|
||||
searchString := v.TextArea.GetContent()
|
||||
|
||||
gui.helpers.Search.OnPromptContentChanged(searchString)
|
||||
|
||||
return matched
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ type IFileTree interface {
|
||||
ITree[models.File]
|
||||
|
||||
FilterFiles(test func(*models.File) bool) []*models.File
|
||||
SetFilter(filter FileTreeDisplayFilter)
|
||||
SetStatusFilter(filter FileTreeDisplayFilter)
|
||||
Get(index int) *FileNode
|
||||
GetFile(path string) *models.File
|
||||
GetAllItems() []*FileNode
|
||||
@ -91,7 +91,7 @@ func (self *FileTree) FilterFiles(test func(*models.File) bool) []*models.File {
|
||||
return slices.Filter(self.getFiles(), test)
|
||||
}
|
||||
|
||||
func (self *FileTree) SetFilter(filter FileTreeDisplayFilter) {
|
||||
func (self *FileTree) SetStatusFilter(filter FileTreeDisplayFilter) {
|
||||
self.filter = filter
|
||||
self.SetTree()
|
||||
}
|
||||
@ -102,7 +102,7 @@ func (self *FileTree) ToggleShowTree() {
|
||||
}
|
||||
|
||||
func (self *FileTree) Get(index int) *FileNode {
|
||||
// need to traverse the three depth first until we get to the index.
|
||||
// need to traverse the tree depth first until we get to the index.
|
||||
return NewFileNode(self.tree.GetNodeAtIndex(index+1, self.collapsedPaths)) // ignoring root
|
||||
}
|
||||
|
||||
|
@ -126,8 +126,8 @@ func (self *FileTreeViewModel) findNewSelectedIdx(prevNodes []*FileNode, currNod
|
||||
return -1
|
||||
}
|
||||
|
||||
func (self *FileTreeViewModel) SetFilter(filter FileTreeDisplayFilter) {
|
||||
self.IFileTree.SetFilter(filter)
|
||||
func (self *FileTreeViewModel) SetStatusFilter(filter FileTreeDisplayFilter) {
|
||||
self.IFileTree.SetStatusFilter(filter)
|
||||
self.IListCursor.SetSelectedLineIdx(0)
|
||||
}
|
||||
|
||||
|
@ -201,7 +201,7 @@ type GuiRepoState struct {
|
||||
SplitMainPanel bool
|
||||
LimitCommits bool
|
||||
|
||||
Searching searchingState
|
||||
SearchState *types.SearchState
|
||||
StartupStage types.StartupStage // Allows us to not load everything at once
|
||||
|
||||
ContextMgr *ContextMgr
|
||||
@ -256,8 +256,12 @@ func (self *GuiRepoState) SetScreenMode(value types.WindowMaximisation) {
|
||||
self.ScreenMode = value
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) IsSearching() bool {
|
||||
return self.Searching.isSearching
|
||||
func (self *GuiRepoState) InSearchPrompt() bool {
|
||||
return self.SearchState.SearchType() != types.SearchTypeNone
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) GetSearchState() *types.SearchState {
|
||||
return self.SearchState
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) SetSplitMainPanel(value bool) {
|
||||
@ -268,12 +272,6 @@ func (self *GuiRepoState) GetSplitMainPanel() bool {
|
||||
return self.SplitMainPanel
|
||||
}
|
||||
|
||||
type searchingState struct {
|
||||
view *gocui.View
|
||||
isSearching bool
|
||||
searchString string
|
||||
}
|
||||
|
||||
func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, reuseState bool) error {
|
||||
var err error
|
||||
gui.git, err = commands.NewGitCommand(
|
||||
@ -358,6 +356,7 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs, reuseState bool) types.
|
||||
ContextMgr: NewContextMgr(gui, contextTree),
|
||||
Contexts: contextTree,
|
||||
WindowViewNameMap: initialWindowViewNameMap(contextTree),
|
||||
SearchState: types.NewSearchState(),
|
||||
}
|
||||
|
||||
gui.RepoStateMap[Repo(currentDir)] = gui.State
|
||||
@ -584,11 +583,12 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
|
||||
})
|
||||
deadlock.Opts.Disable = !gui.Debug
|
||||
|
||||
gui.g.OnSearchEscape = gui.onSearchEscape
|
||||
if err := gui.Config.ReloadUserConfig(); err != nil {
|
||||
return nil
|
||||
}
|
||||
userConfig := gui.UserConfig
|
||||
|
||||
gui.g.OnSearchEscape = func() error { gui.helpers.Search.Cancel(); return nil }
|
||||
gui.g.SearchEscapeKey = keybindings.GetKey(userConfig.Keybinding.Universal.Return)
|
||||
gui.g.NextSearchMatchKey = keybindings.GetKey(userConfig.Keybinding.Universal.NextMatch)
|
||||
gui.g.PrevSearchMatchKey = keybindings.GetKey(userConfig.Keybinding.Universal.PrevMatch)
|
||||
|
@ -128,10 +128,6 @@ func (self *guiCommon) Mutexes() types.Mutexes {
|
||||
return self.gui.Mutexes
|
||||
}
|
||||
|
||||
func (self *guiCommon) OpenSearch() {
|
||||
_ = self.gui.handleOpenSearch(self.gui.currentViewName())
|
||||
}
|
||||
|
||||
func (self *guiCommon) GocuiGui() *gocui.Gui {
|
||||
return self.gui.g
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -70,7 +71,8 @@ func (self *GuiDriver) Fail(message string) {
|
||||
self.gui.g.Close()
|
||||
// need to give the gui time to close
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
panic(fullMessage)
|
||||
fmt.Fprintln(os.Stderr, fullMessage)
|
||||
panic("Test failed")
|
||||
}
|
||||
|
||||
// logs to the normal place that you log to i.e. viewable with `lazygit --logs`
|
||||
|
@ -215,18 +215,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.scrollUpSecondary,
|
||||
},
|
||||
{
|
||||
ViewName: "search",
|
||||
Key: opts.GetKey(opts.Config.Universal.Confirm),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.handleSearch,
|
||||
},
|
||||
{
|
||||
ViewName: "search",
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.handleSearchEscape,
|
||||
},
|
||||
{
|
||||
ViewName: "confirmation",
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevItem),
|
||||
|
@ -132,20 +132,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
|
||||
view.SelBgColor = theme.GocuiSelectedLineBgColor
|
||||
|
||||
// I doubt this is expensive though it's admittedly redundant after the first render
|
||||
view.SetOnSelectItem(gui.onSelectItemWrapper(listContext.OnSearchSelect))
|
||||
}
|
||||
|
||||
for _, context := range gui.c.Context().AllPatchExplorer() {
|
||||
context := context
|
||||
context.GetView().SetOnSelectItem(gui.onSelectItemWrapper(
|
||||
func(selectedLineIdx int) error {
|
||||
context.GetMutex().Lock()
|
||||
defer context.GetMutex().Unlock()
|
||||
return context.NavigateTo(gui.c.IsCurrentContext(context), selectedLineIdx)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
mainViewWidth, mainViewHeight := gui.Views.Main.Size()
|
||||
|
@ -46,9 +46,6 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
|
||||
|
||||
gui.Views.Menu.Title = opts.Title
|
||||
gui.Views.Menu.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Menu.SetOnSelectItem(gui.onSelectItemWrapper(func(selectedLine int) error {
|
||||
return nil
|
||||
}))
|
||||
|
||||
gui.Views.Tooltip.Wrap = true
|
||||
gui.Views.Tooltip.FgColor = theme.GocuiDefaultTextColor
|
||||
|
@ -1,103 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
||||
func (gui *Gui) handleOpenSearch(viewName string) error {
|
||||
view, err := gui.g.View(viewName)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.State.Searching.isSearching = true
|
||||
gui.State.Searching.view = view
|
||||
|
||||
gui.Views.Search.ClearTextArea()
|
||||
|
||||
if err := gui.c.PushContext(gui.State.Contexts.Search); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSearch() error {
|
||||
gui.State.Searching.searchString = gui.Views.Search.TextArea.GetContent()
|
||||
if err := gui.c.PopContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
view := gui.State.Searching.view
|
||||
if view == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := view.Search(gui.State.Searching.searchString); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, int) error {
|
||||
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||
|
||||
return func(y int, index int, total int) error {
|
||||
if total == 0 {
|
||||
gui.c.SetViewContent(
|
||||
gui.Views.Search,
|
||||
fmt.Sprintf(
|
||||
gui.Tr.NoMatchesFor,
|
||||
gui.State.Searching.searchString,
|
||||
theme.OptionsFgColor.Sprintf(gui.Tr.ExitSearchMode, keybindings.Label(keybindingConfig.Universal.Return)),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
gui.c.SetViewContent(
|
||||
gui.Views.Search,
|
||||
fmt.Sprintf(
|
||||
gui.Tr.MatchesFor,
|
||||
gui.State.Searching.searchString,
|
||||
index+1,
|
||||
total,
|
||||
theme.OptionsFgColor.Sprintf(
|
||||
gui.Tr.SearchKeybindings,
|
||||
keybindings.Label(keybindingConfig.Universal.NextMatch),
|
||||
keybindings.Label(keybindingConfig.Universal.PrevMatch),
|
||||
keybindings.Label(keybindingConfig.Universal.Return),
|
||||
),
|
||||
),
|
||||
)
|
||||
if err := innerFunc(y); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) onSearchEscape() error {
|
||||
gui.State.Searching.isSearching = false
|
||||
if gui.State.Searching.view != nil {
|
||||
gui.State.Searching.view.ClearSearch()
|
||||
gui.State.Searching.view = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSearchEscape() error {
|
||||
if err := gui.onSearchEscape(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.c.PopContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -68,8 +68,6 @@ type IGuiCommon interface {
|
||||
Context() IContextMgr
|
||||
|
||||
ActivateContext(context Context) error
|
||||
// enters search mode for the current view
|
||||
OpenSearch()
|
||||
|
||||
GetConfig() config.AppConfigurer
|
||||
GetAppState() *config.AppState
|
||||
@ -251,7 +249,8 @@ type IRepoStateAccessor interface {
|
||||
SetCurrentPopupOpts(*CreatePopupPanelOpts)
|
||||
GetScreenMode() WindowMaximisation
|
||||
SetScreenMode(WindowMaximisation)
|
||||
IsSearching() bool
|
||||
InSearchPrompt() bool
|
||||
GetSearchState() *SearchState
|
||||
SetSplitMainPanel(bool)
|
||||
GetSplitMainPanel() bool
|
||||
}
|
||||
|
@ -87,6 +87,27 @@ type Context interface {
|
||||
HandleRenderToMain() error
|
||||
}
|
||||
|
||||
type IFilterableContext interface {
|
||||
Context
|
||||
IListPanelState
|
||||
|
||||
SetFilter(string)
|
||||
GetFilter() string
|
||||
ClearFilter()
|
||||
IsFiltering() bool
|
||||
IsFilterableContext()
|
||||
}
|
||||
|
||||
type ISearchableContext interface {
|
||||
Context
|
||||
|
||||
SetSearchString(string)
|
||||
GetSearchString() string
|
||||
ClearSearchString()
|
||||
IsSearching() bool
|
||||
IsSearchableContext()
|
||||
}
|
||||
|
||||
type DiffableContext interface {
|
||||
Context
|
||||
|
||||
@ -104,7 +125,6 @@ type IListContext interface {
|
||||
|
||||
GetList() IList
|
||||
|
||||
OnSearchSelect(selectedLineIdx int) error
|
||||
FocusLine()
|
||||
IsListContext() // used for type switch
|
||||
}
|
||||
@ -211,5 +231,7 @@ type IContextMgr interface {
|
||||
IsCurrent(c Context) bool
|
||||
ForEach(func(Context))
|
||||
AllList() []IListContext
|
||||
AllFilterable() []IFilterableContext
|
||||
AllSearchable() []ISearchableContext
|
||||
AllPatchExplorer() []IPatchExplorerContext
|
||||
}
|
||||
|
31
pkg/gui/types/search_state.go
Normal file
31
pkg/gui/types/search_state.go
Normal file
@ -0,0 +1,31 @@
|
||||
package types
|
||||
|
||||
type SearchType int
|
||||
|
||||
const (
|
||||
SearchTypeNone SearchType = iota
|
||||
// searching is where matches are highlighted but the content is not filtered down
|
||||
SearchTypeSearch
|
||||
// filter is where the list is filtered down to only matches
|
||||
SearchTypeFilter
|
||||
)
|
||||
|
||||
// TODO: could we remove this entirely?
|
||||
type SearchState struct {
|
||||
Context Context
|
||||
}
|
||||
|
||||
func NewSearchState() *SearchState {
|
||||
return &SearchState{}
|
||||
}
|
||||
|
||||
func (self *SearchState) SearchType() SearchType {
|
||||
switch self.Context.(type) {
|
||||
case IFilterableContext:
|
||||
return SearchTypeFilter
|
||||
case ISearchableContext:
|
||||
return SearchTypeSearch
|
||||
default:
|
||||
return SearchTypeNone
|
||||
}
|
||||
}
|
@ -91,10 +91,16 @@ func (gui *Gui) createAllViews() error {
|
||||
gui.Views.Options.Frame = false
|
||||
|
||||
gui.Views.SearchPrefix.BgColor = gocui.ColorDefault
|
||||
gui.Views.SearchPrefix.FgColor = gocui.ColorGreen
|
||||
gui.Views.SearchPrefix.FgColor = gocui.ColorCyan
|
||||
gui.Views.SearchPrefix.Frame = false
|
||||
gui.c.SetViewContent(gui.Views.SearchPrefix, gui.Tr.SearchPrefix)
|
||||
|
||||
gui.Views.Search.BgColor = gocui.ColorDefault
|
||||
gui.Views.Search.FgColor = gocui.ColorCyan
|
||||
gui.Views.Search.Editable = true
|
||||
gui.Views.Search.Frame = false
|
||||
gui.Views.Search.Editor = gocui.EditorFunc(gui.searchEditor)
|
||||
|
||||
gui.Views.Stash.Title = gui.c.Tr.StashTitle
|
||||
|
||||
gui.Views.Commits.Title = gui.c.Tr.CommitsTitle
|
||||
@ -141,11 +147,6 @@ func (gui *Gui) createAllViews() error {
|
||||
|
||||
gui.Views.Status.Title = gui.c.Tr.StatusTitle
|
||||
|
||||
gui.Views.Search.BgColor = gocui.ColorDefault
|
||||
gui.Views.Search.FgColor = gocui.ColorGreen
|
||||
gui.Views.Search.Editable = true
|
||||
gui.Views.Search.Frame = false
|
||||
|
||||
gui.Views.AppStatus.BgColor = gocui.ColorDefault
|
||||
gui.Views.AppStatus.FgColor = gocui.ColorCyan
|
||||
gui.Views.AppStatus.Visible = false
|
||||
|
@ -35,7 +35,7 @@ func dutchTranslationSet() TranslationSet {
|
||||
Scroll: "Scroll",
|
||||
FilterStagedFiles: "Show only staged files",
|
||||
FilterUnstagedFiles: "Show only unstaged files",
|
||||
ResetCommitFilterState: "Reset commit file state filter",
|
||||
ResetFilter: "Reset commit file state filter",
|
||||
MergeConflictsTitle: "Merge conflicten",
|
||||
Checkout: "Uitchecken",
|
||||
PullWait: "Pullen...",
|
||||
|
@ -54,7 +54,7 @@ type TranslationSet struct {
|
||||
FileFilter string
|
||||
FilterStagedFiles string
|
||||
FilterUnstagedFiles string
|
||||
ResetCommitFilterState string
|
||||
ResetFilter string
|
||||
MergeConflictsTitle string
|
||||
Checkout string
|
||||
NoChangedFiles string
|
||||
@ -371,6 +371,7 @@ type TranslationSet struct {
|
||||
NextScreenMode string
|
||||
PrevScreenMode string
|
||||
StartSearch string
|
||||
StartFilter string
|
||||
Panel string
|
||||
Keybindings string
|
||||
KeybindingsLegend string
|
||||
@ -536,7 +537,9 @@ type TranslationSet struct {
|
||||
MatchesFor string
|
||||
SearchKeybindings string
|
||||
SearchPrefix string
|
||||
FilterPrefix string
|
||||
ExitSearchMode string
|
||||
ExitTextFilterMode string
|
||||
Actions Actions
|
||||
Bisect Bisect
|
||||
}
|
||||
@ -741,10 +744,10 @@ func EnglishTranslationSet() TranslationSet {
|
||||
Scroll: "Scroll",
|
||||
MergeConflictsTitle: "Merge conflicts",
|
||||
Checkout: "Checkout",
|
||||
FileFilter: "Filter files (staged/unstaged)",
|
||||
FileFilter: "Filter files by status",
|
||||
FilterStagedFiles: "Show only staged files",
|
||||
FilterUnstagedFiles: "Show only unstaged files",
|
||||
ResetCommitFilterState: "Reset filter",
|
||||
ResetFilter: "Reset filter",
|
||||
NoChangedFiles: "No changed files",
|
||||
PullWait: "Pulling...",
|
||||
PushWait: "Pushing...",
|
||||
@ -1054,50 +1057,52 @@ func EnglishTranslationSet() TranslationSet {
|
||||
GitFlowOptions: "Show git-flow options",
|
||||
NotAGitFlowBranch: "This does not seem to be a git flow branch",
|
||||
NewGitFlowBranchPrompt: "New {{.branchType}} name:",
|
||||
IgnoreTracked: "Ignore tracked file",
|
||||
IgnoreTrackedPrompt: "Are you sure you want to ignore a tracked file?",
|
||||
ExcludeTracked: "Exclude tracked file",
|
||||
ExcludeTrackedPrompt: "Are you sure you want to exclude a tracked file?",
|
||||
ViewResetToUpstreamOptions: "View upstream reset options",
|
||||
NextScreenMode: "Next screen mode (normal/half/fullscreen)",
|
||||
PrevScreenMode: "Prev screen mode",
|
||||
StartSearch: "Start search",
|
||||
Panel: "Panel",
|
||||
KeybindingsLegend: "Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b",
|
||||
RenameBranch: "Rename branch",
|
||||
SetUnsetUpstream: "Set/Unset upstream",
|
||||
NewBranchNamePrompt: "Enter new branch name for branch",
|
||||
RenameBranchWarning: "This branch is tracking a remote. This action will only rename the local branch name, not the name of the remote branch. Continue?",
|
||||
OpenMenu: "Open menu",
|
||||
ResetCherryPick: "Reset cherry-picked (copied) commits selection",
|
||||
NextTab: "Next tab",
|
||||
PrevTab: "Previous tab",
|
||||
CantUndoWhileRebasing: "Can't undo while rebasing",
|
||||
CantRedoWhileRebasing: "Can't redo while rebasing",
|
||||
MustStashWarning: "Pulling a patch out into the index requires stashing and unstashing your changes. If something goes wrong, you'll be able to access your files from the stash. Continue?",
|
||||
MustStashTitle: "Must stash",
|
||||
ConfirmationTitle: "Confirmation panel",
|
||||
PrevPage: "Previous page",
|
||||
NextPage: "Next page",
|
||||
GotoTop: "Scroll to top",
|
||||
GotoBottom: "Scroll to bottom",
|
||||
FilteringBy: "Filtering by",
|
||||
ResetInParentheses: "(Reset)",
|
||||
OpenFilteringMenu: "View filter-by-path options",
|
||||
FilterBy: "Filter by",
|
||||
ExitFilterMode: "Stop filtering by path",
|
||||
FilterPathOption: "Enter path to filter by",
|
||||
EnterFileName: "Enter path:",
|
||||
FilteringMenuTitle: "Filtering",
|
||||
MustExitFilterModeTitle: "Command not available",
|
||||
MustExitFilterModePrompt: "Command not available in filtered mode. Exit filtered mode?",
|
||||
Diff: "Diff",
|
||||
EnterRefToDiff: "Enter ref to diff",
|
||||
EnterRefName: "Enter ref:",
|
||||
ExitDiffMode: "Exit diff mode",
|
||||
DiffingMenuTitle: "Diffing",
|
||||
SwapDiff: "Reverse diff direction",
|
||||
OpenDiffingMenu: "Open diff menu",
|
||||
|
||||
IgnoreTracked: "Ignore tracked file",
|
||||
IgnoreTrackedPrompt: "Are you sure you want to ignore a tracked file?",
|
||||
ExcludeTracked: "Exclude tracked file",
|
||||
ExcludeTrackedPrompt: "Are you sure you want to exclude a tracked file?",
|
||||
ViewResetToUpstreamOptions: "View upstream reset options",
|
||||
NextScreenMode: "Next screen mode (normal/half/fullscreen)",
|
||||
PrevScreenMode: "Prev screen mode",
|
||||
StartSearch: "Search the current view by text",
|
||||
StartFilter: "Filter the current view by text",
|
||||
Panel: "Panel",
|
||||
KeybindingsLegend: "Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b",
|
||||
RenameBranch: "Rename branch",
|
||||
SetUnsetUpstream: "Set/Unset upstream",
|
||||
NewBranchNamePrompt: "Enter new branch name for branch",
|
||||
RenameBranchWarning: "This branch is tracking a remote. This action will only rename the local branch name, not the name of the remote branch. Continue?",
|
||||
OpenMenu: "Open menu",
|
||||
ResetCherryPick: "Reset cherry-picked (copied) commits selection",
|
||||
NextTab: "Next tab",
|
||||
PrevTab: "Previous tab",
|
||||
CantUndoWhileRebasing: "Can't undo while rebasing",
|
||||
CantRedoWhileRebasing: "Can't redo while rebasing",
|
||||
MustStashWarning: "Pulling a patch out into the index requires stashing and unstashing your changes. If something goes wrong, you'll be able to access your files from the stash. Continue?",
|
||||
MustStashTitle: "Must stash",
|
||||
ConfirmationTitle: "Confirmation panel",
|
||||
PrevPage: "Previous page",
|
||||
NextPage: "Next page",
|
||||
GotoTop: "Scroll to top",
|
||||
GotoBottom: "Scroll to bottom",
|
||||
FilteringBy: "Filtering by",
|
||||
ResetInParentheses: "(Reset)",
|
||||
OpenFilteringMenu: "View filter-by-path options",
|
||||
FilterBy: "Filter by",
|
||||
ExitFilterMode: "Stop filtering by path",
|
||||
FilterPathOption: "Enter path to filter by",
|
||||
EnterFileName: "Enter path:",
|
||||
FilteringMenuTitle: "Filtering",
|
||||
MustExitFilterModeTitle: "Command not available",
|
||||
MustExitFilterModePrompt: "Command not available in filter-by-path mode. Exit filter-by-path mode?",
|
||||
Diff: "Diff",
|
||||
EnterRefToDiff: "Enter ref to diff",
|
||||
EnterRefName: "Enter ref:",
|
||||
ExitDiffMode: "Exit diff mode",
|
||||
DiffingMenuTitle: "Diffing",
|
||||
SwapDiff: "Reverse diff direction",
|
||||
OpenDiffingMenu: "Open diff menu",
|
||||
// the actual view is the extras view which I intend to give more tabs in future but for now we'll only mention the command log part
|
||||
OpenExtrasMenu: "Open command log menu",
|
||||
ShowingGitDiff: "Showing output for:",
|
||||
@ -1223,9 +1228,11 @@ func EnglishTranslationSet() TranslationSet {
|
||||
CopyPatchToClipboard: "Copy patch to clipboard",
|
||||
NoMatchesFor: "No matches for '%s' %s",
|
||||
ExitSearchMode: "%s: Exit search mode",
|
||||
ExitTextFilterMode: "%s: Exit filter mode",
|
||||
MatchesFor: "matches for '%s' (%d of %d) %s", // lowercase because it's after other text
|
||||
SearchKeybindings: "%s: Next match, %s: Previous match, %s: Exit search mode",
|
||||
SearchPrefix: "Search: ",
|
||||
FilterPrefix: "Filter: ",
|
||||
Actions: Actions{
|
||||
// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
|
||||
CheckoutCommit: "Checkout commit",
|
||||
|
@ -61,7 +61,7 @@ func japaneseTranslationSet() TranslationSet {
|
||||
FileFilter: "ファイルをフィルタ (ステージ/アンステージ)",
|
||||
FilterStagedFiles: "ステージされたファイルのみを表示",
|
||||
FilterUnstagedFiles: "ステージされていないファイルのみを表示",
|
||||
ResetCommitFilterState: "フィルタをリセット",
|
||||
ResetFilter: "フィルタをリセット",
|
||||
// NoChangedFiles: "No changed files",
|
||||
PullWait: "Pull中...",
|
||||
PushWait: "Push中...",
|
||||
|
@ -60,7 +60,7 @@ func koreanTranslationSet() TranslationSet {
|
||||
FileFilter: "파일을 필터하기 (Staged/unstaged)",
|
||||
FilterStagedFiles: "Staged된 파일만 표시",
|
||||
FilterUnstagedFiles: "Stage되지 않은 파일만 표시",
|
||||
ResetCommitFilterState: "필터 리셋",
|
||||
ResetFilter: "필터 리셋",
|
||||
NoChangedFiles: "변경된 파일이 없습니다.",
|
||||
PullWait: "업데이트 중...",
|
||||
PushWait: "푸시 중...",
|
||||
|
@ -31,7 +31,7 @@ func polishTranslationSet() TranslationSet {
|
||||
Scroll: "Przewiń",
|
||||
FilterStagedFiles: "Pokaż tylko pliki w poczekalni",
|
||||
FilterUnstagedFiles: "Pokaż tylko pliki poza poczekalnią",
|
||||
ResetCommitFilterState: "Resetuj filtr commitów",
|
||||
ResetFilter: "Resetuj filtr commitów",
|
||||
Checkout: "Przełącz",
|
||||
NoChangedFiles: "Brak zmienionych plików",
|
||||
PullWait: "Pobieranie zmian...",
|
||||
|
@ -79,7 +79,7 @@ func RussianTranslationSet() TranslationSet {
|
||||
FileFilter: "Фильтровать файлы (проиндексированные/непроиндексированные)",
|
||||
FilterStagedFiles: "Показывать только проиндексированные файлы",
|
||||
FilterUnstagedFiles: "Показывать только непроиндексированные файлы",
|
||||
ResetCommitFilterState: "Сбросить фильтр",
|
||||
ResetFilter: "Сбросить фильтр",
|
||||
NoChangedFiles: "Нет изменённых файлов",
|
||||
PullWait: "Получение и слияние изменении...",
|
||||
PushWait: "Отправка изменении...",
|
||||
|
@ -112,7 +112,7 @@ func traditionalChineseTranslationSet() TranslationSet {
|
||||
FileFilter: "篩選檔案 (預存/未預存)",
|
||||
FilterStagedFiles: "僅顯示預存的檔案",
|
||||
FilterUnstagedFiles: "僅顯示未預存的檔案",
|
||||
ResetCommitFilterState: "重設篩選",
|
||||
ResetFilter: "重設篩選",
|
||||
NoChangedFiles: "沒有變更的檔案",
|
||||
PullWait: "拉取...",
|
||||
PushWait: "推送...",
|
||||
|
@ -10,9 +10,9 @@ type IntMatcher struct {
|
||||
|
||||
func (self *IntMatcher) EqualsInt(target int) *IntMatcher {
|
||||
self.appendRule(matcherRule[int]{
|
||||
name: fmt.Sprintf("equals '%d'", target),
|
||||
name: fmt.Sprintf("equals %d", target),
|
||||
testFn: func(value int) (bool, string) {
|
||||
return value == target, fmt.Sprintf("Expected '%d' to equal '%d'", value, target)
|
||||
return value == target, fmt.Sprintf("Expected %d to equal %d", value, target)
|
||||
},
|
||||
})
|
||||
|
||||
@ -21,9 +21,9 @@ func (self *IntMatcher) EqualsInt(target int) *IntMatcher {
|
||||
|
||||
func (self *IntMatcher) GreaterThan(target int) *IntMatcher {
|
||||
self.appendRule(matcherRule[int]{
|
||||
name: fmt.Sprintf("greater than '%d'", target),
|
||||
name: fmt.Sprintf("greater than %d", target),
|
||||
testFn: func(value int) (bool, string) {
|
||||
return value > target, fmt.Sprintf("Expected '%d' to greater than '%d'", value, target)
|
||||
return value > target, fmt.Sprintf("Expected %d to greater than %d", value, target)
|
||||
},
|
||||
})
|
||||
|
||||
@ -32,9 +32,9 @@ func (self *IntMatcher) GreaterThan(target int) *IntMatcher {
|
||||
|
||||
func (self *IntMatcher) LessThan(target int) *IntMatcher {
|
||||
self.appendRule(matcherRule[int]{
|
||||
name: fmt.Sprintf("less than '%d'", target),
|
||||
name: fmt.Sprintf("less than %d", target),
|
||||
testFn: func(value int) (bool, string) {
|
||||
return value < target, fmt.Sprintf("Expected '%d' to less than '%d'", value, target)
|
||||
return value < target, fmt.Sprintf("Expected %d to less than %d", value, target)
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -48,6 +48,18 @@ func (self *MenuDriver) TopLines(matchers ...*TextMatcher) *MenuDriver {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *MenuDriver) Filter(text string) *MenuDriver {
|
||||
self.getViewDriver().FilterOrSearch(text)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *MenuDriver) LineCount(matcher *IntMatcher) *MenuDriver {
|
||||
self.getViewDriver().LineCount(matcher)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *MenuDriver) checkNecessaryChecksCompleted() {
|
||||
if !self.hasCheckedTitle {
|
||||
self.t.Fail("You must check the title of a menu popup by calling Title() before calling Confirm()/Cancel().")
|
||||
|
@ -66,7 +66,7 @@ func (self *ViewDriver) Title(expected *TextMatcher) *ViewDriver {
|
||||
// If you only care about a subset of lines, use the ContainsLines method instead.
|
||||
func (self *ViewDriver) Lines(matchers ...*TextMatcher) *ViewDriver {
|
||||
self.validateMatchersPassed(matchers)
|
||||
self.LineCount(len(matchers))
|
||||
self.LineCount(EqualsInt(len(matchers)))
|
||||
|
||||
return self.assertLines(0, matchers...)
|
||||
}
|
||||
@ -470,33 +470,60 @@ func (self *ViewDriver) IsEmpty() *ViewDriver {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *ViewDriver) LineCount(expectedCount int) *ViewDriver {
|
||||
if expectedCount == 0 {
|
||||
return self.IsEmpty()
|
||||
}
|
||||
|
||||
func (self *ViewDriver) LineCount(matcher *IntMatcher) *ViewDriver {
|
||||
view := self.getView()
|
||||
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
lines := view.BufferLines()
|
||||
return len(lines) == expectedCount, fmt.Sprintf("unexpected number of lines in view '%s'. Expected %d, got %d", view.Name(), expectedCount, len(lines))
|
||||
lineCount := self.getLineCount()
|
||||
ok, _ := matcher.test(lineCount)
|
||||
return ok, fmt.Sprintf("unexpected number of lines in view '%s'. Expected %s, got %d", view.Name(), matcher.name(), lineCount)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *ViewDriver) getLineCount() int {
|
||||
// can't rely entirely on view.BufferLines because it returns 1 even if there's nothing in the view
|
||||
if strings.TrimSpace(self.getView().Buffer()) == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
view := self.getView()
|
||||
return len(view.BufferLines())
|
||||
}
|
||||
|
||||
func (self *ViewDriver) IsVisible() *ViewDriver {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
lines := view.BufferLines()
|
||||
|
||||
// if the view has a single blank line (often the case) we want to treat that as having no lines
|
||||
if len(lines) == 1 && expectedCount == 1 {
|
||||
actual := strings.TrimSpace(view.Buffer())
|
||||
return actual != "", fmt.Sprintf("unexpected number of lines in view '%s'. Expected 1, got 0", view.Name())
|
||||
}
|
||||
|
||||
return len(lines) == expectedCount, fmt.Sprintf("unexpected number of lines in view '%s'. Expected %d, got %d", view.Name(), expectedCount, len(lines))
|
||||
return self.getView().Visible, fmt.Sprintf("%s: Expected view to be visible, but it was not", self.context)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *ViewDriver) IsInvisible() *ViewDriver {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
return !self.getView().Visible, fmt.Sprintf("%s: Expected view to be visible, but it was not", self.context)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// will filter or search depending on whether the view supports filtering/searching
|
||||
func (self *ViewDriver) FilterOrSearch(text string) *ViewDriver {
|
||||
self.IsFocused()
|
||||
|
||||
self.Press(self.t.keys.Universal.StartSearch).
|
||||
Tap(func() {
|
||||
self.t.ExpectSearch().
|
||||
Type(text).
|
||||
Confirm()
|
||||
|
||||
self.t.Views().Search().IsVisible().Content(Contains(fmt.Sprintf("matches for '%s'", text)))
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// for when you want to make some assertion unrelated to the current view
|
||||
// without breaking the method chain
|
||||
func (self *ViewDriver) Tap(f func()) *ViewDriver {
|
||||
|
@ -42,6 +42,7 @@ var Search = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Press(keys.Universal.StartSearch).
|
||||
Tap(func() {
|
||||
t.ExpectSearch().
|
||||
Clear().
|
||||
Type("o").
|
||||
Confirm()
|
||||
|
||||
|
117
pkg/integration/tests/file/discard_all_dir_changes.go
Normal file
117
pkg/integration/tests/file/discard_all_dir_changes.go
Normal file
@ -0,0 +1,117 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var DiscardAllDirChanges = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Discarding all changes in a directory",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
// typically we would use more bespoke shell methods here, but I struggled to find a way to do that,
|
||||
// and this is copied over from a legacy integration test which did everything in a big shell script
|
||||
// so I'm just copying it across.
|
||||
|
||||
shell.CreateDir("dir")
|
||||
|
||||
// common stuff
|
||||
shell.RunShellCommand(`echo test > dir/both-deleted.txt`)
|
||||
shell.RunShellCommand(`git checkout -b conflict && git add dir/both-deleted.txt`)
|
||||
shell.RunShellCommand(`echo bothmodded > dir/both-modded.txt && git add dir/both-modded.txt`)
|
||||
shell.RunShellCommand(`echo haha > dir/deleted-them.txt && git add dir/deleted-them.txt`)
|
||||
shell.RunShellCommand(`echo haha2 > dir/deleted-us.txt && git add dir/deleted-us.txt`)
|
||||
shell.RunShellCommand(`echo mod > dir/modded.txt && git add dir/modded.txt`)
|
||||
shell.RunShellCommand(`echo mod > dir/modded-staged.txt && git add dir/modded-staged.txt`)
|
||||
shell.RunShellCommand(`echo del > dir/deleted.txt && git add dir/deleted.txt`)
|
||||
shell.RunShellCommand(`echo del > dir/deleted-staged.txt && git add dir/deleted-staged.txt`)
|
||||
shell.RunShellCommand(`echo change-delete > dir/change-delete.txt && git add dir/change-delete.txt`)
|
||||
shell.RunShellCommand(`echo delete-change > dir/delete-change.txt && git add dir/delete-change.txt`)
|
||||
shell.RunShellCommand(`echo double-modded > dir/double-modded.txt && git add dir/double-modded.txt`)
|
||||
shell.RunShellCommand(`echo "renamed\nhaha" > dir/renamed.txt && git add dir/renamed.txt`)
|
||||
shell.RunShellCommand(`git commit -m one`)
|
||||
|
||||
// stuff on other branch
|
||||
shell.RunShellCommand(`git branch conflict_second && git mv dir/both-deleted.txt dir/added-them-changed-us.txt`)
|
||||
shell.RunShellCommand(`git commit -m "dir/both-deleted.txt renamed in dir/added-them-changed-us.txt"`)
|
||||
shell.RunShellCommand(`echo blah > dir/both-added.txt && git add dir/both-added.txt`)
|
||||
shell.RunShellCommand(`echo mod1 > dir/both-modded.txt && git add dir/both-modded.txt`)
|
||||
shell.RunShellCommand(`rm dir/deleted-them.txt && git add dir/deleted-them.txt`)
|
||||
shell.RunShellCommand(`echo modded > dir/deleted-us.txt && git add dir/deleted-us.txt`)
|
||||
shell.RunShellCommand(`git commit -m "two"`)
|
||||
|
||||
// stuff on our branch
|
||||
shell.RunShellCommand(`git checkout conflict_second`)
|
||||
shell.RunShellCommand(`git mv dir/both-deleted.txt dir/changed-them-added-us.txt`)
|
||||
shell.RunShellCommand(`git commit -m "both-deleted.txt renamed in dir/changed-them-added-us.txt"`)
|
||||
shell.RunShellCommand(`echo mod2 > dir/both-modded.txt && git add dir/both-modded.txt`)
|
||||
shell.RunShellCommand(`echo blah2 > dir/both-added.txt && git add dir/both-added.txt`)
|
||||
shell.RunShellCommand(`echo modded > dir/deleted-them.txt && git add dir/deleted-them.txt`)
|
||||
shell.RunShellCommand(`rm dir/deleted-us.txt && git add dir/deleted-us.txt`)
|
||||
shell.RunShellCommand(`git commit -m "three"`)
|
||||
shell.RunShellCommand(`git reset --hard conflict_second`)
|
||||
shell.RunCommandExpectError([]string{"git", "merge", "conflict"})
|
||||
|
||||
shell.RunShellCommand(`echo "new" > dir/new.txt`)
|
||||
shell.RunShellCommand(`echo "new staged" > dir/new-staged.txt && git add dir/new-staged.txt`)
|
||||
shell.RunShellCommand(`echo mod2 > dir/modded.txt`)
|
||||
shell.RunShellCommand(`echo mod2 > dir/modded-staged.txt && git add dir/modded-staged.txt`)
|
||||
shell.RunShellCommand(`rm dir/deleted.txt`)
|
||||
shell.RunShellCommand(`rm dir/deleted-staged.txt && git add dir/deleted-staged.txt`)
|
||||
shell.RunShellCommand(`echo change-delete2 > dir/change-delete.txt && git add dir/change-delete.txt`)
|
||||
shell.RunShellCommand(`rm dir/change-delete.txt`)
|
||||
shell.RunShellCommand(`rm dir/delete-change.txt && git add dir/delete-change.txt`)
|
||||
shell.RunShellCommand(`echo "changed" > dir/delete-change.txt`)
|
||||
shell.RunShellCommand(`echo "change1" > dir/double-modded.txt && git add dir/double-modded.txt`)
|
||||
shell.RunShellCommand(`echo "change2" > dir/double-modded.txt`)
|
||||
shell.RunShellCommand(`echo before > dir/added-changed.txt && git add dir/added-changed.txt`)
|
||||
shell.RunShellCommand(`echo after > dir/added-changed.txt`)
|
||||
shell.RunShellCommand(`rm dir/renamed.txt && git add dir/renamed.txt`)
|
||||
shell.RunShellCommand(`echo "renamed\nhaha" > dir/renamed2.txt && git add dir/renamed2.txt`)
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("dir").IsSelected(),
|
||||
Contains("UA").Contains("added-them-changed-us.txt"),
|
||||
Contains("AA").Contains("both-added.txt"),
|
||||
Contains("DD").Contains("both-deleted.txt"),
|
||||
Contains("UU").Contains("both-modded.txt"),
|
||||
Contains("AU").Contains("changed-them-added-us.txt"),
|
||||
Contains("UD").Contains("deleted-them.txt"),
|
||||
Contains("DU").Contains("deleted-us.txt"),
|
||||
).
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Equals("dir")).
|
||||
Select(Contains("Discard all changes")).
|
||||
Confirm()
|
||||
}).
|
||||
Tap(func() {
|
||||
t.Common().ContinueOnConflictsResolved()
|
||||
}).
|
||||
Lines(
|
||||
Contains("dir").IsSelected(),
|
||||
Contains(" M").Contains("added-changed.txt"),
|
||||
Contains(" D").Contains("change-delete.txt"),
|
||||
Contains("??").Contains("delete-change.txt"),
|
||||
Contains(" D").Contains("deleted.txt"),
|
||||
Contains(" M").Contains("double-modded.txt"),
|
||||
Contains(" M").Contains("modded.txt"),
|
||||
Contains("??").Contains("new.txt"),
|
||||
).
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Equals("dir")).
|
||||
Select(Contains("Discard all changes")).
|
||||
Confirm()
|
||||
}).
|
||||
IsEmpty()
|
||||
},
|
||||
})
|
56
pkg/integration/tests/file/discard_unstaged_dir_changes.go
Normal file
56
pkg/integration/tests/file/discard_unstaged_dir_changes.go
Normal file
@ -0,0 +1,56 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var DiscardUnstagedDirChanges = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Discarding unstaged changes in a directory",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateDir("dir")
|
||||
shell.CreateFileAndAdd("dir/file-one", "original content\n")
|
||||
|
||||
shell.Commit("first commit")
|
||||
|
||||
shell.UpdateFileAndAdd("dir/file-one", "original content\nnew content\n")
|
||||
shell.UpdateFile("dir/file-one", "original content\nnew content\neven newer content\n")
|
||||
|
||||
shell.CreateDir("dir/subdir")
|
||||
shell.CreateFile("dir/subdir/unstaged-file-one", "unstaged file")
|
||||
shell.CreateFile("dir/unstaged-file-two", "unstaged file")
|
||||
|
||||
shell.CreateFile("unstaged-file-three", "unstaged file")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("dir").IsSelected(),
|
||||
Contains("subdir"),
|
||||
Contains("??").Contains("unstaged-file-one"),
|
||||
Contains("MM").Contains("file-one"),
|
||||
Contains("??").Contains("unstaged-file-two"),
|
||||
Contains("??").Contains("unstaged-file-three"),
|
||||
).
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Equals("dir")).
|
||||
Select(Contains("Discard unstaged changes")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
Contains("dir").IsSelected(),
|
||||
Contains("M ").Contains("file-one"),
|
||||
// this guy remains untouched because it wasn't inside the 'dir' directory
|
||||
Contains("??").Contains("unstaged-file-three"),
|
||||
)
|
||||
|
||||
t.FileSystem().FileContent("dir/file-one", Equals("original content\nnew content\n"))
|
||||
},
|
||||
})
|
41
pkg/integration/tests/file/discard_unstaged_file_changes.go
Normal file
41
pkg/integration/tests/file/discard_unstaged_file_changes.go
Normal file
@ -0,0 +1,41 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var DiscardUnstagedFileChanges = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Discarding unstaged changes in a file",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file-one", "original content\n")
|
||||
|
||||
shell.Commit("first commit")
|
||||
|
||||
shell.UpdateFileAndAdd("file-one", "original content\nnew content\n")
|
||||
shell.UpdateFile("file-one", "original content\nnew content\neven newer content\n")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("MM").Contains("file-one").IsSelected(),
|
||||
).
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Equals("file-one")).
|
||||
Select(Contains("Discard unstaged changes")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
Contains("M ").Contains("file-one").IsSelected(),
|
||||
)
|
||||
|
||||
t.FileSystem().FileContent("file-one", Equals("original content\nnew content\n"))
|
||||
},
|
||||
})
|
@ -0,0 +1,84 @@
|
||||
package filter_and_search
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var FilterCommitFiles = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Basic commit file filtering by text",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: true, // skipping until we have implemented file view filtering
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateDir("folder1")
|
||||
shell.CreateFileAndAdd("folder1/apple-grape", "apple-grape")
|
||||
shell.CreateFileAndAdd("folder1/apple-orange", "apple-orange")
|
||||
shell.CreateFileAndAdd("folder1/grape-orange", "grape-orange")
|
||||
shell.Commit("first commit")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains(`first commit`).IsSelected(),
|
||||
).
|
||||
Press(keys.Universal.Confirm)
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains(`folder1`).IsSelected(),
|
||||
Contains(`apple-grape`),
|
||||
Contains(`apple-orange`),
|
||||
Contains(`grape-orange`),
|
||||
).
|
||||
Press(keys.Files.ToggleTreeView).
|
||||
Lines(
|
||||
Contains(`folder1/apple-grape`).IsSelected(),
|
||||
Contains(`folder1/apple-orange`),
|
||||
Contains(`folder1/grape-orange`),
|
||||
).
|
||||
FilterOrSearch("apple").
|
||||
Lines(
|
||||
Contains(`folder1/apple-grape`).IsSelected(),
|
||||
Contains(`folder1/apple-orange`),
|
||||
).
|
||||
Press(keys.Files.ToggleTreeView).
|
||||
// filter still applies when we toggle tree view
|
||||
Lines(
|
||||
Contains(`folder1`),
|
||||
Contains(`apple-grape`).IsSelected(),
|
||||
Contains(`apple-orange`),
|
||||
).
|
||||
Press(keys.Files.ToggleTreeView).
|
||||
Lines(
|
||||
Contains(`folder1/apple-grape`).IsSelected(),
|
||||
Contains(`folder1/apple-orange`),
|
||||
).
|
||||
NavigateToLine(Contains(`folder1/apple-orange`)).
|
||||
Press(keys.Universal.Return).
|
||||
Lines(
|
||||
Contains(`folder1/apple-grape`),
|
||||
// selection is retained after escaping filter mode
|
||||
Contains(`folder1/apple-orange`).IsSelected(),
|
||||
Contains(`folder1/grape-orange`),
|
||||
).
|
||||
Tap(func() {
|
||||
t.Views().Search().IsInvisible()
|
||||
}).
|
||||
Press(keys.Files.ToggleTreeView).
|
||||
Lines(
|
||||
Contains(`folder1`),
|
||||
Contains(`apple-grape`),
|
||||
Contains(`apple-orange`).IsSelected(),
|
||||
Contains(`grape-orange`),
|
||||
).
|
||||
FilterOrSearch("folder1/grape").
|
||||
Lines(
|
||||
// first item is always selected after filtering
|
||||
Contains(`folder1`).IsSelected(),
|
||||
Contains(`grape-orange`),
|
||||
)
|
||||
},
|
||||
})
|
76
pkg/integration/tests/filter_and_search/filter_files.go
Normal file
76
pkg/integration/tests/filter_and_search/filter_files.go
Normal file
@ -0,0 +1,76 @@
|
||||
package filter_and_search
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var FilterFiles = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Basic file filtering by text",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: true, // Skipping until we have implemented file view filtering
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateDir("folder1")
|
||||
shell.CreateFile("folder1/apple-grape", "apple-grape")
|
||||
shell.CreateFile("folder1/apple-orange", "apple-orange")
|
||||
shell.CreateFile("folder1/grape-orange", "grape-orange")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains(`folder1`).IsSelected(),
|
||||
Contains(`apple-grape`),
|
||||
Contains(`apple-orange`),
|
||||
Contains(`grape-orange`),
|
||||
).
|
||||
Press(keys.Files.ToggleTreeView).
|
||||
Lines(
|
||||
Contains(`folder1/apple-grape`).IsSelected(),
|
||||
Contains(`folder1/apple-orange`),
|
||||
Contains(`folder1/grape-orange`),
|
||||
).
|
||||
FilterOrSearch("apple").
|
||||
Lines(
|
||||
Contains(`folder1/apple-grape`).IsSelected(),
|
||||
Contains(`folder1/apple-orange`),
|
||||
).
|
||||
Press(keys.Files.ToggleTreeView).
|
||||
// filter still applies when we toggle tree view
|
||||
Lines(
|
||||
Contains(`folder1`),
|
||||
Contains(`apple-grape`).IsSelected(),
|
||||
Contains(`apple-orange`),
|
||||
).
|
||||
Press(keys.Files.ToggleTreeView).
|
||||
Lines(
|
||||
Contains(`folder1/apple-grape`).IsSelected(),
|
||||
Contains(`folder1/apple-orange`),
|
||||
).
|
||||
NavigateToLine(Contains(`folder1/apple-orange`)).
|
||||
Press(keys.Universal.Return).
|
||||
Lines(
|
||||
Contains(`folder1/apple-grape`),
|
||||
// selection is retained after escaping filter mode
|
||||
Contains(`folder1/apple-orange`).IsSelected(),
|
||||
Contains(`folder1/grape-orange`),
|
||||
).
|
||||
Tap(func() {
|
||||
t.Views().Search().IsInvisible()
|
||||
}).
|
||||
Press(keys.Files.ToggleTreeView).
|
||||
Lines(
|
||||
Contains(`folder1`),
|
||||
Contains(`apple-grape`),
|
||||
Contains(`apple-orange`).IsSelected(),
|
||||
Contains(`grape-orange`),
|
||||
).
|
||||
FilterOrSearch("folder1/grape").
|
||||
Lines(
|
||||
// first item is always selected after filtering
|
||||
Contains(`folder1`).IsSelected(),
|
||||
Contains(`grape-orange`),
|
||||
)
|
||||
},
|
||||
})
|
48
pkg/integration/tests/filter_and_search/filter_menu.go
Normal file
48
pkg/integration/tests/filter_and_search/filter_menu.go
Normal file
@ -0,0 +1,48 @@
|
||||
package filter_and_search
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var FilterMenu = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Filtering the keybindings menu",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFile("myfile", "myfile")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains(`??`).Contains(`myfile`).IsSelected(),
|
||||
).
|
||||
Press(keys.Universal.OptionMenu).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Equals("Keybindings")).
|
||||
Filter("Toggle staged").
|
||||
Lines(
|
||||
// menu has filtered down to the one item that matches the filter
|
||||
Contains(`Toggle staged`).IsSelected(),
|
||||
).
|
||||
Confirm()
|
||||
})
|
||||
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
// file has been staged
|
||||
Contains(`A `).Contains(`myfile`).IsSelected(),
|
||||
).
|
||||
// Upon opening the menu again, the filter should have been reset
|
||||
Press(keys.Universal.OptionMenu).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Equals("Keybindings")).
|
||||
LineCount(GreaterThan(1))
|
||||
})
|
||||
},
|
||||
})
|
@ -0,0 +1,59 @@
|
||||
package filter_and_search
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var FilterRemoteBranches = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Filtering remote branches",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.NewBranch("branch-apple")
|
||||
shell.EmptyCommit("commit-one")
|
||||
shell.NewBranch("branch-grape")
|
||||
shell.NewBranch("branch-orange")
|
||||
|
||||
shell.CloneIntoRemote("origin")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Remotes().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains(`origin`).IsSelected(),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().RemoteBranches().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains(`branch-apple`).IsSelected(),
|
||||
Contains(`branch-grape`),
|
||||
Contains(`branch-orange`),
|
||||
).
|
||||
FilterOrSearch("grape").
|
||||
Lines(
|
||||
Contains(`branch-grape`).IsSelected(),
|
||||
).
|
||||
// cancel the filter
|
||||
PressEscape().
|
||||
Tap(func() {
|
||||
t.Views().Search().IsInvisible()
|
||||
}).
|
||||
Lines(
|
||||
Contains(`branch-apple`),
|
||||
Contains(`branch-grape`).IsSelected(),
|
||||
Contains(`branch-orange`),
|
||||
).
|
||||
// return to remotes view
|
||||
PressEscape()
|
||||
|
||||
t.Views().Remotes().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains(`origin`).IsSelected(),
|
||||
)
|
||||
},
|
||||
})
|
151
pkg/integration/tests/filter_and_search/nested_filter.go
Normal file
151
pkg/integration/tests/filter_and_search/nested_filter.go
Normal file
@ -0,0 +1,151 @@
|
||||
package filter_and_search
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var NestedFilter = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Filter in the several nested panels and verify the filters are preserved as you escape back to the surface",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
// need to create some branches, each with their own commits
|
||||
shell.NewBranch("branch-gold")
|
||||
shell.CreateFileAndAdd("apple", "apple")
|
||||
shell.CreateFileAndAdd("orange", "orange")
|
||||
shell.CreateFileAndAdd("grape", "grape")
|
||||
shell.Commit("commit-knife")
|
||||
|
||||
shell.NewBranch("branch-silver")
|
||||
shell.UpdateFileAndAdd("apple", "apple-2")
|
||||
shell.UpdateFileAndAdd("orange", "orange-2")
|
||||
shell.UpdateFileAndAdd("grape", "grape-2")
|
||||
shell.Commit("commit-spoon")
|
||||
|
||||
shell.NewBranch("branch-bronze")
|
||||
shell.UpdateFileAndAdd("apple", "apple-3")
|
||||
shell.UpdateFileAndAdd("orange", "orange-3")
|
||||
shell.UpdateFileAndAdd("grape", "grape-3")
|
||||
shell.Commit("commit-fork")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains(`branch-bronze`).IsSelected(),
|
||||
Contains(`branch-silver`),
|
||||
Contains(`branch-gold`),
|
||||
).
|
||||
FilterOrSearch("sil").
|
||||
Lines(
|
||||
Contains(`branch-silver`).IsSelected(),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().SubCommits().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains(`commit-spoon`).IsSelected(),
|
||||
Contains(`commit-knife`),
|
||||
).
|
||||
FilterOrSearch("knife").
|
||||
Lines(
|
||||
// sub-commits view searches, it doesn't filter, so we haven't filtered down the list
|
||||
Contains(`commit-spoon`),
|
||||
Contains(`commit-knife`).IsSelected(),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains(`apple`).IsSelected(),
|
||||
Contains(`grape`),
|
||||
Contains(`orange`),
|
||||
).
|
||||
FilterOrSearch("grape").
|
||||
Lines(
|
||||
Contains(`apple`),
|
||||
Contains(`grape`).IsSelected(),
|
||||
Contains(`orange`),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().PatchBuilding().
|
||||
IsFocused().
|
||||
FilterOrSearch("newline").
|
||||
SelectedLine(Contains("No newline at end of file")).
|
||||
PressEscape(). // cancel search
|
||||
Tap(func() {
|
||||
t.Views().Search().IsInvisible()
|
||||
}).
|
||||
// escape to commit-files view
|
||||
PressEscape()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains(`apple`),
|
||||
Contains(`grape`).IsSelected(),
|
||||
Contains(`orange`),
|
||||
).
|
||||
Tap(func() {
|
||||
t.Views().Search().IsVisible().Content(Contains("matches for 'grape'"))
|
||||
}).
|
||||
// cancel search
|
||||
PressEscape().
|
||||
Tap(func() {
|
||||
t.Views().Search().IsInvisible()
|
||||
}).
|
||||
Lines(
|
||||
Contains(`apple`),
|
||||
Contains(`grape`).IsSelected(),
|
||||
Contains(`orange`),
|
||||
).
|
||||
// escape to sub-commits view
|
||||
PressEscape()
|
||||
|
||||
t.Views().SubCommits().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains(`commit-spoon`),
|
||||
Contains(`commit-knife`).IsSelected(),
|
||||
).
|
||||
Tap(func() {
|
||||
t.Views().Search().IsVisible().Content(Contains("matches for 'knife'"))
|
||||
}).
|
||||
// cancel search
|
||||
PressEscape().
|
||||
Tap(func() {
|
||||
t.Views().Search().IsInvisible()
|
||||
}).
|
||||
Lines(
|
||||
Contains(`commit-spoon`),
|
||||
// still selected
|
||||
Contains(`commit-knife`).IsSelected(),
|
||||
).
|
||||
// escape to branches view
|
||||
PressEscape()
|
||||
|
||||
t.Views().Branches().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains(`branch-silver`).IsSelected(),
|
||||
).
|
||||
Tap(func() {
|
||||
t.Views().Search().IsVisible().Content(Contains("matches for 'sil'"))
|
||||
}).
|
||||
// cancel search
|
||||
PressEscape().
|
||||
Tap(func() {
|
||||
t.Views().Search().IsInvisible()
|
||||
}).
|
||||
Lines(
|
||||
Contains(`branch-bronze`),
|
||||
Contains(`branch-silver`).IsSelected(),
|
||||
Contains(`branch-gold`),
|
||||
)
|
||||
},
|
||||
})
|
@ -0,0 +1,106 @@
|
||||
package filter_and_search
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
// This one requires some explanation: the sub-commits and diff-file contexts are
|
||||
// 'transient' in that they are spawned inside a window when you need them, but
|
||||
// can be relocated elsewhere if you need them somewhere else. So for example if
|
||||
// I hit enter on a branch I'll see the sub-commits view, but if I then navigate
|
||||
// to the reflog context and hit enter on a reflog, the sub-commits view is moved
|
||||
// to the reflog window. This is because we re-use the same view (it's a limitation
|
||||
// that would be nice to remove in the future).
|
||||
// Nonetheless, we need to ensure that upon moving the view, the filter is cancelled.
|
||||
|
||||
var NestedFilterTransient = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Filter in a transient panel (sub-commits and diff-files) and ensure filter is cancelled when the panel is moved",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
// need to create some branches, each with their own commits
|
||||
shell.NewBranch("mybranch")
|
||||
shell.CreateFileAndAdd("file-one", "file-one")
|
||||
shell.CreateFileAndAdd("file-two", "file-two")
|
||||
shell.Commit("commit-one")
|
||||
shell.EmptyCommit("commit-two")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains(`mybranch`).IsSelected(),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().SubCommits().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains(`commit-two`).IsSelected(),
|
||||
Contains(`commit-one`),
|
||||
).
|
||||
FilterOrSearch("one").
|
||||
Lines(
|
||||
Contains(`commit-two`),
|
||||
Contains(`commit-one`).IsSelected(),
|
||||
)
|
||||
|
||||
t.Views().ReflogCommits().
|
||||
Focus().
|
||||
SelectedLine(Contains("commit: commit-two")).
|
||||
PressEnter()
|
||||
|
||||
t.Views().SubCommits().
|
||||
IsFocused().
|
||||
// the search on the sub-commits context has been cancelled
|
||||
Lines(
|
||||
Contains(`commit-two`).IsSelected(),
|
||||
Contains(`commit-one`),
|
||||
).
|
||||
Tap(func() {
|
||||
t.Views().Search().IsInvisible()
|
||||
}).
|
||||
NavigateToLine(Contains("commit-one")).
|
||||
PressEnter()
|
||||
|
||||
// Now let's test the commit files context
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains(`file-one`).IsSelected(),
|
||||
Contains(`file-two`),
|
||||
).
|
||||
FilterOrSearch("two").
|
||||
Lines(
|
||||
Contains(`file-one`),
|
||||
Contains(`file-two`).IsSelected(),
|
||||
)
|
||||
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
SelectedLine(Contains("mybranch")).
|
||||
PressEnter()
|
||||
|
||||
t.Views().SubCommits().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains(`commit-two`).IsSelected(),
|
||||
Contains(`commit-one`),
|
||||
).
|
||||
NavigateToLine(Contains("commit-one")).
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
// the search on the commit-files context has been cancelled
|
||||
Lines(
|
||||
Contains(`file-one`).IsSelected(),
|
||||
Contains(`file-two`),
|
||||
).
|
||||
Tap(func() {
|
||||
t.Views().Search().IsInvisible()
|
||||
})
|
||||
},
|
||||
})
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/custom_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/diff"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/file"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/filter_and_search"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/filter_by_path"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/interactive_rebase"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/misc"
|
||||
@ -87,10 +88,19 @@ var tests = []*components.IntegrationTest{
|
||||
diff.DiffCommits,
|
||||
diff.IgnoreWhitespace,
|
||||
file.DirWithUntrackedFile,
|
||||
file.DiscardAllDirChanges,
|
||||
file.DiscardChanges,
|
||||
file.DiscardStagedChanges,
|
||||
file.DiscardUnstagedDirChanges,
|
||||
file.DiscardUnstagedFileChanges,
|
||||
file.Gitignore,
|
||||
file.RememberCommitMessageAfterFail,
|
||||
filter_and_search.FilterCommitFiles,
|
||||
filter_and_search.FilterFiles,
|
||||
filter_and_search.FilterMenu,
|
||||
filter_and_search.FilterRemoteBranches,
|
||||
filter_and_search.NestedFilter,
|
||||
filter_and_search.NestedFilterTransient,
|
||||
filter_by_path.CliArg,
|
||||
filter_by_path.SelectFile,
|
||||
filter_by_path.TypeFile,
|
||||
|
@ -19,6 +19,9 @@ var (
|
||||
// InactiveBorderColor is the border color of the inactive active frames
|
||||
InactiveBorderColor gocui.Attribute
|
||||
|
||||
// FilteredActiveBorderColor is the border color of the active frame, when it's being searched/filtered
|
||||
SearchingActiveBorderColor gocui.Attribute
|
||||
|
||||
// GocuiSelectedLineBgColor is the background color for the selected line in gocui
|
||||
GocuiSelectedLineBgColor gocui.Attribute
|
||||
|
||||
@ -44,6 +47,7 @@ var (
|
||||
func UpdateTheme(themeConfig config.ThemeConfig) {
|
||||
ActiveBorderColor = GetGocuiStyle(themeConfig.ActiveBorderColor)
|
||||
InactiveBorderColor = GetGocuiStyle(themeConfig.InactiveBorderColor)
|
||||
SearchingActiveBorderColor = GetGocuiStyle(themeConfig.SearchingActiveBorderColor)
|
||||
SelectedLineBgColor = GetTextStyle(themeConfig.SelectedLineBgColor, true)
|
||||
SelectedRangeBgColor = GetTextStyle(themeConfig.SelectedRangeBgColor, true)
|
||||
|
||||
|
@ -1,21 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/sahilm/fuzzy"
|
||||
)
|
||||
|
||||
func FuzzySearch(needle string, haystack []string) []string {
|
||||
if needle == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
matches := fuzzy.Find(needle, haystack)
|
||||
sort.Sort(matches)
|
||||
|
||||
return slices.Map(matches, func(match fuzzy.Match) string {
|
||||
return match.Str
|
||||
})
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestFuzzySearch is a function.
|
||||
func TestFuzzySearch(t *testing.T) {
|
||||
type scenario struct {
|
||||
needle string
|
||||
haystack []string
|
||||
expected []string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
needle: "",
|
||||
haystack: []string{"test"},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
needle: "test",
|
||||
haystack: []string{"test"},
|
||||
expected: []string{"test"},
|
||||
},
|
||||
{
|
||||
needle: "o",
|
||||
haystack: []string{"a", "o", "e"},
|
||||
expected: []string{"o"},
|
||||
},
|
||||
{
|
||||
needle: "mybranch",
|
||||
haystack: []string{"my_branch", "mybranch", "branch", "this is my branch"},
|
||||
expected: []string{"mybranch", "my_branch", "this is my branch"},
|
||||
},
|
||||
{
|
||||
needle: "test",
|
||||
haystack: []string{"not a good match", "this 'test' is a good match", "test"},
|
||||
expected: []string{"test", "this 'test' is a good match"},
|
||||
},
|
||||
{
|
||||
needle: "test",
|
||||
haystack: []string{"Test"},
|
||||
expected: []string{"Test"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
assert.EqualValues(t, s.expected, FuzzySearch(s.needle, s.haystack))
|
||||
}
|
||||
}
|
48
pkg/utils/search.go
Normal file
48
pkg/utils/search.go
Normal file
@ -0,0 +1,48 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/sahilm/fuzzy"
|
||||
)
|
||||
|
||||
func FuzzySearch(needle string, haystack []string) []string {
|
||||
if needle == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
matches := fuzzy.Find(needle, haystack)
|
||||
sort.Sort(matches)
|
||||
|
||||
return slices.Map(matches, func(match fuzzy.Match) string {
|
||||
return match.Str
|
||||
})
|
||||
}
|
||||
|
||||
func CaseAwareContains(haystack, needle string) bool {
|
||||
// if needle contains an uppercase letter, we'll do a case sensitive search
|
||||
if ContainsUppercase(needle) {
|
||||
return strings.Contains(haystack, needle)
|
||||
}
|
||||
|
||||
return CaseInsensitiveContains(haystack, needle)
|
||||
}
|
||||
|
||||
func ContainsUppercase(s string) bool {
|
||||
for _, r := range s {
|
||||
if r >= 'A' && r <= 'Z' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func CaseInsensitiveContains(haystack, needle string) bool {
|
||||
return strings.Contains(
|
||||
strings.ToLower(haystack),
|
||||
strings.ToLower(needle),
|
||||
)
|
||||
}
|
80
pkg/utils/search_test.go
Normal file
80
pkg/utils/search_test.go
Normal file
@ -0,0 +1,80 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestFuzzySearch is a function.
|
||||
func TestFuzzySearch(t *testing.T) {
|
||||
type scenario struct {
|
||||
needle string
|
||||
haystack []string
|
||||
expected []string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
needle: "",
|
||||
haystack: []string{"test"},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
needle: "test",
|
||||
haystack: []string{"test"},
|
||||
expected: []string{"test"},
|
||||
},
|
||||
{
|
||||
needle: "o",
|
||||
haystack: []string{"a", "o", "e"},
|
||||
expected: []string{"o"},
|
||||
},
|
||||
{
|
||||
needle: "mybranch",
|
||||
haystack: []string{"my_branch", "mybranch", "branch", "this is my branch"},
|
||||
expected: []string{"mybranch", "my_branch", "this is my branch"},
|
||||
},
|
||||
{
|
||||
needle: "test",
|
||||
haystack: []string{"not a good match", "this 'test' is a good match", "test"},
|
||||
expected: []string{"test", "this 'test' is a good match"},
|
||||
},
|
||||
{
|
||||
needle: "test",
|
||||
haystack: []string{"Test"},
|
||||
expected: []string{"Test"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
assert.EqualValues(t, s.expected, FuzzySearch(s.needle, s.haystack))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaseInsensitiveContains(t *testing.T) {
|
||||
testCases := []struct {
|
||||
haystack string
|
||||
needle string
|
||||
expected bool
|
||||
}{
|
||||
{"Hello, World!", "world", true}, // Case-insensitive match
|
||||
{"Hello, World!", "WORLD", true}, // Case-insensitive match
|
||||
{"Hello, World!", "orl", true}, // Case-insensitive match
|
||||
{"Hello, World!", "o, W", true}, // Case-insensitive match
|
||||
{"Hello, World!", "hello", true}, // Case-insensitive match
|
||||
{"Hello, World!", "Foo", false}, // No match
|
||||
{"Hello, World!", "Hello, World!!", false}, // No match
|
||||
{"Hello, World!", "", true}, // Empty needle matches
|
||||
{"", "Hello", false}, // Empty haystack doesn't match
|
||||
{"", "", true}, // Empty strings match
|
||||
{"", " ", false}, // Empty haystack, non-empty needle
|
||||
{" ", "", true}, // Non-empty haystack, empty needle
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := CaseInsensitiveContains(testCase.haystack, testCase.needle)
|
||||
assert.Equal(t, testCase.expected, result, fmt.Sprintf("Test case %d failed. Expected '%v', got '%v' for '%s' in '%s'", i, testCase.expected, result, testCase.needle, testCase.haystack))
|
||||
}
|
||||
}
|
@ -113,3 +113,14 @@ func MoveElement[T any](slice []T, from int, to int) []T {
|
||||
|
||||
return newSlice
|
||||
}
|
||||
|
||||
func ValuesAtIndices[T any](slice []T, indices []int) []T {
|
||||
result := make([]T, len(indices))
|
||||
for i, index := range indices {
|
||||
// gracefully handling the situation where the index is out of bounds
|
||||
if index < len(slice) {
|
||||
result[i] = slice[index]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
4
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
4
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
@ -207,6 +207,10 @@ func (v *View) gotoPreviousMatch() error {
|
||||
return v.SelectSearchResult(v.searcher.currentSearchIndex)
|
||||
}
|
||||
|
||||
func (v *View) SelectCurrentSearchResult() error {
|
||||
return v.SelectSearchResult(v.searcher.currentSearchIndex)
|
||||
}
|
||||
|
||||
func (v *View) SelectSearchResult(index int) error {
|
||||
itemCount := len(v.searcher.searchPositions)
|
||||
if itemCount == 0 {
|
||||
|
4
vendor/golang.org/x/sys/cpu/endian_little.go
generated
vendored
4
vendor/golang.org/x/sys/cpu/endian_little.go
generated
vendored
@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build 386 || amd64 || amd64p32 || alpha || arm || arm64 || loong64 || mipsle || mips64le || mips64p32le || nios2 || ppc64le || riscv || riscv64 || sh
|
||||
// +build 386 amd64 amd64p32 alpha arm arm64 loong64 mipsle mips64le mips64p32le nios2 ppc64le riscv riscv64 sh
|
||||
//go:build 386 || amd64 || amd64p32 || alpha || arm || arm64 || loong64 || mipsle || mips64le || mips64p32le || nios2 || ppc64le || riscv || riscv64 || sh || wasm
|
||||
// +build 386 amd64 amd64p32 alpha arm arm64 loong64 mipsle mips64le mips64p32le nios2 ppc64le riscv riscv64 sh wasm
|
||||
|
||||
package cpu
|
||||
|
||||
|
2
vendor/golang.org/x/sys/unix/mkall.sh
generated
vendored
2
vendor/golang.org/x/sys/unix/mkall.sh
generated
vendored
@ -50,7 +50,7 @@ if [[ "$GOOS" = "linux" ]]; then
|
||||
# Use the Docker-based build system
|
||||
# Files generated through docker (use $cmd so you can Ctl-C the build or run)
|
||||
$cmd docker build --tag generate:$GOOS $GOOS
|
||||
$cmd docker run --interactive --tty --volume $(cd -- "$(dirname -- "$0")/.." && /bin/pwd):/build generate:$GOOS
|
||||
$cmd docker run --interactive --tty --volume $(cd -- "$(dirname -- "$0")/.." && pwd):/build generate:$GOOS
|
||||
exit
|
||||
fi
|
||||
|
||||
|
6
vendor/golang.org/x/sys/unix/mkerrors.sh
generated
vendored
6
vendor/golang.org/x/sys/unix/mkerrors.sh
generated
vendored
@ -741,7 +741,8 @@ main(void)
|
||||
e = errors[i].num;
|
||||
if(i > 0 && errors[i-1].num == e)
|
||||
continue;
|
||||
strcpy(buf, strerror(e));
|
||||
strncpy(buf, strerror(e), sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
// lowercase first letter: Bad -> bad, but STREAM -> STREAM.
|
||||
if(A <= buf[0] && buf[0] <= Z && a <= buf[1] && buf[1] <= z)
|
||||
buf[0] += a - A;
|
||||
@ -760,7 +761,8 @@ main(void)
|
||||
e = signals[i].num;
|
||||
if(i > 0 && signals[i-1].num == e)
|
||||
continue;
|
||||
strcpy(buf, strsignal(e));
|
||||
strncpy(buf, strsignal(e), sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
// lowercase first letter: Bad -> bad, but STREAM -> STREAM.
|
||||
if(A <= buf[0] && buf[0] <= Z && a <= buf[1] && buf[1] <= z)
|
||||
buf[0] += a - A;
|
||||
|
30
vendor/golang.org/x/sys/unix/syscall_linux.go
generated
vendored
30
vendor/golang.org/x/sys/unix/syscall_linux.go
generated
vendored
@ -1699,12 +1699,23 @@ func PtracePokeUser(pid int, addr uintptr, data []byte) (count int, err error) {
|
||||
return ptracePoke(PTRACE_POKEUSR, PTRACE_PEEKUSR, pid, addr, data)
|
||||
}
|
||||
|
||||
// elfNT_PRSTATUS is a copy of the debug/elf.NT_PRSTATUS constant so
|
||||
// x/sys/unix doesn't need to depend on debug/elf and thus
|
||||
// compress/zlib, debug/dwarf, and other packages.
|
||||
const elfNT_PRSTATUS = 1
|
||||
|
||||
func PtraceGetRegs(pid int, regsout *PtraceRegs) (err error) {
|
||||
return ptracePtr(PTRACE_GETREGS, pid, 0, unsafe.Pointer(regsout))
|
||||
var iov Iovec
|
||||
iov.Base = (*byte)(unsafe.Pointer(regsout))
|
||||
iov.SetLen(int(unsafe.Sizeof(*regsout)))
|
||||
return ptracePtr(PTRACE_GETREGSET, pid, uintptr(elfNT_PRSTATUS), unsafe.Pointer(&iov))
|
||||
}
|
||||
|
||||
func PtraceSetRegs(pid int, regs *PtraceRegs) (err error) {
|
||||
return ptracePtr(PTRACE_SETREGS, pid, 0, unsafe.Pointer(regs))
|
||||
var iov Iovec
|
||||
iov.Base = (*byte)(unsafe.Pointer(regs))
|
||||
iov.SetLen(int(unsafe.Sizeof(*regs)))
|
||||
return ptracePtr(PTRACE_SETREGSET, pid, uintptr(elfNT_PRSTATUS), unsafe.Pointer(&iov))
|
||||
}
|
||||
|
||||
func PtraceSetOptions(pid int, options int) (err error) {
|
||||
@ -2420,6 +2431,21 @@ func PthreadSigmask(how int, set, oldset *Sigset_t) error {
|
||||
return rtSigprocmask(how, set, oldset, _C__NSIG/8)
|
||||
}
|
||||
|
||||
//sysnb getresuid(ruid *_C_int, euid *_C_int, suid *_C_int)
|
||||
//sysnb getresgid(rgid *_C_int, egid *_C_int, sgid *_C_int)
|
||||
|
||||
func Getresuid() (ruid, euid, suid int) {
|
||||
var r, e, s _C_int
|
||||
getresuid(&r, &e, &s)
|
||||
return int(r), int(e), int(s)
|
||||
}
|
||||
|
||||
func Getresgid() (rgid, egid, sgid int) {
|
||||
var r, e, s _C_int
|
||||
getresgid(&r, &e, &s)
|
||||
return int(r), int(e), int(s)
|
||||
}
|
||||
|
||||
/*
|
||||
* Unimplemented
|
||||
*/
|
||||
|
17
vendor/golang.org/x/sys/unix/syscall_openbsd.go
generated
vendored
17
vendor/golang.org/x/sys/unix/syscall_openbsd.go
generated
vendored
@ -151,6 +151,21 @@ func Getfsstat(buf []Statfs_t, flags int) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
//sysnb getresuid(ruid *_C_int, euid *_C_int, suid *_C_int)
|
||||
//sysnb getresgid(rgid *_C_int, egid *_C_int, sgid *_C_int)
|
||||
|
||||
func Getresuid() (ruid, euid, suid int) {
|
||||
var r, e, s _C_int
|
||||
getresuid(&r, &e, &s)
|
||||
return int(r), int(e), int(s)
|
||||
}
|
||||
|
||||
func Getresgid() (rgid, egid, sgid int) {
|
||||
var r, e, s _C_int
|
||||
getresgid(&r, &e, &s)
|
||||
return int(r), int(e), int(s)
|
||||
}
|
||||
|
||||
//sys ioctl(fd int, req uint, arg uintptr) (err error)
|
||||
//sys ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) = SYS_IOCTL
|
||||
|
||||
@ -338,8 +353,6 @@ func Uname(uname *Utsname) error {
|
||||
// getgid
|
||||
// getitimer
|
||||
// getlogin
|
||||
// getresgid
|
||||
// getresuid
|
||||
// getthrid
|
||||
// ktrace
|
||||
// lfs_bmapv
|
||||
|
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