diff --git a/docs-master/Config.md b/docs-master/Config.md index a7633cf33..8925b994c 100644 --- a/docs-master/Config.md +++ b/docs-master/Config.md @@ -697,6 +697,7 @@ keybinding: - "4" - "5" focusMainView: "0" + jumpToFile: nextMatch: "n" prevMatch: "N" startSearch: / diff --git a/docs-master/keybindings/Keybindings_en.md b/docs-master/keybindings/Keybindings_en.md index 2aac3e182..992f34dc9 100644 --- a/docs-master/keybindings/Keybindings_en.md +++ b/docs-master/keybindings/Keybindings_en.md @@ -24,6 +24,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` _ `` | Prev screen mode | | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. | | `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. | +| `` `` | Jump to file in diff | Pick one of the files of the diff shown in the main view, and scroll the main view to it. The focus stays in this panel. | | `` `` | Cancel | | | `` ? `` | Open keybindings menu | | | `` `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. | @@ -234,6 +235,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | Go to next hunk | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | Exit back to side panel | | | `` c `` | Commit | Commit staged changes. | @@ -317,6 +319,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | Go to next hunk | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | Exit back to side panel | | | `` c `` | Commit | Commit staged changes. | diff --git a/docs-master/keybindings/Keybindings_ja.md b/docs-master/keybindings/Keybindings_ja.md index d273dbd2e..33f8a59b3 100644 --- a/docs-master/keybindings/Keybindings_ja.md +++ b/docs-master/keybindings/Keybindings_ja.md @@ -24,6 +24,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` _ `` | 前の画面モード | | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. | | `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. | +| `` `` | Jump to file in diff | Pick one of the files of the diff shown in the main view, and scroll the main view to it. The focus stays in this panel. | | `` `` | キャンセル | | | `` ? `` | キーバインディングメニューを開く | | | `` `` | フィルターオプションを表示 | コミットログのフィルタリングオプションを表示し、フィルタに一致するコミットのみを表示します。 | @@ -203,6 +204,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | 次のハンクに移動 | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | サイドパネルに戻る | | | `` c `` | コミット | ステージされた変更をコミットします。 | @@ -294,6 +296,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | 次のハンクに移動 | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | サイドパネルに戻る | | | `` c `` | コミット | ステージされた変更をコミットします。 | diff --git a/docs-master/keybindings/Keybindings_ko.md b/docs-master/keybindings/Keybindings_ko.md index 4c28bfaa6..88a272d5b 100644 --- a/docs-master/keybindings/Keybindings_ko.md +++ b/docs-master/keybindings/Keybindings_ko.md @@ -24,6 +24,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` _ `` | 이전 스크린 모드 | | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. | | `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. | +| `` `` | Jump to file in diff | Pick one of the files of the diff shown in the main view, and scroll the main view to it. The focus stays in this panel. | | `` `` | 취소 | | | `` ? `` | 매뉴 열기 | | | `` `` | View filter-by-path options | View options for filtering the commit log, so that only commits matching the filter are shown. | @@ -95,6 +96,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | 다음 hunk를 선택 | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | Exit back to side panel | | | `` c `` | 커밋 변경내용 | 스테이징된 변경 사항 커밋. | @@ -189,6 +191,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | 다음 hunk를 선택 | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | Exit back to side panel | | | `` c `` | 커밋 변경내용 | 스테이징된 변경 사항 커밋. | diff --git a/docs-master/keybindings/Keybindings_nl.md b/docs-master/keybindings/Keybindings_nl.md index 6fb5291a9..df0d2fa25 100644 --- a/docs-master/keybindings/Keybindings_nl.md +++ b/docs-master/keybindings/Keybindings_nl.md @@ -24,6 +24,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` _ `` | Vorige scherm modus | | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. | | `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. | +| `` `` | Jump to file in diff | Pick one of the files of the diff shown in the main view, and scroll the main view to it. The focus stays in this panel. | | `` `` | Annuleren | | | `` ? `` | Open menu | | | `` `` | Bekijk scoping opties | View options for filtering the commit log, so that only commits matching the filter are shown. | @@ -242,6 +243,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | Selecteer de volgende hunk | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | Exit back to side panel | | | `` c `` | Commit veranderingen | Commit gestagede wijzigingen. | @@ -317,6 +319,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | Selecteer de volgende hunk | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | Exit back to side panel | | | `` c `` | Commit veranderingen | Commit gestagede wijzigingen. | diff --git a/docs-master/keybindings/Keybindings_pl.md b/docs-master/keybindings/Keybindings_pl.md index 46117e5fb..940a6181e 100644 --- a/docs-master/keybindings/Keybindings_pl.md +++ b/docs-master/keybindings/Keybindings_pl.md @@ -24,6 +24,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` _ `` | Poprzedni tryb ekranu | | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. | | `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. | +| `` `` | Jump to file in diff | Pick one of the files of the diff shown in the main view, and scroll the main view to it. The focus stays in this panel. | | `` `` | Anuluj | | | `` ? `` | Otwórz menu przypisań klawiszy | | | `` `` | Pokaż opcje filtrowania | Pokaż opcje filtrowania dziennika commitów, tak aby pokazywane były tylko commity pasujące do filtra. | @@ -110,6 +111,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | Idź do następnego fragmentu | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | Exit back to side panel | | | `` c `` | Commit | Zatwierdź zmiany zatwierdzone. | @@ -212,6 +214,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | Idź do następnego fragmentu | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | Exit back to side panel | | | `` c `` | Commit | Zatwierdź zmiany zatwierdzone. | diff --git a/docs-master/keybindings/Keybindings_pt.md b/docs-master/keybindings/Keybindings_pt.md index 704ccebf4..5ac753a74 100644 --- a/docs-master/keybindings/Keybindings_pt.md +++ b/docs-master/keybindings/Keybindings_pt.md @@ -24,6 +24,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` _ `` | Modo de tela anterior | | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. | | `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. | +| `` `` | Jump to file in diff | Pick one of the files of the diff shown in the main view, and scroll the main view to it. The focus stays in this panel. | | `` `` | Cancelar | | | `` ? `` | Abrir o menu de atalhos do teclado | | | `` `` | Ver opções de filtro | View options for filtering the commit log, so that only commits matching the filter are shown. | @@ -246,6 +247,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | Ir para o próximo trecho | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | Exit back to side panel | | | `` c `` | Commit | Submeter mudanças em staging | @@ -326,6 +328,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | Ir para o próximo trecho | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | Exit back to side panel | | | `` c `` | Commit | Submeter mudanças em staging | diff --git a/docs-master/keybindings/Keybindings_ru.md b/docs-master/keybindings/Keybindings_ru.md index 33f7da705..9726e8012 100644 --- a/docs-master/keybindings/Keybindings_ru.md +++ b/docs-master/keybindings/Keybindings_ru.md @@ -24,6 +24,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` _ `` | Предыдущий режим экрана | | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. | | `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. | +| `` `` | Jump to file in diff | Pick one of the files of the diff shown in the main view, and scroll the main view to it. The focus stays in this panel. | | `` `` | Отменить | | | `` ? `` | Открыть меню | | | `` `` | Просмотреть параметры фильтрации по пути | View options for filtering the commit log, so that only commits matching the filter are shown. | @@ -85,6 +86,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | Выбрать следующую часть | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | Exit back to side panel | | | `` c `` | Сохранить изменения | Commit staged changes. | @@ -111,6 +113,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | Выбрать следующую часть | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | Exit back to side panel | | | `` c `` | Сохранить изменения | Commit staged changes. | diff --git a/docs-master/keybindings/Keybindings_zh-CN.md b/docs-master/keybindings/Keybindings_zh-CN.md index 9b5afed6e..946b61812 100644 --- a/docs-master/keybindings/Keybindings_zh-CN.md +++ b/docs-master/keybindings/Keybindings_zh-CN.md @@ -24,6 +24,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` _ `` | 上一屏模式 | | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. | | `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. | +| `` `` | Jump to file in diff | Pick one of the files of the diff shown in the main view, and scroll the main view to it. The focus stays in this panel. | | `` `` | 取消 | | | `` ? `` | 打开菜单 | | | `` `` | 查看按路径过滤选项 | 查看用于过滤提交日志的选项,以便仅显示与过滤器匹配的提交。 | @@ -281,6 +282,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | 选择下一个区块 | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | 退出回到侧边面板 | | | `` c `` | 提交变更 | 提交暂存文件 | @@ -323,6 +325,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | 选择下一个区块 | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | 退出回到侧边面板 | | | `` c `` | 提交变更 | 提交暂存文件 | diff --git a/docs-master/keybindings/Keybindings_zh-TW.md b/docs-master/keybindings/Keybindings_zh-TW.md index bee7ce31f..af5625138 100644 --- a/docs-master/keybindings/Keybindings_zh-TW.md +++ b/docs-master/keybindings/Keybindings_zh-TW.md @@ -24,6 +24,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` _ `` | 上一個螢幕模式 | | | `` \| `` | 切換差異渲染器 | 選擇已設定的差異渲染器清單中的下一個渲染器。 | | `` \ `` | 切換差異渲染器(反向) | 選擇已設定的差異渲染器清單中的上一個渲染器。 | +| `` `` | Jump to file in diff | Pick one of the files of the diff shown in the main view, and scroll the main view to it. The focus stays in this panel. | | `` `` | 取消 | | | `` ? `` | 開啟選單 | | | `` `` | 檢視篩選路徑選項 | 檢視用於過濾提交日誌的選項,以便僅顯示與過濾器匹配的提交。 | @@ -70,6 +71,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | 選擇下一段 | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | 退出回到側邊面板 | | | `` c `` | 提交變更 | 提交暫存區變更 | @@ -345,6 +347,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , l `` | 選擇下一段 | | | `` N `` | Go to previous file | | | `` n `` | Go to next file | | +| `` `` | Jump to file | | | `` G `` | Open pull request at selected line | Open the branch's pull request in your browser, at the line the selection is on, so that you can comment on it there. Only pull requests on GitHub are found. | | `` `` | 退出回到側邊面板 | | | `` c `` | 提交變更 | 提交暫存區變更 | diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index ab0668201..f8013a5d1 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -511,6 +511,7 @@ type KeybindingUniversalConfig struct { PrevBlockAlt2 Keybinding `yaml:"prevBlock-alt2"` JumpToBlock []Keybinding `yaml:"jumpToBlock"` FocusMainView Keybinding `yaml:"focusMainView"` + JumpToFile Keybinding `yaml:"jumpToFile"` NextMatch Keybinding `yaml:"nextMatch"` PrevMatch Keybinding `yaml:"prevMatch"` StartSearch Keybinding `yaml:"startSearch"` @@ -1037,6 +1038,7 @@ func GetDefaultConfigForPlatform(platform string) *UserConfig { NextBlockAlt2: Keybinding{""}, JumpToBlock: []Keybinding{{"1"}, {"2"}, {"3"}, {"4"}, {"5"}}, FocusMainView: Keybinding{"0"}, + JumpToFile: Keybinding{""}, NextMatch: Keybinding{"n"}, PrevMatch: Keybinding{"N"}, StartSearch: Keybinding{"/"}, diff --git a/pkg/gui/controllers/global_controller.go b/pkg/gui/controllers/global_controller.go index f4de89567..a51b70fc5 100644 --- a/pkg/gui/controllers/global_controller.go +++ b/pkg/gui/controllers/global_controller.go @@ -3,6 +3,7 @@ package controllers import ( "strconv" + "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -74,6 +75,14 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type Description: self.c.Tr.CycleDiffRenderersReverse, Tooltip: self.c.Tr.CycleDiffRenderersReverseTooltip, }, + { + Keys: opts.GetKeys(opts.Config.Universal.JumpToFile), + Handler: opts.Guards.NoPopupPanel(self.jumpToFileInDiff), + Description: self.c.Tr.JumpToFileInDiff, + DescriptionFunc: self.jumpToFileInDiffDescription, + Tooltip: self.c.Tr.JumpToFileInDiffTooltip, + OpensMenu: true, + }, { Keys: opts.GetKeys(opts.Config.Universal.Return), Handler: self.escape, @@ -206,6 +215,58 @@ func (self *GlobalController) canCycleDiffRenderers() *types.DisabledReason { return nil } +// jumpToFileInDiff offers the files of the diff the main section is showing in a menu, +// and scrolls that pane to the file picked. The panel the user is in keeps the focus; +// they are reading the diff from there, and the next commit or file to read is picked +// there too. +func (self *GlobalController) jumpToFileInDiff() error { + pane := self.diffPane() + if pane == nil { + return nil + } + + return self.c.Helpers().DiffLine.OpenJumpToFileMenu(pane, self.c.Tr.JumpToFileInDiff) +} + +// jumpToFileInDiffDescription qualifies the command's description so that it is listed +// only where it applies. A command with no description is left out of the keybindings +// menu. +// +// It doesn't apply where the main section is showing content that is no diff of the +// panel's — a branch's commit log, the status dashboard, a message. Nor does it while +// the focus is in one of the panes, which bind the key themselves; the menu would +// otherwise offer it twice there, once for the pane and once among the global keys. +// +// The static Description stays as it is: the cheatsheets are generated from that, and +// they document what a key does rather than when it applies. +func (self *GlobalController) jumpToFileInDiffDescription() string { + _, focusIsInAPane := self.c.Context().Current().(*context.MainContext) + if focusIsInAPane || self.diffPane() == nil { + return "" + } + return self.c.Tr.JumpToFileInDiff +} + +// diffPane returns the pane of the main section showing the diff of the panel the user +// is in, and nil when neither of them is showing one. A pane is cleared as it is +// emptied, so a pane that says it is showing a diff is showing one. Its window also has +// to be showing the pane. Resolving a conflicted file puts the merge conflicts view +// there instead, and the pane behind it goes on holding the diff it last rendered. +// +// Where both panes show a diff — the unstaged and staged sides of a file — the answer +// is the upper one, the pane the keys for scrolling the section act on. +func (self *GlobalController) diffPane() *context.MainContext { + for _, pane := range []*context.MainContext{ + self.c.Contexts().Normal, self.c.Contexts().NormalSecondary, + } { + onScreen := self.c.Helpers().Window.GetContextForWindow(pane.GetWindowName()) == pane + if onScreen && pane.ContentIsDiff() { + return pane + } + } + return nil +} + func (self *GlobalController) createOptionsMenu() error { return (&OptionsMenuAction{c: self.c}).Call() } diff --git a/pkg/gui/controllers/helpers/diff_line_file_menu.go b/pkg/gui/controllers/helpers/diff_line_file_menu.go new file mode 100644 index 000000000..a2e4c30bb --- /dev/null +++ b/pkg/gui/controllers/helpers/diff_line_file_menu.go @@ -0,0 +1,70 @@ +package helpers + +import ( + "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/samber/lo" +) + +// OpenJumpToFileMenu offers the files of the diff the given pane is showing in a menu, +// so that one of them can be gone to directly rather than by stepping through the diff +// a file at a time. Picking one goes to where that file's diff begins, the way stepping +// to it with next-file does: the selection moves there in a focused pane, and an +// unfocused one scrolls the file to the top. +// +// The diff is read to the end before the menu is built: a file below the part of it that +// has been read so far is in neither the list nor the view, and reaching the far end of +// a long diff is what the menu is for. +func (self *DiffLineHelper) OpenJumpToFileMenu(pane types.DiffPaneContext, title string) error { + manager := self.c.GetViewBufferManagerForView(pane.GetView()) + if manager == nil { + return nil + } + manager.ReadToEnd(func() { + self.c.OnUIThread(func() error { return self.showJumpToFileMenu(pane, title) }) + }) + return nil +} + +// showJumpToFileMenu offers the diff's files by the paths git names them by. Each item +// names its file rather than the row that file begins at, so that a diff re-rendered +// while the menu is up is jumped into at the row the file begins at now. +// +// A menu offering the one file of a single-file diff would be a menu with nothing to +// choose, so it says what it found instead. It says it here rather than as the key's +// disabled reason because how many files there are is only known once the diff has been +// read to the end, which is too much to do for every keypress that asks whether a key +// applies. +func (self *DiffLineHelper) showJumpToFileMenu(pane types.DiffPaneContext, title string) error { + view := pane.GetView() + files := self.FilesInDiff(view) + if len(files) == 0 { + return nil + } + if len(files) == 1 { + self.c.ErrorToast(self.c.Tr.DisabledMenuItemPrefix + self.c.Tr.OnlyOneFileInDiff) + return nil + } + + worktreePath := self.c.Git().RepoPaths.WorktreePath() + menuItems := lo.Map(files, func(path string, _ int) *types.MenuItem { + label := repoRelativePath(worktreePath, path) + if label == "" { + label = path + } + return &types.MenuItem{ + Label: label, + OnPress: func() error { + if target, ok := self.StartOfFileInDiff(view, path); ok { + self.PlaceNavigationTarget(pane, target, true) + } + return nil + }, + } + }) + + return self.c.Menu(types.CreateMenuOptions{ + Title: title, + Items: menuItems, + FilterAsYouType: true, + }) +} diff --git a/pkg/gui/controllers/helpers/diff_line_plain_text.go b/pkg/gui/controllers/helpers/diff_line_plain_text.go index f76c4f0a7..6b53d94af 100644 --- a/pkg/gui/controllers/helpers/diff_line_plain_text.go +++ b/pkg/gui/controllers/helpers/diff_line_plain_text.go @@ -154,11 +154,19 @@ func (self *selectedDiffLines) covers(info types.DiffLineInfo) bool { func repoRelativePaths(worktreePath string, paths []string) []string { relPaths := make([]string, 0, len(paths)) for _, path := range paths { - relPath, err := filepath.Rel(worktreePath, path) - if err != nil || strings.HasPrefix(relPath, "..") { - continue + if relPath := repoRelativePath(worktreePath, path); relPath != "" { + relPaths = append(relPaths, relPath) } - relPaths = append(relPaths, filepath.ToSlash(relPath)) } return relPaths } + +// repoRelativePath turns one such path into the repo-relative one, and is "" for a path +// that lies outside the worktree. +func repoRelativePath(worktreePath string, path string) string { + relPath, err := filepath.Rel(worktreePath, path) + if err != nil || strings.HasPrefix(relPath, "..") { + return "" + } + return filepath.ToSlash(relPath) +} diff --git a/pkg/gui/controllers/helpers/diff_line_queries.go b/pkg/gui/controllers/helpers/diff_line_queries.go index b03a98739..ce6fba60a 100644 --- a/pkg/gui/controllers/helpers/diff_line_queries.go +++ b/pkg/gui/controllers/helpers/diff_line_queries.go @@ -345,6 +345,28 @@ func (self *DiffLineHelper) AdjacentFile(view *gocui.View, anchorViewLine int, f return view.ViewLineForBufferLine(target) } +// FilesInDiff lists the files of view's (possibly multi-file) rendered diff, in the +// order it shows them, by the paths of the repo's files. It is what a menu offering to +// jump between them is built from; jumping to one of them goes to StartOfFileInDiff. +func (self *DiffLineHelper) FilesInDiff(view *gocui.View) []string { + return lo.Map(fileStarts(self.filePaths(view)), + func(start diffFileStart, _ int) string { return start.path }) +} + +// StartOfFileInDiff returns the view line the given file's section of view's rendered +// diff begins at. That is the row file navigation lands on, so jumping to a file from a +// menu and stepping to it with next-file land in the same place. ok is false for a file +// the diff doesn't show, e.g. because it was re-rendered since the file was listed. +func (self *DiffLineHelper) StartOfFileInDiff(view *gocui.View, path string) (int, bool) { + start, ok := lo.Find(fileStarts(self.filePaths(view)), func(start diffFileStart) bool { + return start.path == path + }) + if !ok { + return 0, false + } + return view.ViewLineForBufferLine(start.row) +} + // filePaths resolves view's rendered diff to the path each buffer line belongs to, in // the repo's terms, and empty for a row whose identity couldn't be recovered. Naming the // files the way the rest of the queries name them means a row of the custom patch's diff --git a/pkg/gui/controllers/main_view_controller.go b/pkg/gui/controllers/main_view_controller.go index 340a12414..ff314dafc 100644 --- a/pkg/gui/controllers/main_view_controller.go +++ b/pkg/gui/controllers/main_view_controller.go @@ -159,6 +159,15 @@ func (self *MainViewController) GetKeybindings(opts types.KeybindingsOpts) []*ty DescriptionFunc: self.diffSelectionDescriptionText(self.c.Tr.NextFileInDiff), GetDisabledReason: self.diffSelectionDisabledReason, }, + { + Keys: opts.GetKeys(opts.Config.Universal.JumpToFile), + Handler: self.openJumpToFileMenu, + Description: self.c.Tr.JumpToFile, + DescriptionFunc: self.diffSelectionDescriptionText(self.c.Tr.JumpToFile), + GetDisabledReason: self.diffSelectionDisabledReason, + OpensMenu: true, + DisplayOnScreen: true, + }, { Keys: opts.GetKeys(opts.Config.Commits.OpenPullRequestInBrowser), Handler: self.openPullRequestAtSelectedLine, @@ -842,6 +851,10 @@ func (self *MainViewController) placeNavigationTarget(target int, alignTop bool) self.c.Helpers().DiffLine.PlaceNavigationTarget(self.context, target, alignTop) } +func (self *MainViewController) openJumpToFileMenu() error { + return self.c.Helpers().DiffLine.OpenJumpToFileMenu(self.context, self.c.Tr.JumpToFile) +} + // moveCursor moves the selection cursor by delta view lines (negative = up), with the // configured scroll-off margin, reading more content in first when moving down. The // range anchor is left untouched, so this extends or contracts a range and just moves diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 795e8d289..88eddd9cb 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -416,6 +416,10 @@ type TranslationSet struct { NextHunk string PrevFileInDiff string NextFileInDiff string + JumpToFile string + JumpToFileInDiff string + JumpToFileInDiffTooltip string + OnlyOneFileInDiff string PrevConflict string NextConflict string SelectPrevHunk string @@ -1602,6 +1606,10 @@ func EnglishTranslationSet() *TranslationSet { NextHunk: "Go to next hunk", PrevFileInDiff: "Go to previous file", NextFileInDiff: "Go to next file", + JumpToFile: "Jump to file", + JumpToFileInDiff: "Jump to file in diff", + JumpToFileInDiffTooltip: "Pick one of the files of the diff shown in the main view, and scroll the main view to it. The focus stays in this panel.", + OnlyOneFileInDiff: "There is only one file in this diff", PrevConflict: "Previous conflict", NextConflict: "Next conflict", SelectPrevHunk: "Previous hunk", diff --git a/pkg/integration/tests/main_view/jump_to_a_file_of_the_diff.go b/pkg/integration/tests/main_view/jump_to_a_file_of_the_diff.go new file mode 100644 index 000000000..d36959b16 --- /dev/null +++ b/pkg/integration/tests/main_view/jump_to_a_file_of_the_diff.go @@ -0,0 +1,155 @@ +package main_view + +import ( + "fmt" + "strings" + + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var JumpToAFileOfTheDiff = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Jump to a file of a commit's diff by picking it from a menu of the diff's files", + ExtraCmdArgs: []string{}, + Skip: false, + // A short terminal, so that the first file's diff is longer than the part of the + // diff that has been read when the menu asks which files there are. + Width: 100, + Height: 20, + SetupConfig: func(config *config.AppConfig) { + config.GetUserConfig().Gui.UseHunkModeInDiffView = false + }, + SetupRepo: func(shell *Shell) { + lines := make([]string, 600) + for i := range lines { + lines[i] = fmt.Sprintf("line%03d", i+1) + } + shell.CreateFileAndAdd("aaa.txt", strings.Join(lines, "\n")+"\n") + shell.CreateFileAndAdd("ccc.txt", "one\n") + shell.CreateFileAndAdd("dir/bbb.txt", "one\n") + // Another long one at the end, so that the file jumped to below has a diff + // under it to scroll past and ends up at the top of the view. + shell.CreateFileAndAdd("zzz.txt", strings.Join(lines, "\n")+"\n") + shell.Commit("one") + + shell.UpdateFileAndAdd("ccc.txt", "two\n") + shell.Commit("two") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + // A menu offering the one file of a single-file diff would be a menu with + // nothing to choose, so it says what it found instead. + t.Views().Commits(). + Focus(). + SelectedLine(Contains("two")). + Press(keys.Universal.FocusMainView) + + t.Views().Main(). + IsFocused(). + SelectionIsActive(). + Press(keys.Universal.JumpToFile) + + t.ExpectToast(Contains("There is only one file in this diff")) + + t.Views().Main(). + IsFocused(). + Press(keys.Universal.Return) + + t.Views().Commits(). + IsFocused(). + SelectNextItem(). + SelectedLine(Contains("one")). + Press(keys.Universal.FocusMainView) + + // Every file of the diff is offered, in the order the diff shows them and by + // the path the repo knows them by, including the ones below the part of the + // diff that has been read. + t.Views().Main(). + IsFocused(). + SelectionIsActive(). + Press(keys.Universal.JumpToFile) + + t.ExpectPopup().Menu(). + Title(Equals("Jump to file")). + Lines( + Equals("aaa.txt"), + Equals("ccc.txt"), + Equals("dir/bbb.txt"), + Equals("zzz.txt"), + Equals("Cancel"), + ). + Select(Equals("dir/bbb.txt")). + Confirm() + + // The file lands where stepping to it with next-file would leave it: selected, + // and at the top of the view. + t.Views().Main(). + IsFocused(). + TopVisibleLine(Contains("diff --git a/dir/bbb.txt b/dir/bbb.txt")). + SelectedLines( + Contains("diff --git a/dir/bbb.txt b/dir/bbb.txt"), + ). + Press(keys.Universal.JumpToFile) + + // The menu filters as you type, which is the point of it for a diff of many + // files. + t.ExpectPopup().Menu(). + Title(Equals("Jump to file")). + Filter("ccc"). + Lines( + Equals("ccc.txt"), + ). + Confirm() + + t.Views().Main(). + IsFocused(). + SelectedLines( + Contains("diff --git a/ccc.txt b/ccc.txt"), + ). + Press(keys.Universal.Return) + + // The panel the diff belongs to offers the same menu, so that a file can be + // jumped to without focusing the diff and having to come back out of it. + t.Views().Commits(). + IsFocused(). + SelectedLine(Contains("one")). + Press(keys.Universal.JumpToFile) + + t.ExpectPopup().Menu(). + Title(Equals("Jump to file in diff")). + Select(Equals("zzz.txt")). + Confirm() + + // Only the diff moves: it goes to the top of the file, while the panel keeps + // the focus and its selection. + t.Views().Commits(). + IsFocused(). + SelectedLine(Contains("one")) + + t.Views().Main(). + TopVisibleLine(Contains("diff --git a/zzz.txt b/zzz.txt")) + + // A panel showing something else in the main view — a branch's commit log — + // doesn't offer the menu at all, rather than offering it disabled. + t.Views().Branches(). + Focus(). + Press(keys.Universal.OptionMenu) + + t.ExpectPopup().Menu(). + Title(Equals("Keybindings")). + Tap(func() { + t.Views().Menu().Content(DoesNotContain("Jump to file")) + }). + Cancel() + + t.Views().Commits(). + Focus(). + Press(keys.Universal.OptionMenu) + + t.ExpectPopup().Menu(). + Title(Equals("Keybindings")). + Tap(func() { + t.Views().Menu().Content(Contains("Jump to file in diff")) + }). + Cancel() + }, +}) diff --git a/pkg/integration/tests/main_view/jump_to_a_file_only_over_a_diff.go b/pkg/integration/tests/main_view/jump_to_a_file_only_over_a_diff.go new file mode 100644 index 000000000..73a72d889 --- /dev/null +++ b/pkg/integration/tests/main_view/jump_to_a_file_only_over_a_diff.go @@ -0,0 +1,74 @@ +package main_view + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" + "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" +) + +var JumpToAFileOnlyOverADiff = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "The menu of the diff's files is offered only while the main view is showing a diff", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shared.CreateMergeConflictFiles(shell) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + // A commit shows its diff in the main view, so the menu is offered over it. + t.Views().Commits(). + Focus(). + NavigateToLine(Contains("original")). + Press(keys.Universal.OptionMenu) + + t.ExpectPopup().Menu(). + Title(Equals("Keybindings")). + Tap(func() { + t.Views().Menu().Content(Contains("Jump to file in diff")) + }). + Cancel() + + // The pane binds the same key itself, so the global one isn't offered on top of + // the pane's while the pane has the focus. + t.Views().Commits(). + IsFocused(). + Press(keys.Universal.FocusMainView) + + t.Views().Main(). + IsFocused(). + Press(keys.Universal.OptionMenu) + + t.ExpectPopup().Menu(). + Title(Equals("Keybindings")). + Tap(func() { + t.Views().Menu(). + Content(Contains("Jump to file")). + Content(DoesNotContain("Jump to file in diff")) + }). + Cancel() + + t.Views().Main(). + IsFocused(). + Press(keys.Universal.Return) + + // Working through a conflicted file gives the main section over to the merge + // conflicts view, which is no diff to jump around in. The pane behind it goes + // on holding the diff it last rendered, so it is the view on screen that + // decides. + t.Views().Files(). + Focus(). + NavigateToLine(Contains("UU file1")). + PressEnter() + + t.Views().MergeConflicts(). + IsFocused(). + Press(keys.Universal.OptionMenu) + + t.ExpectPopup().Menu(). + Title(Equals("Keybindings")). + Tap(func() { + t.Views().Menu().Content(DoesNotContain("Jump to file")) + }). + Cancel() + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 0e27c9bbd..dad72fd68 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -405,6 +405,8 @@ var tests = []*components.IntegrationTest{ main_view.FocusLeavesAnAlwaysSplitEmptyPane, main_view.FocusReturnsWhenSplitCollapses, main_view.HideSelectionWhenChangesVanish, + main_view.JumpToAFileOfTheDiff, + main_view.JumpToAFileOnlyOverADiff, main_view.KeepAWrappedLineCoveredAcrossARerender, main_view.KeepBothHalvesOfAChangeSelected, main_view.KeepPositionByTheVisibleEndOfASelection, diff --git a/schema-master/config.json b/schema-master/config.json index 161619557..afccd61b2 100644 --- a/schema-master/config.json +++ b/schema-master/config.json @@ -3128,6 +3128,20 @@ ], "default": "0" }, + "jumpToFile": { + "oneOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "default": "\u003cctrl+g\u003e" + }, "nextMatch": { "oneOf": [ {