Create a range selection by dragging with the mouse in list views

Dragging with the left button held now extends the selection from the
pressed line, exactly like moving with shift+up/down does. We use the
non-sticky flavor so that the range collapses on the next plain cursor
movement, again matching the keyboard behavior.

The binding is only registered for contexts that support range selection
in the first place; dragging in other lists continues to do nothing.
This commit is contained in:
Stefan Haller 2026-07-22 09:49:50 +02:00
parent 10bac9dbf6
commit e7998356be
2 changed files with 64 additions and 1 deletions

View file

@ -257,6 +257,15 @@ func (self *ListController) HandleClick(opts gocui.ViewMouseBindingOpts) error {
return nil
}
func (self *ListController) HandleDrag(opts gocui.ViewMouseBindingOpts) error {
list := self.context.GetList()
newSelectedLineIdx := self.context.ViewIndexToModelIndex(opts.Y)
list.ExpandNonStickyRange(newSelectedLineIdx - list.GetSelectedLineIdx())
self.context.HandleFocus(types.OnFocusOpts{})
return nil
}
func (self *ListController) pushContextIfNotFocused() error {
if !self.isFocused() {
self.c.Context().Push(self.context, types.OnFocusOpts{})
@ -295,7 +304,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 +321,15 @@ 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,
})
}
return bindings
}

View file

@ -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)
},
})