diff --git a/docs/Config.md b/docs/Config.md index 614d8d79a64..c6a74d47331 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -686,6 +686,7 @@ keybinding: copyFileInfoToClipboard: "y" collapseAll: '-' expandAll: = + toggleSubtreeExpansion: branches: createPullRequest: o viewPullRequestOptions: O diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md index cf78b8574a8..8b3f43c72ad 100644 --- a/docs/keybindings/Keybindings_en.md +++ b/docs/keybindings/Keybindings_en.md @@ -158,6 +158,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` f `` | Fetch | Fetch changes from remote. | | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | +| `` `` | Recursively expand/collapse the selected directory | Recursively expand/collapse the selected directory in the file tree | | `` 0 `` | Focus main view | | | `` / `` | Search the current view by text | | diff --git a/docs/keybindings/Keybindings_ja.md b/docs/keybindings/Keybindings_ja.md index 198578d0929..6354d840141 100644 --- a/docs/keybindings/Keybindings_ja.md +++ b/docs/keybindings/Keybindings_ja.md @@ -240,6 +240,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味 | `` f `` | フェッチ | リモートから変更をフェッチします。 | | `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます | | `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します | +| `` `` | Recursively expand/collapse the selected directory | Recursively expand/collapse the selected directory in the file tree | | `` 0 `` | メインビューにフォーカス | | | `` / `` | 現在のビューをテキストで検索 | | diff --git a/docs/keybindings/Keybindings_ko.md b/docs/keybindings/Keybindings_ko.md index 5c4f1ad7b3a..ccc62d20ada 100644 --- a/docs/keybindings/Keybindings_ko.md +++ b/docs/keybindings/Keybindings_ko.md @@ -401,6 +401,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` f `` | Fetch | Fetch changes from remote. | | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | +| `` `` | Recursively expand/collapse the selected directory | Recursively expand/collapse the selected directory in the file tree | | `` 0 `` | Focus main view | | | `` / `` | 검색 시작 | | diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md index d74247d717d..24fbe9f06d2 100644 --- a/docs/keybindings/Keybindings_nl.md +++ b/docs/keybindings/Keybindings_nl.md @@ -83,6 +83,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` f `` | Fetch | Fetch changes from remote. | | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | +| `` `` | Recursively expand/collapse the selected directory | Recursively expand/collapse the selected directory in the file tree | | `` 0 `` | Focus main view | | | `` / `` | Start met zoeken | | diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md index 5d5d53609d3..8f15f690d96 100644 --- a/docs/keybindings/Keybindings_pl.md +++ b/docs/keybindings/Keybindings_pl.md @@ -257,6 +257,7 @@ _Legenda: `` oznacza ctrl+b, `` oznacza alt+b, `B` oznacza shift+b_ | `` f `` | Pobierz | Pobierz zmiany ze zdalnego serwera. | | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | +| `` `` | Recursively expand/collapse the selected directory | Recursively expand/collapse the selected directory in the file tree | | `` 0 `` | Focus main view | | | `` / `` | Szukaj w bieżącym widoku po tekście | | diff --git a/docs/keybindings/Keybindings_pt.md b/docs/keybindings/Keybindings_pt.md index 34094ba2bb2..224863e2f78 100644 --- a/docs/keybindings/Keybindings_pt.md +++ b/docs/keybindings/Keybindings_pt.md @@ -83,6 +83,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_ | `` f `` | Buscar | Buscar alterações do controle remoto. | | `` - `` | Recolher todos os arquivos | Recolher todos os diretórios na árvore de arquivos | | `` = `` | Expandir todos os arquivos | Expandir todos os diretórios na árvore do arquivo | +| `` `` | Recursively expand/collapse the selected directory | Recursively expand/collapse the selected directory in the file tree | | `` 0 `` | Focus main view | | | `` / `` | Search the current view by text | | diff --git a/docs/keybindings/Keybindings_ru.md b/docs/keybindings/Keybindings_ru.md index c1cd6f53c13..6d01fb32095 100644 --- a/docs/keybindings/Keybindings_ru.md +++ b/docs/keybindings/Keybindings_ru.md @@ -395,6 +395,7 @@ _Связки клавиш_ | `` f `` | Получить изменения | Fetch changes from remote. | | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | +| `` `` | Recursively expand/collapse the selected directory | Recursively expand/collapse the selected directory in the file tree | | `` 0 `` | Focus main view | | | `` / `` | Найти | | diff --git a/docs/keybindings/Keybindings_zh-CN.md b/docs/keybindings/Keybindings_zh-CN.md index 799d7e511b2..db3c7227750 100644 --- a/docs/keybindings/Keybindings_zh-CN.md +++ b/docs/keybindings/Keybindings_zh-CN.md @@ -221,6 +221,7 @@ _图例:`` 意味着ctrl+b, `意味着Alt+b, `B` 意味着shift+b_ | `` f `` | 抓取 | 从远程获取变更 | | `` - `` | 折叠全部文件 | 折叠文件树中的全部目录 | | `` = `` | 展开全部文件 | 展开文件树中的全部目录 | +| `` `` | Recursively expand/collapse the selected directory | Recursively expand/collapse the selected directory in the file tree | | `` 0 `` | Focus main view | | | `` / `` | 开始搜索 | | diff --git a/docs/keybindings/Keybindings_zh-TW.md b/docs/keybindings/Keybindings_zh-TW.md index 9c9f2e913e8..1b8ed151313 100644 --- a/docs/keybindings/Keybindings_zh-TW.md +++ b/docs/keybindings/Keybindings_zh-TW.md @@ -352,6 +352,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B | `` f `` | 擷取 | 同步遠端異動 | | `` - `` | Collapse all files | Collapse all directories in the files tree | | `` = `` | Expand all files | Expand all directories in the file tree | +| `` `` | Recursively expand/collapse the selected directory | Recursively expand/collapse the selected directory in the file tree | | `` 0 `` | Focus main view | | | `` / `` | 搜尋 | | diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index f0b41603ce2..a54f44724d1 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -510,6 +510,7 @@ type KeybindingFilesConfig struct { CopyFileInfoToClipboard string `yaml:"copyFileInfoToClipboard"` CollapseAll string `yaml:"collapseAll"` ExpandAll string `yaml:"expandAll"` + ToggleSubtreeExpansion string `yaml:"toggleSubtreeExpansion"` } type KeybindingBranchesConfig struct { @@ -967,6 +968,7 @@ func GetDefaultConfig() *UserConfig { CopyFileInfoToClipboard: "y", CollapseAll: "-", ExpandAll: "=", + ToggleSubtreeExpansion: "", }, Branches: KeybindingBranchesConfig{ CopyPullRequestURL: "", diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go index 0289936de16..fe40ec7f493 100644 --- a/pkg/gui/controllers/files_controller.go +++ b/pkg/gui/controllers/files_controller.go @@ -206,6 +206,13 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types Tooltip: self.c.Tr.ExpandAllTooltip, GetDisabledReason: self.require(self.isInTreeMode), }, + { + Key: opts.GetKey(opts.Config.Files.ToggleSubtreeExpansion), + Handler: self.toggleSubtreeExpansion, + Description: self.c.Tr.ToggleSubtreeExpansion, + Tooltip: self.c.Tr.ToggleSubtreeExpansionTooltip, + GetDisabledReason: self.require(self.isInTreeMode), + }, } } @@ -1192,6 +1199,23 @@ func (self *FilesController) handleToggleDirCollapsed() error { return nil } +func (self *FilesController) toggleSubtreeExpansion() error { + node := self.context().GetSelected() + if node == nil { + return nil + } + + if node.File == nil { + // toggle this directory and all descendants + self.context().FileTreeViewModel.ToggleSubtreeExpansion(node.GetInternalPath()) + self.c.PostRefreshUpdate(self.context()) + return nil + } + + // if it's a file, behave like normal enter + return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1}) +} + func (self *FilesController) toggleTreeView() error { self.context().FileTreeViewModel.ToggleShowTree() diff --git a/pkg/gui/filetree/collapsed_paths.go b/pkg/gui/filetree/collapsed_paths.go index e22435b7ffa..ac830b6c271 100644 --- a/pkg/gui/filetree/collapsed_paths.go +++ b/pkg/gui/filetree/collapsed_paths.go @@ -1,6 +1,10 @@ package filetree -import "github.com/jesseduffield/generics/set" +import ( + "strings" + + "github.com/jesseduffield/generics/set" +) type CollapsedPaths struct { collapsedPaths *set.Set[string] @@ -37,7 +41,62 @@ func (self *CollapsedPaths) ToggleCollapsed(path string) { } } +// Expand removes the given path from the collapsed set (i.e. mark expanded) +func (self *CollapsedPaths) Expand(path string) { + self.collapsedPaths.Remove(path) +} + func (self *CollapsedPaths) ExpandAll() { // Could be cleaner if Set had a Clear() method... self.collapsedPaths.RemoveSlice(self.collapsedPaths.ToSlice()) } + +// ToggleSubtreeExpansionOn toggles the collapsed state for the given path and all +// descendant directories within the provided tree root. It's a generic helper +// shared by different tree implementations to avoid code duplication. +func ToggleSubtreeExpansionOn[T any](self *CollapsedPaths, root *Node[T], path string) { + if root == nil { + return + } + + if self.IsCollapsed(path) { + expandSubtree(self, root, path) + } else { + collapseSubtree(self, root, path) + } +} + +// expandSubtree expands the given path and all its descendant directories +func expandSubtree[T any](self *CollapsedPaths, root *Node[T], path string) { + collectAndApply(self, root, path, func(self *CollapsedPaths, p string) { + self.Expand(p) + }) +} + +// collapseSubtree collapses the given path and all its descendant directories +func collapseSubtree[T any](self *CollapsedPaths, root *Node[T], path string) { + collectAndApply(self, root, path, func(self *CollapsedPaths, p string) { + self.Collapse(p) + }) +} + +// collectAndApply traverses the tree and applies the given operation to all +// directories that are the target path or its descendants +func collectAndApply[T any](self *CollapsedPaths, root *Node[T], path string, apply func(*CollapsedPaths, string)) { + var collect func(n *Node[T]) + collect = func(n *Node[T]) { + if n == nil { + return + } + if !n.IsFile() { + p := n.GetInternalPath() + if p == path || strings.HasPrefix(p, path+"/") { + apply(self, p) + } + } + for _, ch := range n.Children { + collect(ch) + } + } + collect(root) +} diff --git a/pkg/gui/filetree/collapsed_paths_test.go b/pkg/gui/filetree/collapsed_paths_test.go new file mode 100644 index 00000000000..323cc154e38 --- /dev/null +++ b/pkg/gui/filetree/collapsed_paths_test.go @@ -0,0 +1,38 @@ +package filetree + +import "testing" + +func TestCollapsedPathsBasic(t *testing.T) { + cp := NewCollapsedPaths() + + p := "./a" + + // initially not collapsed + if cp.IsCollapsed(p) { + t.Fatalf("expected %s to be expanded", p) + } + + // collapse + cp.Collapse(p) + if !cp.IsCollapsed(p) { + t.Fatalf("expected %s to be collapsed", p) + } + + // toggle -> expand + cp.ToggleCollapsed(p) + if cp.IsCollapsed(p) { + t.Fatalf("expected %s to be expanded after toggle", p) + } + + // toggle -> collapse + cp.ToggleCollapsed(p) + if !cp.IsCollapsed(p) { + t.Fatalf("expected %s to be collapsed after toggle", p) + } + + // expand + cp.Expand(p) + if cp.IsCollapsed(p) { + t.Fatalf("expected %s to be expanded after Expand", p) + } +} diff --git a/pkg/gui/filetree/commit_file_tree.go b/pkg/gui/filetree/commit_file_tree.go index 7af83176f60..983e69b0bcf 100644 --- a/pkg/gui/filetree/commit_file_tree.go +++ b/pkg/gui/filetree/commit_file_tree.go @@ -131,3 +131,7 @@ func (self *CommitFileTree) GetFile(path string) *models.CommitFile { func (self *CommitFileTree) InTreeMode() bool { return self.showTree } + +func (self *CommitFileTree) ToggleSubtreeExpansion(path string) { + ToggleSubtreeExpansionOn(self.collapsedPaths, self.tree, path) +} diff --git a/pkg/gui/filetree/commit_file_tree_view_model.go b/pkg/gui/filetree/commit_file_tree_view_model.go index 02b0fff9a86..2454a3a5711 100644 --- a/pkg/gui/filetree/commit_file_tree_view_model.go +++ b/pkg/gui/filetree/commit_file_tree_view_model.go @@ -192,6 +192,23 @@ func (self *CommitFileTreeViewModel) ExpandAll() { } } +// ToggleSubtreeExpansion toggles the collapsed/expanded state for the given +// path and tries to preserve selection. +func (self *CommitFileTreeViewModel) ToggleSubtreeExpansion(path string) { + selectedNode := self.GetSelected() + + self.ICommitFileTree.ToggleSubtreeExpansion(path) + + if selectedNode == nil { + return + } + + index, found := self.GetIndexForPath(selectedNode.path) + if found { + self.SetSelectedLineIdx(index) + } +} + // Try to select the given path if present. If it doesn't exist, or one of the parent directories is // collapsed, do nothing. // Note that filepath is an actual file path, not an internal tree path as with e.g. diff --git a/pkg/gui/filetree/file_tree.go b/pkg/gui/filetree/file_tree.go index d0056a0a86e..00375147064 100644 --- a/pkg/gui/filetree/file_tree.go +++ b/pkg/gui/filetree/file_tree.go @@ -31,6 +31,7 @@ type ITree[T any] interface { SetTree() IsCollapsed(path string) bool ToggleCollapsed(path string) + ToggleSubtreeExpansion(path string) CollapsedPaths() *CollapsedPaths CollapseAll() ExpandAll() @@ -213,3 +214,7 @@ func (self *FileTree) CollapsedPaths() *CollapsedPaths { func (self *FileTree) GetFilter() FileTreeDisplayFilter { return self.filter } + +func (self *FileTree) ToggleSubtreeExpansion(path string) { + ToggleSubtreeExpansionOn(self.collapsedPaths, self.tree, path) +} diff --git a/pkg/gui/filetree/file_tree_view_model.go b/pkg/gui/filetree/file_tree_view_model.go index 3db39d0a197..3d9e7d2280b 100644 --- a/pkg/gui/filetree/file_tree_view_model.go +++ b/pkg/gui/filetree/file_tree_view_model.go @@ -220,3 +220,21 @@ func (self *FileTreeViewModel) ExpandAll() { self.SetSelectedLineIdx(index) } } + +// ToggleSubtreeExpansion toggles the collapsed/expanded state of the given path +// and attempts to preserve selection on that path after the change. +func (self *FileTreeViewModel) ToggleSubtreeExpansion(path string) { + selectedNode := self.GetSelected() + + self.IFileTree.ToggleSubtreeExpansion(path) + + // After changing collapsed state, try to keep selection on the same item + if selectedNode == nil { + return + } + + index, found := self.GetIndexForPath(selectedNode.path) + if found { + self.SetSelectedLineIdx(index) + } +} diff --git a/pkg/gui/filetree/subtree_toggle_test.go b/pkg/gui/filetree/subtree_toggle_test.go new file mode 100644 index 00000000000..46268934902 --- /dev/null +++ b/pkg/gui/filetree/subtree_toggle_test.go @@ -0,0 +1,40 @@ +package filetree + +import ( + "testing" + + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/common" + "github.com/stretchr/testify/assert" +) + +func TestFileTreeViewModel_ToggleSubtreeExpansion_Recursive(t *testing.T) { + files := []*models.File{ + {Path: "a/b/c/file1"}, + {Path: "a/b/d/file2"}, + {Path: "a/e/file3"}, + } + + cmn := common.NewDummyCommon() + vm := NewFileTreeViewModel(func() []*models.File { return files }, cmn, true) + vm.SetTree() + + // ensure initially nothing is collapsed + if vm.IsCollapsed("./a") || vm.IsCollapsed("./a/b") || vm.IsCollapsed("./a/b/c") { + t.Fatalf("expected no collapsed paths initially") + } + + // collapse recursively at ./a + vm.ToggleSubtreeExpansion("./a") + + // now a and descendants should be collapsed + assert.True(t, vm.IsCollapsed("./a")) + assert.True(t, vm.IsCollapsed("./a/b")) + assert.True(t, vm.IsCollapsed("./a/b/c")) + + // toggle again should expand them + vm.ToggleSubtreeExpansion("./a") + assert.False(t, vm.IsCollapsed("./a")) + assert.False(t, vm.IsCollapsed("./a/b")) + assert.False(t, vm.IsCollapsed("./a/b/c")) +} diff --git a/pkg/gui/filetree/toggle_recursive_test.go b/pkg/gui/filetree/toggle_recursive_test.go new file mode 100644 index 00000000000..bf8e63f52b0 --- /dev/null +++ b/pkg/gui/filetree/toggle_recursive_test.go @@ -0,0 +1,83 @@ +package filetree + +import ( + "strings" + "testing" + + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/common" + "github.com/stretchr/testify/assert" +) + +func TestToggleSubtreeExpansion_FileTree(t *testing.T) { + files := []*models.File{ + {Path: "a/b/c/file1"}, + {Path: "a/b/d/file2"}, + {Path: "a/e/file3"}, + } + + cmn := common.NewDummyCommon() + ft := NewFileTree(func() []*models.File { return files }, cmn, true) + ft.SetTree() + + // ensure initially nothing is collapsed + if ft.IsCollapsed("./a") || ft.IsCollapsed("./a/b") || ft.IsCollapsed("./a/b/c") { + t.Fatalf("expected no collapsed paths initially") + } + + // collapse recursively at ./a + ft.ToggleSubtreeExpansion("./a") + + // now a and descendants should be collapsed + assert.True(t, ft.IsCollapsed("./a")) + assert.True(t, ft.IsCollapsed("./a/b")) + assert.True(t, ft.IsCollapsed("./a/b/c")) + + // toggle again should expand them + ft.ToggleSubtreeExpansion("./a") + assert.False(t, ft.IsCollapsed("./a")) + assert.False(t, ft.IsCollapsed("./a/b")) + assert.False(t, ft.IsCollapsed("./a/b/c")) +} + +func TestFileTreeViewModel_ToggleSubtreeExpansion_PreservesSelection(t *testing.T) { + files := []*models.File{ + {Path: "a/b/c/file1"}, + {Path: "a/b/d/file2"}, + {Path: "a/e/file3"}, + } + + // create view model and set tree + cmn := common.NewDummyCommon() + vm := NewFileTreeViewModel(func() []*models.File { return files }, cmn, true) + vm.SetTree() + + // select the deepest file + idx, found := vm.GetIndexForPath("a/b/c/file1") + if !found { + // try with root prefix + idx, found = vm.GetIndexForPath("./a/b/c/file1") + } + if !found { + t.Fatalf("expected to find path") + } + vm.SetSelectedLineIdx(idx) + + // toggle recursively on ./a/b + vm.ToggleSubtreeExpansion("./a/b") + + // selection should either be preserved (if still visible) or be an ancestor + selected := vm.GetSelected() + if selected == nil { + t.Fatalf("expected a selected node") + } + + // normalize paths by trimming a possible leading "./" + normalize := func(p string) string { return strings.TrimPrefix(p, "./") } + selPath := normalize(selected.GetPath()) + + // After collapsing ./a/b, the selected node must not be inside the collapsed subtree + if strings.HasPrefix(selPath, "a/b/") { + t.Fatalf("expected selected to not be inside the collapsed subtree, got %s", selPath) + } +} diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index cb070b89207..7c053ec06e1 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -285,6 +285,8 @@ type TranslationSet struct { CollapseAllTooltip string ExpandAll string ExpandAllTooltip string + ToggleSubtreeExpansion string + ToggleSubtreeExpansionTooltip string DisabledInFlatView string FileEnter string FileEnterTooltip string @@ -1376,6 +1378,8 @@ func EnglishTranslationSet() *TranslationSet { CollapseAllTooltip: "Collapse all directories in the files tree", ExpandAll: "Expand all files", ExpandAllTooltip: "Expand all directories in the file tree", + ToggleSubtreeExpansion: "Recursively expand/collapse the selected directory", + ToggleSubtreeExpansionTooltip: "Recursively expand/collapse the selected directory in the file tree", 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.", diff --git a/pkg/integration/tests/file/toggle_subtree_expansion.go b/pkg/integration/tests/file/toggle_subtree_expansion.go new file mode 100644 index 00000000000..fecb5d10c8a --- /dev/null +++ b/pkg/integration/tests/file/toggle_subtree_expansion.go @@ -0,0 +1,60 @@ +package file + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var ToggleSubtreeExpansion = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Toggle subtree expansion recursively with Alt+Enter", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + }, + SetupRepo: func(shell *Shell) { + shell.CreateDir("level1") + shell.CreateDir("level1/level2") + shell.CreateDir("level1/level2/level3") + shell.CreateFile("level1/level2/level3/file-deep", "deep content\n") + shell.CreateFile("level1/file-mid", "mid content\n") + shell.CreateDir("other") + shell.CreateFile("other/file-other", "other content\n") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + IsFocused(). + Lines( + Equals("▼ /").IsSelected(), + Equals(" ▼ level1"), + Equals(" ▼ level2/level3"), + Equals(" ?? file-deep"), + Equals(" ?? file-mid"), + Equals(" ▼ other"), + Equals(" ?? file-other"), + ) + + // Navigate to level1 directory and press Alt+Enter to collapse recursively + t.Views().Files(). + SelectNextItem(). + Press(keys.Files.ToggleSubtreeExpansion). + Lines( + Equals("▼ /"), + Equals(" ▶ level1").IsSelected(), + Equals(" ▼ other"), + Equals(" ?? file-other"), + ) + + // Press Alt+Enter again on level1 to expand recursively + t.Views().Files(). + Press(keys.Files.ToggleSubtreeExpansion). + Lines( + Equals("▼ /"), + Equals(" ▼ level1").IsSelected(), + Equals(" ▼ level2/level3"), + Equals(" ?? file-deep"), + Equals(" ?? file-mid"), + Equals(" ▼ other"), + Equals(" ?? file-other"), + ) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index da21d1f0d3c..e1614f2c017 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -223,6 +223,7 @@ var tests = []*components.IntegrationTest{ file.StageChildrenRangeSelect, file.StageDeletedRangeSelect, file.StageRangeSelect, + file.ToggleSubtreeExpansion, filter_and_search.FilterByFileStatus, filter_and_search.FilterCommitFiles, filter_and_search.FilterFiles, diff --git a/schema/config.json b/schema/config.json index 4aaa7a655e4..9540e637a42 100644 --- a/schema/config.json +++ b/schema/config.json @@ -1142,6 +1142,10 @@ "expandAll": { "type": "string", "default": "=" + }, + "toggleSubtreeExpansion": { + "type": "string", + "default": "\u003ca-enter\u003e" } }, "additionalProperties": false,