mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-13 09:06:27 -04:00
Add a keybinding to collapse the parent directory of the selected file (#1)
When a file is deeply nested (e.g. dir/subdir/file), there was no quick way to collapse an ancestor directory without walking back up the tree one row at a time. This adds <backspace> in the Files and Commit Files panels to jump the selection to the parent directory and collapse it, skipping over directories that got merged away by path compression. Pressing it again from the root collapses the root itself. Relates to upstream repo's #4984 and #4095.
This commit is contained in:
parent
292035709f
commit
19c702f9ab
|
|
@ -583,6 +583,7 @@ type KeybindingFilesConfig struct {
|
|||
CopyFileInfoToClipboard Keybinding `yaml:"copyFileInfoToClipboard"`
|
||||
CollapseAll Keybinding `yaml:"collapseAll"`
|
||||
ExpandAll Keybinding `yaml:"expandAll"`
|
||||
CollapseParent Keybinding `yaml:"collapseParent"`
|
||||
}
|
||||
|
||||
type KeybindingBranchesConfig struct {
|
||||
|
|
@ -1099,6 +1100,7 @@ func GetDefaultConfigForPlatform(platform string) *UserConfig {
|
|||
CopyFileInfoToClipboard: Keybinding{"y"},
|
||||
CollapseAll: Keybinding{"-"},
|
||||
ExpandAll: Keybinding{"="},
|
||||
CollapseParent: Keybinding{"<backspace>"},
|
||||
},
|
||||
Branches: KeybindingBranchesConfig{
|
||||
CopyPullRequestURL: Keybinding{"<ctrl+y>"},
|
||||
|
|
|
|||
|
|
@ -132,6 +132,13 @@ func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []
|
|||
Tooltip: self.c.Tr.ExpandAllTooltip,
|
||||
GetDisabledReason: self.require(self.isInTreeMode),
|
||||
},
|
||||
{
|
||||
Keys: opts.GetKeys(opts.Config.Files.CollapseParent),
|
||||
Handler: self.collapseParent,
|
||||
Description: self.c.Tr.CollapseParent,
|
||||
Tooltip: self.c.Tr.CollapseParentTooltip,
|
||||
GetDisabledReason: self.require(self.isInTreeMode),
|
||||
},
|
||||
}
|
||||
|
||||
return bindings
|
||||
|
|
@ -603,6 +610,27 @@ func (self *CommitFilesController) expandAll() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) collapseParent() error {
|
||||
node := self.getSelectedItem()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
viewModel := self.context().CommitFileTreeViewModel
|
||||
|
||||
parentPath, idx, found := collapseParentPath(node.GetInternalPath(), viewModel.GetIndexForPath)
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
viewModel.CollapsedPaths().Collapse(parentPath)
|
||||
viewModel.SetSelectedLineIdx(idx)
|
||||
|
||||
self.c.PostRefreshUpdate(self.context())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) GetOnClickFocusedMainView() func(mainViewName string, clickedLineIdx int) error {
|
||||
return func(mainViewName string, clickedLineIdx int) error {
|
||||
node := self.getSelectedItem()
|
||||
|
|
|
|||
|
|
@ -206,6 +206,13 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
|||
Tooltip: self.c.Tr.ExpandAllTooltip,
|
||||
GetDisabledReason: self.require(self.isInTreeMode),
|
||||
},
|
||||
{
|
||||
Keys: opts.GetKeys(opts.Config.Files.CollapseParent),
|
||||
Handler: self.collapseParent,
|
||||
Description: self.c.Tr.CollapseParent,
|
||||
Tooltip: self.c.Tr.CollapseParentTooltip,
|
||||
GetDisabledReason: self.require(self.isInTreeMode),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -720,6 +727,53 @@ func (self *FilesController) expandAll() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (self *FilesController) collapseParent() error {
|
||||
node := self.context().GetSelected()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
viewModel := self.context().FileTreeViewModel
|
||||
|
||||
parentPath, idx, found := collapseParentPath(node.GetInternalPath(), viewModel.GetIndexForPath)
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
viewModel.CollapsedPaths().Collapse(parentPath)
|
||||
viewModel.SetSelectedLineIdx(idx)
|
||||
|
||||
self.c.PostRefreshUpdate(self.context())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// collapseParentPath walks up the internal-path segments of the tree from the
|
||||
// given node until it finds an existing ancestor node, since a compressed
|
||||
// directory (e.g. "dir/subdir" merged into one visual node) means the
|
||||
// immediate one-segment-up path isn't always addressable. The root node's
|
||||
// parent is itself, so backspacing from the top collapses the root.
|
||||
func collapseParentPath(internalPath string, getIndexForPath func(string) (int, bool)) (string, int, bool) {
|
||||
segments := strings.Split(internalPath, "/")
|
||||
|
||||
if len(segments) == 1 {
|
||||
if idx, ok := getIndexForPath(internalPath); ok {
|
||||
return internalPath, idx, true
|
||||
}
|
||||
return "", 0, false
|
||||
}
|
||||
|
||||
for len(segments) > 1 {
|
||||
segments = segments[:len(segments)-1]
|
||||
candidate := strings.Join(segments, "/")
|
||||
if idx, ok := getIndexForPath(candidate); ok {
|
||||
return candidate, idx, true
|
||||
}
|
||||
}
|
||||
|
||||
return "", 0, false
|
||||
}
|
||||
|
||||
func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
|
||||
node := self.context().GetSelected()
|
||||
if node == nil {
|
||||
|
|
|
|||
|
|
@ -294,6 +294,8 @@ type TranslationSet struct {
|
|||
CollapseAllTooltip string
|
||||
ExpandAll string
|
||||
ExpandAllTooltip string
|
||||
CollapseParent string
|
||||
CollapseParentTooltip string
|
||||
DisabledInFlatView string
|
||||
FileEnter string
|
||||
FileEnterTooltip string
|
||||
|
|
@ -1438,6 +1440,8 @@ func EnglishTranslationSet() *TranslationSet {
|
|||
CollapseAllTooltip: "Collapse all directories in the files tree",
|
||||
ExpandAll: "Expand all files",
|
||||
ExpandAllTooltip: "Expand all directories in the file tree",
|
||||
CollapseParent: "Collapse parent directory",
|
||||
CollapseParentTooltip: "Move selection up to the parent directory and collapse it",
|
||||
DisabledInFlatView: "Not available in flat view",
|
||||
FileEnter: `Stage lines / Collapse directory`,
|
||||
FileEnterTooltip: "If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it.",
|
||||
|
|
|
|||
54
pkg/integration/tests/commit/collapse_parent_files.go
Normal file
54
pkg/integration/tests/commit/collapse_parent_files.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package commit
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var CollapseParentFiles = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Pressing backspace in the commit files panel jumps to the parent directory and collapses it",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("dir1/subd1/subfile0", "file0\n")
|
||||
shell.CreateFileAndAdd("dir2/d2_file1", "d2f1 content\n")
|
||||
shell.Commit("add files in two dirs")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("add files in two dirs").IsSelected(),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Equals("▼ /").IsSelected(),
|
||||
Equals(" ▼ dir1/subd1"),
|
||||
Equals(" A subfile0"),
|
||||
Equals(" ▼ dir2"),
|
||||
Equals(" A d2_file1"),
|
||||
).
|
||||
NavigateToLine(Contains("subfile0"))
|
||||
|
||||
// backspace jumps to and collapses the immediate (compressed) parent "dir1/subd1"
|
||||
t.Views().CommitFiles().
|
||||
Press(keys.Files.CollapseParent).
|
||||
Lines(
|
||||
Equals("▼ /"),
|
||||
Equals(" ▶ dir1/subd1").IsSelected(),
|
||||
Equals(" ▼ dir2"),
|
||||
Equals(" A d2_file1"),
|
||||
)
|
||||
|
||||
// backspace again jumps up to and collapses the root
|
||||
t.Views().CommitFiles().
|
||||
Press(keys.Files.CollapseParent).
|
||||
Lines(
|
||||
Equals("▶ /").IsSelected(),
|
||||
)
|
||||
},
|
||||
})
|
||||
57
pkg/integration/tests/file/collapse_parent.go
Normal file
57
pkg/integration/tests/file/collapse_parent.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var CollapseParent = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Pressing backspace on a selected file jumps to its parent directory and collapses it",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateDir("dir")
|
||||
shell.CreateDir("dir/subdir")
|
||||
shell.CreateFile("dir/subdir/file-one", "original content\n")
|
||||
shell.CreateDir("dir2")
|
||||
shell.CreateFile("dir2/file-two", "original content\n")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Equals("▼ /").IsSelected(),
|
||||
Equals(" ▼ dir/subdir"),
|
||||
Equals(" ?? file-one"),
|
||||
Equals(" ▼ dir2"),
|
||||
Equals(" ?? file-two"),
|
||||
).
|
||||
// select "file-one" nested two levels deep
|
||||
NavigateToLine(Contains("file-one"))
|
||||
|
||||
// backspace jumps to and collapses the immediate (compressed) parent "dir/subdir"
|
||||
t.Views().Files().
|
||||
Press(keys.Files.CollapseParent).
|
||||
Lines(
|
||||
Equals("▼ /"),
|
||||
Equals(" ▶ dir/subdir").IsSelected(),
|
||||
Equals(" ▼ dir2"),
|
||||
Equals(" ?? file-two"),
|
||||
)
|
||||
|
||||
// backspace again jumps up to and collapses the root
|
||||
t.Views().Files().
|
||||
Press(keys.Files.CollapseParent).
|
||||
Lines(
|
||||
Equals("▶ /").IsSelected(),
|
||||
)
|
||||
|
||||
// backspace on an already-collapsed root is a no-op
|
||||
t.Views().Files().
|
||||
Press(keys.Files.CollapseParent).
|
||||
Lines(
|
||||
Equals("▶ /").IsSelected(),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
|
@ -110,6 +110,7 @@ var tests = []*components.IntegrationTest{
|
|||
commit.CheckoutFileFromCommit,
|
||||
commit.CheckoutFileFromRangeSelectionOfCommits,
|
||||
commit.CheckoutFileWithLocalModifications,
|
||||
commit.CollapseParentFiles,
|
||||
commit.Commit,
|
||||
commit.CommitMultiline,
|
||||
commit.CommitSkipHooks,
|
||||
|
|
@ -227,6 +228,7 @@ var tests = []*components.IntegrationTest{
|
|||
diff.RenameSimilarityThresholdChange,
|
||||
file.ClickArrowToCollapse,
|
||||
file.CollapseExpand,
|
||||
file.CollapseParent,
|
||||
file.CopyMenu,
|
||||
file.DirWithUntrackedFile,
|
||||
file.DiscardAllDirChanges,
|
||||
|
|
|
|||
Loading…
Reference in a new issue