mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-08-24 10:13:41 -05:00
Create a range selection in list views by dragging with the mouse (#5856)
Like in the staging view, drag with the mouse in a list view to create a non-sticky range selection (including auto-scrolling as the mouse reaches the view edge).
This commit is contained in:
@@ -2,6 +2,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -16,18 +17,27 @@ func NewListControllerFactory(c *ControllerCommon) *ListControllerFactory {
|
||||
}
|
||||
|
||||
func (self *ListControllerFactory) Create(context types.IListContext) *ListController {
|
||||
return &ListController{
|
||||
controller := &ListController{
|
||||
baseController: baseController{},
|
||||
c: self.c,
|
||||
context: context,
|
||||
}
|
||||
controller.dragAutoscroller = helpers.NewDragAutoscroller(
|
||||
self.c.HelperCommon,
|
||||
context,
|
||||
func(int) bool { return context.GetList().IsSelectingRange() },
|
||||
controller.handleDragAutoscroll,
|
||||
)
|
||||
return controller
|
||||
}
|
||||
|
||||
type ListController struct {
|
||||
baseController
|
||||
c *ControllerCommon
|
||||
|
||||
context types.IListContext
|
||||
context types.IListContext
|
||||
dragAutoscroller *helpers.DragAutoscroller
|
||||
draggingWithMouse bool
|
||||
}
|
||||
|
||||
func (self *ListController) Context() types.Context {
|
||||
@@ -257,6 +267,48 @@ func (self *ListController) HandleClick(opts gocui.ViewMouseBindingOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ListController) HandleDrag(opts gocui.ViewMouseBindingOpts) error {
|
||||
self.draggingWithMouse = true
|
||||
self.selectRangeThroughViewIndex(opts.Y)
|
||||
originY, _ := self.context.GetViewTrait().ViewPortYBounds()
|
||||
self.dragAutoscroller.Update(opts.Y - originY)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ListController) selectRangeThroughViewIndex(viewIndex int) {
|
||||
list := self.context.GetList()
|
||||
newSelectedLineIdx := self.context.ViewIndexToModelIndex(viewIndex)
|
||||
list.ExpandNonStickyRange(newSelectedLineIdx - list.GetSelectedLineIdx())
|
||||
|
||||
self.context.HandleFocus(types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (self *ListController) handleDragAutoscroll(viewIndex int) bool {
|
||||
if !self.context.GetList().IsSelectingRange() {
|
||||
return false
|
||||
}
|
||||
|
||||
self.context.SetNeedRerenderVisibleLines()
|
||||
self.selectRangeThroughViewIndex(viewIndex)
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *ListController) handleDragRelease() error {
|
||||
self.draggingWithMouse = false
|
||||
self.dragAutoscroller.Cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ListController) GetOnFocusLost() func(types.OnFocusLostOpts) {
|
||||
return func(types.OnFocusLostOpts) {
|
||||
self.dragAutoscroller.Cancel()
|
||||
if self.draggingWithMouse {
|
||||
self.draggingWithMouse = false
|
||||
self.c.GocuiGui().CancelMouseCapture()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ListController) pushContextIfNotFocused() error {
|
||||
if !self.isFocused() {
|
||||
self.c.Context().Push(self.context, types.OnFocusOpts{})
|
||||
@@ -295,7 +347,7 @@ func (self *ListController) GetKeybindings(opts types.KeybindingsOpts) []*types.
|
||||
}
|
||||
|
||||
func (self *ListController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
|
||||
return []*gocui.ViewMouseBinding{
|
||||
bindings := []*gocui.ViewMouseBinding{
|
||||
{
|
||||
ViewName: self.context.GetViewName(),
|
||||
Key: gocui.MouseWheelUp,
|
||||
@@ -312,4 +364,22 @@ func (self *ListController) GetMouseKeybindings(opts types.KeybindingsOpts) []*g
|
||||
Handler: func(gocui.ViewMouseBindingOpts) error { return self.HandleScrollDown() },
|
||||
},
|
||||
}
|
||||
|
||||
if self.context.RangeSelectEnabled() {
|
||||
bindings = append(bindings,
|
||||
&gocui.ViewMouseBinding{
|
||||
ViewName: self.context.GetViewName(),
|
||||
Key: gocui.MouseLeft,
|
||||
Modifier: gocui.ModMotion,
|
||||
Handler: self.HandleDrag,
|
||||
},
|
||||
&gocui.ViewMouseBinding{
|
||||
ViewName: self.context.GetViewName(),
|
||||
Key: gocui.MouseRelease,
|
||||
Handler: func(gocui.ViewMouseBindingOpts) error { return self.handleDragRelease() },
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return bindings
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ var RangeSelect = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
config.GetUserConfig().Gui.UseHunkModeInStagingView = false
|
||||
config.GetUserConfig().Gui.ExpandFocusedSidePanel = true
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
// We're testing the commits view as our representative list context,
|
||||
@@ -51,6 +52,7 @@ var RangeSelect = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
}
|
||||
shell.CreateFileAndAdd("file1", "staged\n")
|
||||
shell.UpdateFile("file1", fileContent)
|
||||
shell.NewBranch("branch1").NewBranch("branch2")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
assertRangeSelectBehaviour := func(v *ViewDriver, focusOtherView func(), lineIdxOfFirstItem int) {
|
||||
@@ -179,5 +181,46 @@ var RangeSelect = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
PressEnter()
|
||||
|
||||
assertRangeSelectBehaviour(t.Views().Staging().IsFocused(), func() { t.Views().Staging().PressTab() }, 6)
|
||||
|
||||
t.Views().Branches().Focus()
|
||||
t.Views().Branches().
|
||||
SelectedLines(
|
||||
Contains("branch2"),
|
||||
)
|
||||
t.Views().Commits().
|
||||
ClickAndHold(1, 3).
|
||||
MouseMoveToView(t.Views().Branches(), 1, 2).
|
||||
SelectedLines(
|
||||
Contains("line 1"),
|
||||
Contains("line 2"),
|
||||
Contains("line 3"),
|
||||
Contains("line 4"),
|
||||
).
|
||||
Tap(func() {
|
||||
t.Views().Branches().SelectedLines(
|
||||
Contains("branch2"),
|
||||
)
|
||||
}).
|
||||
MouseRelease()
|
||||
|
||||
t.Views().Branches().Focus()
|
||||
t.Views().Commits().
|
||||
ClickAndHold(1, 0).
|
||||
SelectedLines(
|
||||
Contains("line 1"),
|
||||
).
|
||||
RepeatMouseMove().
|
||||
SelectedLines(
|
||||
Contains("line 1"),
|
||||
).
|
||||
MouseMove(1, 3).
|
||||
SelectedLines(
|
||||
Contains("line 1"),
|
||||
Contains("line 2"),
|
||||
Contains("line 3"),
|
||||
Contains("line 4"),
|
||||
).
|
||||
MouseRelease().
|
||||
Click(1, 0)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -17,6 +17,7 @@ var RangeSelectWithAutoscroll = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
config.GetUserConfig().Gui.UseHunkModeInStagingView = false
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateNCommits(40)
|
||||
fileContent := "base\n"
|
||||
shell.CreateFileAndAdd("file1", fileContent)
|
||||
for i := 1; i <= 40; i++ {
|
||||
@@ -25,6 +26,14 @@ var RangeSelectWithAutoscroll = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
shell.UpdateFile("file1", fileContent)
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().Focus()
|
||||
t.Views().Commits().
|
||||
ClickAndHold(1, 0).
|
||||
MouseMoveToBottom(1).
|
||||
OriginYAtLeast(3).
|
||||
SelectedLineIdxAtLeast(3).
|
||||
MouseRelease()
|
||||
|
||||
t.Views().Files().
|
||||
Focus().
|
||||
PressEnter()
|
||||
|
||||
Reference in New Issue
Block a user