mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Add support for clicking on arrows in the file list to expand/collapse directories (#5365)
### PR Description This PR adds support for clicking on arrows in the file changes list view, so that a mouse can be used to expand/collapse directories. This enhancement was raised here as issue #5088.
This commit is contained in:
commit
d864edb0c3
|
|
@ -15,7 +15,8 @@ type BaseContext struct {
|
|||
|
||||
keybindingsFns []types.KeybindingsFn
|
||||
mouseKeybindingsFns []types.MouseKeybindingsFn
|
||||
onClickFn func() error
|
||||
onDoubleClickFn func() error
|
||||
onClickFn func(opts gocui.ViewMouseBindingOpts) error
|
||||
onClickFocusedMainViewFn onClickFocusedMainViewFn
|
||||
onRenderToMainFn func()
|
||||
onFocusFns []onFocusFn
|
||||
|
|
@ -140,12 +141,22 @@ func (self *BaseContext) ClearAllAttachedControllerFunctions() {
|
|||
self.mouseKeybindingsFns = nil
|
||||
self.onFocusFns = nil
|
||||
self.onFocusLostFns = nil
|
||||
self.onDoubleClickFn = nil
|
||||
self.onClickFn = nil
|
||||
self.onClickFocusedMainViewFn = nil
|
||||
self.onRenderToMainFn = nil
|
||||
}
|
||||
|
||||
func (self *BaseContext) AddOnClickFn(fn func() error) {
|
||||
func (self *BaseContext) AddOnDoubleClickFn(fn func() error) {
|
||||
if fn != nil {
|
||||
if self.onDoubleClickFn != nil {
|
||||
panic("only one controller is allowed to set an onDoubleClickFn")
|
||||
}
|
||||
self.onDoubleClickFn = fn
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BaseContext) AddOnClickFn(fn func(opts gocui.ViewMouseBindingOpts) error) {
|
||||
if fn != nil {
|
||||
if self.onClickFn != nil {
|
||||
panic("only one controller is allowed to set an onClickFn")
|
||||
|
|
@ -163,7 +174,11 @@ func (self *BaseContext) AddOnClickFocusedMainViewFn(fn onClickFocusedMainViewFn
|
|||
}
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetOnClick() func() error {
|
||||
func (self *BaseContext) GetOnDoubleClick() func() error {
|
||||
return self.onDoubleClickFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetOnClick() func(opts gocui.ViewMouseBindingOpts) error {
|
||||
return self.onClickFn
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,6 @@ func (self *SuggestionsContext) RangeSelectEnabled() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (self *SuggestionsContext) GetOnClick() func() error {
|
||||
func (self *SuggestionsContext) GetOnDoubleClick() func() error {
|
||||
return self.State.OnConfirm
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ func AttachControllers(context types.Context, controllers ...types.IController)
|
|||
for _, controller := range controllers {
|
||||
context.AddKeybindingsFn(controller.GetKeybindings)
|
||||
context.AddMouseKeybindingsFn(controller.GetMouseKeybindings)
|
||||
context.AddOnDoubleClickFn(controller.GetOnDoubleClick())
|
||||
context.AddOnClickFn(controller.GetOnClick())
|
||||
context.AddOnClickFocusedMainViewFn(controller.GetOnClickFocusedMainView())
|
||||
context.AddOnRenderToMainFn(controller.GetOnRenderToMain())
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ func (self *baseController) GetMouseKeybindings(opts types.KeybindingsOpts) []*g
|
|||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnClick() func() error {
|
||||
func (self *baseController) GetOnDoubleClick() func() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -23,6 +23,10 @@ func (self *baseController) GetOnClickFocusedMainView() func(mainViewName string
|
|||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnClick() func(opts gocui.ViewMouseBindingOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnRenderToMain() func() {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
@ -329,7 +353,7 @@ func (self *FilesController) GetOnRenderToMain() func() {
|
|||
}
|
||||
}
|
||||
|
||||
func (self *FilesController) GetOnClick() func() error {
|
||||
func (self *FilesController) GetOnDoubleClick() func() error {
|
||||
return self.withItemGraceful(func(node *filetree.FileNode) error {
|
||||
return self.press([]*filetree.FileNode{node})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -243,10 +243,17 @@ func (self *ListController) HandleClick(opts gocui.ViewMouseBindingOpts) error {
|
|||
|
||||
self.context.GetList().SetSelection(newSelectedLineIdx)
|
||||
|
||||
if opts.IsDoubleClick && alreadyFocused && self.context.GetOnClick() != nil {
|
||||
return self.context.GetOnClick()()
|
||||
if opts.IsDoubleClick && alreadyFocused && self.context.GetOnDoubleClick() != nil {
|
||||
return self.context.GetOnDoubleClick()()
|
||||
}
|
||||
|
||||
self.context.HandleFocus(types.OnFocusOpts{})
|
||||
|
||||
// Let view-specific controllers do additional click handling
|
||||
if self.context.GetOnClick() != nil {
|
||||
return self.context.GetOnClick()(opts)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func (self *MenuController) GetKeybindings(opts types.KeybindingsOpts) []*types.
|
|||
return bindings
|
||||
}
|
||||
|
||||
func (self *MenuController) GetOnClick() func() error {
|
||||
func (self *MenuController) GetOnDoubleClick() func() error {
|
||||
return self.withItemGraceful(self.press)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ func (self *RemotesController) GetOnRenderToMain() func() {
|
|||
}
|
||||
}
|
||||
|
||||
func (self *RemotesController) GetOnClick() func() error {
|
||||
func (self *RemotesController) GetOnDoubleClick() func() error {
|
||||
return self.withItemGraceful(self.enter)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ func (self *SubmodulesController) GetKeybindings(opts types.KeybindingsOpts) []*
|
|||
}
|
||||
}
|
||||
|
||||
func (self *SubmodulesController) GetOnClick() func() error {
|
||||
func (self *SubmodulesController) GetOnDoubleClick() func() error {
|
||||
return self.withItemGraceful(self.enter)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ func (self *SwitchToDiffFilesController) Context() types.Context {
|
|||
return self.context
|
||||
}
|
||||
|
||||
func (self *SwitchToDiffFilesController) GetOnClick() func() error {
|
||||
func (self *SwitchToDiffFilesController) GetOnDoubleClick() func() error {
|
||||
return func() error {
|
||||
if self.canEnter() == nil {
|
||||
return self.enter()
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func (self *SwitchToSubCommitsController) GetKeybindings(opts types.KeybindingsO
|
|||
return bindings
|
||||
}
|
||||
|
||||
func (self *SwitchToSubCommitsController) GetOnClick() func() error {
|
||||
func (self *SwitchToSubCommitsController) GetOnDoubleClick() func() error {
|
||||
return self.viewCommits
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ func (self *WorktreesController) remove(worktree *models.Worktree) error {
|
|||
return self.c.Helpers().Worktree.Remove(worktree, false)
|
||||
}
|
||||
|
||||
func (self *WorktreesController) GetOnClick() func() error {
|
||||
func (self *WorktreesController) GetOnDoubleClick() func() error {
|
||||
return self.withItemGraceful(self.enter)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -151,6 +151,10 @@ func (self *CommitFileTree) GetFile(path string) *models.CommitFile {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitFileTree) GetVisualDepth(index int) int {
|
||||
return self.tree.GetVisualDepthAtIndex(index+1, self.collapsedPaths) // +1 to skip root
|
||||
}
|
||||
|
||||
func (self *CommitFileTree) InTreeMode() bool {
|
||||
return self.showTree
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ type ITree[T any] interface {
|
|||
CollapsedPaths() *CollapsedPaths
|
||||
CollapseAll()
|
||||
ExpandAll()
|
||||
GetVisualDepth(index int) int
|
||||
}
|
||||
|
||||
type IFileTree interface {
|
||||
|
|
@ -221,6 +222,10 @@ func (self *FileTree) CollapsedPaths() *CollapsedPaths {
|
|||
return self.collapsedPaths
|
||||
}
|
||||
|
||||
func (self *FileTree) GetVisualDepth(index int) int {
|
||||
return self.tree.GetVisualDepthAtIndex(index+1, self.collapsedPaths) // +1 to skip root
|
||||
}
|
||||
|
||||
func (self *FileTree) GetStatusFilter() FileTreeDisplayFilter {
|
||||
return self.filter
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,29 +202,43 @@ func (self *Node[T]) GetNodeAtIndex(index int, collapsedPaths *CollapsedPaths) *
|
|||
return nil
|
||||
}
|
||||
|
||||
node, _ := self.getNodeAtIndexAux(index, collapsedPaths)
|
||||
node, _, _ := self.getNodeAtIndexAux(index, collapsedPaths, -1)
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (self *Node[T]) getNodeAtIndexAux(index int, collapsedPaths *CollapsedPaths) (*Node[T], int) {
|
||||
// GetVisualDepthAtIndex returns the visual depth (indentation level) of the
|
||||
// node at the given flat index. Visual depth differs from tree depth because
|
||||
// compressed nodes (e.g. "a/b/") count as a single visual level.
|
||||
// Returns -1 if the index is out of range.
|
||||
func (self *Node[T]) GetVisualDepthAtIndex(index int, collapsedPaths *CollapsedPaths) int {
|
||||
if self == nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
_, _, depth := self.getNodeAtIndexAux(index, collapsedPaths, -1)
|
||||
|
||||
return depth
|
||||
}
|
||||
|
||||
func (self *Node[T]) getNodeAtIndexAux(index int, collapsedPaths *CollapsedPaths, visualDepth int) (*Node[T], int, int) {
|
||||
offset := 1
|
||||
|
||||
if index == 0 {
|
||||
return self, offset
|
||||
return self, offset, visualDepth
|
||||
}
|
||||
|
||||
if !collapsedPaths.IsCollapsed(self.path) {
|
||||
for _, child := range self.Children {
|
||||
foundNode, offsetChange := child.getNodeAtIndexAux(index-offset, collapsedPaths)
|
||||
foundNode, offsetChange, depth := child.getNodeAtIndexAux(index-offset, collapsedPaths, visualDepth+1)
|
||||
offset += offsetChange
|
||||
if foundNode != nil {
|
||||
return foundNode, offset
|
||||
return foundNode, offset, depth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, offset
|
||||
return nil, offset, -1
|
||||
}
|
||||
|
||||
func (self *Node[T]) GetIndexForPath(path string, collapsedPaths *CollapsedPaths) (int, bool) {
|
||||
|
|
|
|||
134
pkg/gui/filetree/node_test.go
Normal file
134
pkg/gui/filetree/node_test.go
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
package filetree
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetVisualDepthAtIndex(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
files []*models.File
|
||||
showRootItem bool
|
||||
collapsedPaths []string
|
||||
expectedDepths []int // one per visible node, skipping root
|
||||
}{
|
||||
{
|
||||
name: "flat files with root item",
|
||||
files: []*models.File{
|
||||
{Path: "a"},
|
||||
{Path: "b"},
|
||||
},
|
||||
showRootItem: true,
|
||||
// Displayed as:
|
||||
// index 0: ▼ / (depth 0, the "." root dir)
|
||||
// index 1: a (depth 1)
|
||||
// index 2: b (depth 1)
|
||||
expectedDepths: []int{0, 1, 1},
|
||||
},
|
||||
{
|
||||
name: "flat files without root item",
|
||||
files: []*models.File{
|
||||
{Path: "a"},
|
||||
{Path: "b"},
|
||||
},
|
||||
showRootItem: false,
|
||||
// Displayed as:
|
||||
// index 0: a (depth 0)
|
||||
// index 1: b (depth 0)
|
||||
expectedDepths: []int{0, 0},
|
||||
},
|
||||
{
|
||||
name: "nested directories with root item",
|
||||
files: []*models.File{
|
||||
{Path: "dir/a"},
|
||||
{Path: "dir/b"},
|
||||
{Path: "c"},
|
||||
},
|
||||
showRootItem: true,
|
||||
// Displayed as:
|
||||
// index 0: ▼ / (depth 0)
|
||||
// index 1: ▼ dir (depth 1)
|
||||
// index 2: a (depth 2)
|
||||
// index 3: b (depth 2)
|
||||
// index 4: c (depth 1)
|
||||
expectedDepths: []int{0, 1, 2, 2, 1},
|
||||
},
|
||||
{
|
||||
name: "compressed paths with root item",
|
||||
files: []*models.File{
|
||||
{Path: "dir1/dir3/a"},
|
||||
{Path: "dir2/dir4/b"},
|
||||
},
|
||||
showRootItem: true,
|
||||
// Tree compresses dir1/dir3 and dir2/dir4 into single nodes.
|
||||
// Displayed as:
|
||||
// index 0: ▼ / (depth 0)
|
||||
// index 1: ▼ dir1/dir3 (depth 1, compressed)
|
||||
// index 2: a (depth 2)
|
||||
// index 3: ▼ dir2/dir4 (depth 1, compressed)
|
||||
// index 4: b (depth 2)
|
||||
expectedDepths: []int{0, 1, 2, 1, 2},
|
||||
},
|
||||
{
|
||||
name: "compressed paths without root item",
|
||||
files: []*models.File{
|
||||
{Path: "dir1/dir3/a"},
|
||||
{Path: "dir2/dir4/b"},
|
||||
},
|
||||
showRootItem: false,
|
||||
// Displayed as:
|
||||
// index 0: ▼ dir1/dir3 (depth 0, compressed)
|
||||
// index 1: a (depth 1)
|
||||
// index 2: ▼ dir2/dir4 (depth 0, compressed)
|
||||
// index 3: b (depth 1)
|
||||
expectedDepths: []int{0, 1, 0, 1},
|
||||
},
|
||||
{
|
||||
name: "collapsed directory hides children",
|
||||
files: []*models.File{
|
||||
{Path: "dir/a"},
|
||||
{Path: "dir/b"},
|
||||
{Path: "c"},
|
||||
},
|
||||
showRootItem: true,
|
||||
collapsedPaths: []string{"./dir"},
|
||||
// Displayed as:
|
||||
// index 0: ▼ / (depth 0)
|
||||
// index 1: ▶ dir (depth 1, collapsed)
|
||||
// index 2: c (depth 1)
|
||||
expectedDepths: []int{0, 1, 1},
|
||||
},
|
||||
{
|
||||
name: "out of range returns -1",
|
||||
files: []*models.File{
|
||||
{Path: "a"},
|
||||
},
|
||||
showRootItem: false,
|
||||
expectedDepths: []int{0},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
tree := BuildTreeFromFiles(s.files, s.showRootItem)
|
||||
collapsedPaths := NewCollapsedPaths()
|
||||
for _, p := range s.collapsedPaths {
|
||||
collapsedPaths.Collapse(p)
|
||||
}
|
||||
|
||||
for i, expectedDepth := range s.expectedDepths {
|
||||
// +1 to skip the invisible root node, matching what FileTree.GetVisualDepth does
|
||||
actualDepth := tree.GetVisualDepthAtIndex(i+1, collapsedPaths)
|
||||
assert.Equal(t, expectedDepth, actualDepth,
|
||||
"index %d: expected depth %d, got %d", i, expectedDepth, actualDepth)
|
||||
}
|
||||
|
||||
// Verify out-of-range returns -1
|
||||
outOfRange := tree.GetVisualDepthAtIndex(len(s.expectedDepths)+1, collapsedPaths)
|
||||
assert.Equal(t, -1, outOfRange, "out of range index should return -1")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -91,13 +91,16 @@ type IBaseContext interface {
|
|||
AddMouseKeybindingsFn(MouseKeybindingsFn)
|
||||
ClearAllAttachedControllerFunctions()
|
||||
|
||||
// This is a bit of a hack at the moment: we currently only set an onclick function so that
|
||||
// our list controller can come along and wrap it in a list-specific click handler.
|
||||
// This is a bit of a hack at the moment: we currently only set an onDoubleClick function so
|
||||
// that the generic ListController can be specialized by view-specific controllers.
|
||||
// We'll need to think of a better way to do this.
|
||||
AddOnClickFn(func() error)
|
||||
AddOnDoubleClickFn(func() error)
|
||||
// Likewise for the focused main view: we need this to communicate between a
|
||||
// side panel controller and the focused main view controller.
|
||||
AddOnClickFocusedMainViewFn(func(mainViewName string, clickedLineIdx int) error)
|
||||
// Adding on to the above, this is so that a list-specific handler can register
|
||||
// a hook for doing additional click handling
|
||||
AddOnClickFn(func(opts gocui.ViewMouseBindingOpts) error)
|
||||
|
||||
AddOnRenderToMainFn(func())
|
||||
AddOnFocusFn(func(OnFocusOpts))
|
||||
|
|
@ -246,7 +249,19 @@ type (
|
|||
type HasKeybindings interface {
|
||||
GetKeybindings(opts KeybindingsOpts) []*Binding
|
||||
GetMouseKeybindings(opts KeybindingsOpts) []*gocui.ViewMouseBinding
|
||||
GetOnClick() func() error
|
||||
|
||||
// Implement this to get called when there's a double-click on the view. Only supported by list
|
||||
// views currently. Will be called after the double-clicked list entry has been selected.
|
||||
GetOnDoubleClick() func() error
|
||||
|
||||
// Implement this to get called for any non-double-click in the view. Only supported by list
|
||||
// views currently. Will be called after the clicked list entry has been selected, and
|
||||
// HandleFocus has already been called (so the main view is up to date). Should return nil if it
|
||||
// decides not to do anything with the click.
|
||||
GetOnClick() func(opts gocui.ViewMouseBindingOpts) error
|
||||
|
||||
// Implement this in a side-panel controller to get called when there's a click in the main view
|
||||
// that belongs to your panel while the main view is already focused.
|
||||
GetOnClickFocusedMainView() func(mainViewName string, clickedLineIdx int) error
|
||||
}
|
||||
|
||||
|
|
|
|||
86
pkg/integration/tests/file/click_arrow_to_collapse.go
Normal file
86
pkg/integration/tests/file/click_arrow_to_collapse.go
Normal file
|
|
@ -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(),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
|
@ -210,6 +210,7 @@ var tests = []*components.IntegrationTest{
|
|||
diff.DiffNonStickyRange,
|
||||
diff.IgnoreWhitespace,
|
||||
diff.RenameSimilarityThresholdChange,
|
||||
file.ClickArrowToCollapse,
|
||||
file.CollapseExpand,
|
||||
file.CopyMenu,
|
||||
file.DirWithUntrackedFile,
|
||||
|
|
|
|||
Loading…
Reference in a new issue