From ee3bb06b2a69c5483794ae6d7c9f0c3d14f2f71d Mon Sep 17 00:00:00 2001 From: blakemckeany Date: Fri, 13 Mar 2026 11:11:32 +1300 Subject: [PATCH] Add support for clicking on arrows in the file list to expand/collapse directories Co-authored-by: Stefan Haller --- .../controllers/commits_files_controller.go | 24 ++++++ pkg/gui/controllers/files_controller.go | 24 ++++++ .../tests/file/click_arrow_to_collapse.go | 86 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 4 files changed, 135 insertions(+) create mode 100644 pkg/integration/tests/file/click_arrow_to_collapse.go diff --git a/pkg/gui/controllers/commits_files_controller.go b/pkg/gui/controllers/commits_files_controller.go index 7d677b756..e4a7cba68 100644 --- a/pkg/gui/controllers/commits_files_controller.go +++ b/pkg/gui/controllers/commits_files_controller.go @@ -142,6 +142,30 @@ func (self *CommitFilesController) context() *context.CommitFilesContext { return self.c.Contexts().CommitFiles } +func (self *CommitFilesController) GetOnClick() func(opts gocui.ViewMouseBindingOpts) error { + return func(opts gocui.ViewMouseBindingOpts) error { + clickedIdx := self.context().GetSelectedLineIdx() + node := self.context().CommitFileTreeViewModel.Get(clickedIdx) + if node == nil || node.File != nil { + return nil + } + + // The arrow is at column visualDepth*2 (after indentation of 2 spaces per level). + // Only treat clicks on the arrow and the trailing space as arrow clicks. + visualDepth := self.context().CommitFileTreeViewModel.GetVisualDepth(clickedIdx) + arrowStartCol := visualDepth * 2 + arrowEndCol := arrowStartCol + 1 + if opts.X < arrowStartCol || opts.X > arrowEndCol { + return nil + } + + self.context().CommitFileTreeViewModel.ToggleCollapsed(node.GetInternalPath()) + self.c.PostRefreshUpdate(self.context()) + + return nil + } +} + func (self *CommitFilesController) GetOnRenderToMain() func() { return func() { node := self.context().GetSelected() diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go index 557b40e6a..acc615cce 100644 --- a/pkg/gui/controllers/files_controller.go +++ b/pkg/gui/controllers/files_controller.go @@ -229,6 +229,30 @@ func (self *FilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []* } } +func (self *FilesController) GetOnClick() func(opts gocui.ViewMouseBindingOpts) error { + return func(opts gocui.ViewMouseBindingOpts) error { + clickedIdx := self.context().GetSelectedLineIdx() + node := self.context().FileTreeViewModel.Get(clickedIdx) + if node == nil || node.File != nil { + return nil + } + + // The arrow is at column visualDepth*2 (after indentation of 2 spaces per level). + // Only treat clicks on the arrow and the trailing space as arrow clicks. + visualDepth := self.context().FileTreeViewModel.GetVisualDepth(clickedIdx) + arrowStartCol := visualDepth * 2 + arrowEndCol := arrowStartCol + 1 + if opts.X < arrowStartCol || opts.X > arrowEndCol { + return nil + } + + self.context().FileTreeViewModel.ToggleCollapsed(node.GetInternalPath()) + self.c.PostRefreshUpdate(self.context()) + + return nil + } +} + func (self *FilesController) GetOnRenderToMain() func() { return func() { self.c.Helpers().Diff.WithDiffModeCheck(func() { diff --git a/pkg/integration/tests/file/click_arrow_to_collapse.go b/pkg/integration/tests/file/click_arrow_to_collapse.go new file mode 100644 index 000000000..26d8b8d6a --- /dev/null +++ b/pkg/integration/tests/file/click_arrow_to_collapse.go @@ -0,0 +1,86 @@ +package file + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var ClickArrowToCollapse = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Click the arrow on a directory to collapse/expand it", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.CreateDir("dir") + shell.CreateFile("dir/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"), + Equals(" ?? file-one"), + Equals(" ▼ dir2"), + Equals(" ?? file-two"), + ) + + // Click the arrow on "dir" (row 1, column 2) to collapse it + t.Views().Files(). + Click(2, 1). + Lines( + Equals("▼ /"), + Equals(" ▶ dir").IsSelected(), + Equals(" ▼ dir2"), + Equals(" ?? file-two"), + ) + + // Click one to the right of the arrow on "dir2" (row 2, column 3) to collapse it + // Arrow + space after should register a collapse toggle + t.Views().Files(). + Click(3, 2). + Lines( + Equals("▼ /"), + Equals(" ▶ dir"), + Equals(" ▶ dir2").IsSelected(), + ) + + // Click one to the left of the arrow on "dir2" (row 2, column 1) + // Space before arrow should not register a collapse toggle + t.Views().Files(). + Click(1, 2). + Lines( + Equals("▼ /"), + Equals(" ▶ dir"), + Equals(" ▶ dir2").IsSelected(), + ) + + // Clicking on the file/directory name "dir" should change selected but not toggle collapse + t.Views().Files(). + Click(5, 1). + Lines( + Equals("▼ /"), + Equals(" ▶ dir").IsSelected(), + Equals(" ▶ dir2"), + ) + + // Click the arrow again to expand it + t.Views().Files(). + Click(2, 1). + Lines( + Equals("▼ /"), + Equals(" ▼ dir").IsSelected(), + Equals(" ?? file-one"), + Equals(" ▶ dir2"), + ) + + // Click the arrow on the root "/" (row 0, column 0) to collapse everything + t.Views().Files(). + Click(0, 0). + Lines( + Equals("▶ /").IsSelected(), + ) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index a10bc210e..85183406b 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -210,6 +210,7 @@ var tests = []*components.IntegrationTest{ diff.DiffNonStickyRange, diff.IgnoreWhitespace, diff.RenameSimilarityThresholdChange, + file.ClickArrowToCollapse, file.CollapseExpand, file.CopyMenu, file.DirWithUntrackedFile,