diff --git a/docs-master/keybindings/Keybindings_en.md b/docs-master/keybindings/Keybindings_en.md index edfe27106..cd9bed28d 100644 --- a/docs-master/keybindings/Keybindings_en.md +++ b/docs-master/keybindings/Keybindings_en.md @@ -234,6 +234,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 | | +| `` f `` | Jump to file | | | `` `` | Exit back to side panel | | | `` c `` | Commit | Commit staged changes. | | `` w `` | Commit changes without pre-commit hook | | @@ -316,6 +317,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 | | +| `` f `` | Jump to file | | | `` `` | Exit back to side panel | | | `` c `` | Commit | Commit staged changes. | | `` w `` | Commit changes without pre-commit hook | | diff --git a/docs-master/keybindings/Keybindings_ja.md b/docs-master/keybindings/Keybindings_ja.md index 4aef4bd13..49ae515f5 100644 --- a/docs-master/keybindings/Keybindings_ja.md +++ b/docs-master/keybindings/Keybindings_ja.md @@ -203,6 +203,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 | | +| `` f `` | Jump to file | | | `` `` | サイドパネルに戻る | | | `` c `` | コミット | ステージされた変更をコミットします。 | | `` w `` | pre-commitフックなしで変更をコミット | | @@ -293,6 +294,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 | | +| `` f `` | Jump to file | | | `` `` | サイドパネルに戻る | | | `` c `` | コミット | ステージされた変更をコミットします。 | | `` w `` | pre-commitフックなしで変更をコミット | | diff --git a/docs-master/keybindings/Keybindings_ko.md b/docs-master/keybindings/Keybindings_ko.md index 680e9cf30..d4fc3c81d 100644 --- a/docs-master/keybindings/Keybindings_ko.md +++ b/docs-master/keybindings/Keybindings_ko.md @@ -95,6 +95,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 | | +| `` f `` | Jump to file | | | `` `` | Exit back to side panel | | | `` c `` | 커밋 변경내용 | 스테이징된 변경 사항 커밋. | | `` w `` | Commit changes without pre-commit hook | | @@ -188,6 +189,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 | | +| `` f `` | Jump to file | | | `` `` | Exit back to side panel | | | `` c `` | 커밋 변경내용 | 스테이징된 변경 사항 커밋. | | `` w `` | Commit changes without pre-commit hook | | diff --git a/docs-master/keybindings/Keybindings_nl.md b/docs-master/keybindings/Keybindings_nl.md index d8b40dbfa..e74e96b96 100644 --- a/docs-master/keybindings/Keybindings_nl.md +++ b/docs-master/keybindings/Keybindings_nl.md @@ -242,6 +242,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 | | +| `` f `` | Jump to file | | | `` `` | Exit back to side panel | | | `` c `` | Commit veranderingen | Commit gestagede wijzigingen. | | `` w `` | Commit veranderingen zonder pre-commit hook | | @@ -316,6 +317,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 | | +| `` f `` | Jump to file | | | `` `` | Exit back to side panel | | | `` c `` | Commit veranderingen | Commit gestagede wijzigingen. | | `` w `` | Commit veranderingen zonder pre-commit hook | | diff --git a/docs-master/keybindings/Keybindings_pl.md b/docs-master/keybindings/Keybindings_pl.md index a612f44ab..98b797916 100644 --- a/docs-master/keybindings/Keybindings_pl.md +++ b/docs-master/keybindings/Keybindings_pl.md @@ -110,6 +110,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 | | +| `` f `` | Jump to file | | | `` `` | Exit back to side panel | | | `` c `` | Commit | Zatwierdź zmiany zatwierdzone. | | `` w `` | Zatwierdź zmiany bez hooka pre-commit | | @@ -211,6 +212,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 | | +| `` f `` | Jump to file | | | `` `` | Exit back to side panel | | | `` c `` | Commit | Zatwierdź zmiany zatwierdzone. | | `` w `` | Zatwierdź zmiany bez hooka pre-commit | | diff --git a/docs-master/keybindings/Keybindings_pt.md b/docs-master/keybindings/Keybindings_pt.md index 252c72060..1320c8fe3 100644 --- a/docs-master/keybindings/Keybindings_pt.md +++ b/docs-master/keybindings/Keybindings_pt.md @@ -246,6 +246,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 | | +| `` f `` | Jump to file | | | `` `` | Exit back to side panel | | | `` c `` | Commit | Submeter mudanças em staging | | `` w `` | Fazer commit de alterações sem pré-commit | | @@ -325,6 +326,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 | | +| `` f `` | Jump to file | | | `` `` | Exit back to side panel | | | `` c `` | Commit | Submeter mudanças em staging | | `` w `` | Fazer commit de alterações sem pré-commit | | diff --git a/docs-master/keybindings/Keybindings_ru.md b/docs-master/keybindings/Keybindings_ru.md index b93a4164c..4ced54140 100644 --- a/docs-master/keybindings/Keybindings_ru.md +++ b/docs-master/keybindings/Keybindings_ru.md @@ -85,6 +85,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 | | +| `` f `` | Jump to file | | | `` `` | Exit back to side panel | | | `` c `` | Сохранить изменения | Commit staged changes. | | `` w `` | Закоммитить изменения без предварительного хука коммита | | @@ -110,6 +111,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 | | +| `` f `` | Jump to file | | | `` `` | Exit back to side panel | | | `` c `` | Сохранить изменения | Commit staged changes. | | `` w `` | Закоммитить изменения без предварительного хука коммита | | diff --git a/docs-master/keybindings/Keybindings_zh-CN.md b/docs-master/keybindings/Keybindings_zh-CN.md index 773e2e8e9..95f66f13c 100644 --- a/docs-master/keybindings/Keybindings_zh-CN.md +++ b/docs-master/keybindings/Keybindings_zh-CN.md @@ -281,6 +281,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 | | +| `` f `` | Jump to file | | | `` `` | 退出回到侧边面板 | | | `` c `` | 提交变更 | 提交暂存文件 | | `` w `` | 提交变更而无需预先提交钩子 | | @@ -322,6 +323,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 | | +| `` f `` | Jump to file | | | `` `` | 退出回到侧边面板 | | | `` c `` | 提交变更 | 提交暂存文件 | | `` w `` | 提交变更而无需预先提交钩子 | | diff --git a/docs-master/keybindings/Keybindings_zh-TW.md b/docs-master/keybindings/Keybindings_zh-TW.md index 2a70fca04..f1a75f60f 100644 --- a/docs-master/keybindings/Keybindings_zh-TW.md +++ b/docs-master/keybindings/Keybindings_zh-TW.md @@ -70,6 +70,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 | | +| `` f `` | Jump to file | | | `` `` | 退出回到側邊面板 | | | `` c `` | 提交變更 | 提交暫存區變更 | | `` w `` | 沒有預提交 hook 就提交更改 | | @@ -344,6 +345,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 | | +| `` f `` | Jump to file | | | `` `` | 退出回到側邊面板 | | | `` c `` | 提交變更 | 提交暫存區變更 | | `` w `` | 沒有預提交 hook 就提交更改 | | diff --git a/pkg/gui/controllers/helpers/diff_line_queries.go b/pkg/gui/controllers/helpers/diff_line_queries.go index 02af32efa..c03885183 100644 --- a/pkg/gui/controllers/helpers/diff_line_queries.go +++ b/pkg/gui/controllers/helpers/diff_line_queries.go @@ -455,3 +455,32 @@ func changeBlockStart(isChange []bool, from int, forward bool) (int, bool) { } return i, true } + +// DiffFile is a file shown in a (possibly multi-file) diff: its absolute path and the +// view line its section starts at — the row that next/previous-file navigation lands on. +type DiffFile struct { + Path string + FirstViewLine int +} + +// FilesInDiff lists the files shown in view's diff, in display order, each paired with +// the view line its section starts at. It is the jump-to-file menu's source: jumping to +// a file goes to its FirstViewLine, computed the same way (backUpOverHeader) that +// AdjacentFile lands on a file, so the menu and n/N agree on where each file begins. A +// file whose start row isn't currently mapped to a view line (not loaded yet) is skipped. +func (self *DiffLineHelper) FilesInDiff(view *gocui.View) []DiffFile { + paths := self.filePaths(view) + + var files []DiffFile + prevPath := "" + for i, path := range paths { + if path == "" || path == prevPath { + continue + } + prevPath = path + if viewLine, ok := view.ViewLineForBufferLine(i); ok { + files = append(files, DiffFile{Path: path, FirstViewLine: viewLine}) + } + } + return files +} diff --git a/pkg/gui/controllers/main_view_controller.go b/pkg/gui/controllers/main_view_controller.go index 3cdd4d16a..32d9acabd 100644 --- a/pkg/gui/controllers/main_view_controller.go +++ b/pkg/gui/controllers/main_view_controller.go @@ -1,8 +1,10 @@ package controllers import ( + "path/filepath" "time" + "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gocui" "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" @@ -154,6 +156,13 @@ func (self *MainViewController) GetKeybindings(opts types.KeybindingsOpts) []*ty DescriptionFunc: self.diffSelectionDescriptionText(self.c.Tr.NextFileInDiff), GetDisabledReason: self.diffSelectionDisabledReason, }, + { + Keys: opts.GetKeys(config.Keybinding{"f"}), + Handler: self.openJumpToFileMenu, + Description: "Jump to file", + DescriptionFunc: self.diffSelectionDescriptionText("Jump to file"), + GetDisabledReason: self.diffSelectionDisabledReason, + }, { Keys: opts.GetKeys(opts.Config.Universal.Return), Handler: self.escape, @@ -747,6 +756,60 @@ func (self *MainViewController) placeNavigationTarget(target int) { self.c.Helpers().DiffLine.ShowSelectionAtLine(v, target, true) } +// openJumpToFileMenu pops up a menu listing the files in the focused main view's diff, in +// the order they appear, as repo-relative paths; picking one jumps straight to it. It's a +// complement to n / N for a diff that spans many files. A no-op when the main view holds +// no diff (nothing to list). +// +// The diff loads lazily, so we read it to the end first — otherwise a file past the loaded +// portion of a long diff would be missing from the menu (and have no view line to jump to). +// Same as handleGotoBottom. +func (self *MainViewController) openJumpToFileMenu() error { + manager := self.c.GetViewBufferManagerForView(self.context.GetView()) + if manager == nil { + return nil + } + manager.ReadToEnd(func() { + self.c.OnUIThread(func() error { + return self.showJumpToFileMenu() + }) + }) + return nil +} + +func (self *MainViewController) showJumpToFileMenu() error { + files := self.c.Helpers().DiffLine.FilesInDiff(self.context.GetView()) + if len(files) == 0 { + return nil + } + + worktreePath := self.c.Git().RepoPaths.WorktreePath() + menuItems := lo.Map(files, func(file helpers.DiffFile, index int) *types.MenuItem { + label := file.Path + if rel, err := filepath.Rel(worktreePath, file.Path); err == nil { + label = rel + } + firstViewLine := file.FirstViewLine + return &types.MenuItem{ + Label: label, + OnPress: func() error { self.jumpToFile(firstViewLine); return nil }, + } + }) + + // TODO: i18n-ize this title + return self.c.Menu(types.CreateMenuOptions{Title: "Jump to file", Items: menuItems, HideCancel: true, FilterAsYouType: true}) +} + +// jumpToFile moves the focused main view to the given view line exactly the way +// next/previous-file navigation does (see navigate), so jumping from the menu and +// stepping with n / N land identically — scrolling the file to the top with no selection, +// or moving the selection (or hunk selection) to it when one is showing. +func (self *MainViewController) jumpToFile(firstViewLine int) { + self.navigate(func(*gocui.View, int, bool) (int, bool) { + return firstViewLine, true + }, true) +} + // 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