diff --git a/pkg/gui/context/main_context.go b/pkg/gui/context/main_context.go index 10c3c445d..d32b53a2d 100644 --- a/pkg/gui/context/main_context.go +++ b/pkg/gui/context/main_context.go @@ -10,6 +10,10 @@ type MainContext struct { *SearchTrait diffSelect DiffSelectState + // dragAnchorViewLine is the view line a mouse-down landed on, remembered so a drag + // that follows can anchor its range there. The click can show a whole hunk (a range + // with a different anchor), so the clicked line can't be read back from the view. + dragAnchorViewLine int } var _ types.ISearchableContext = (*MainContext)(nil) @@ -48,6 +52,17 @@ func (self *MainContext) DiffSelectState() *DiffSelectState { return &self.diffSelect } +// SetDragAnchorViewLine records the view line a mouse-down landed on, so a drag that +// follows can anchor its range there (see dragAnchorViewLine). +func (self *MainContext) SetDragAnchorViewLine(viewLine int) { + self.dragAnchorViewLine = viewLine +} + +// DragAnchorViewLine returns the view line the last mouse-down landed on. +func (self *MainContext) DragAnchorViewLine() int { + return self.dragAnchorViewLine +} + func NewMainContext( view *gocui.View, windowName string, diff --git a/pkg/gui/controllers/main_view_controller.go b/pkg/gui/controllers/main_view_controller.go index 99e2f97e4..8d31bd882 100644 --- a/pkg/gui/controllers/main_view_controller.go +++ b/pkg/gui/controllers/main_view_controller.go @@ -182,6 +182,14 @@ func (self *MainViewController) GetMouseKeybindings(opts types.KeybindingsOpts) Handler: self.onClickInOtherViewOfMainViewPair, FocusedView: self.otherContext.GetViewName(), }, + { + // Dragging after a click extends a range selection from the clicked line. + ViewName: self.context.GetViewName(), + Key: gocui.MouseLeft, + Modifier: gocui.ModMotion, + Handler: self.onDragInFocusedView, + FocusedView: self.context.GetViewName(), + }, { // Alt- or shift-click anywhere on a diff line opens it in the editor, // without focusing the view or creating a selection. Two modifiers @@ -296,6 +304,9 @@ func placeOrHideInitialDiffSelection(c *ControllerCommon, mainContext *context.M return } if clickedViewLine >= 0 { + // Remember where the click landed so a drag that follows anchors its range there, + // even when the click selects a whole hunk (whose anchor is the block's far end). + mainContext.SetDragAnchorViewLine(clickedViewLine) if c.UserConfig().Gui.UseHunkModeInStagingView && c.Helpers().Staging.IsChangeLine(view, clickedViewLine) { mainContext.DiffSelectState().Mode = context.DiffSelectModeHunk selectDiffHunk(c, mainContext, clickedViewLine) @@ -998,6 +1009,9 @@ func (self *MainViewController) selectClickedDiffLine(opts gocui.ViewMouseBindin return nil } view := self.context.GetView() + // Remember where the click landed so a drag that follows anchors its range there, + // even when this click selects a whole hunk (whose anchor is the block's far end). + self.context.SetDragAnchorViewLine(opts.Y) if self.sel().Mode == context.DiffSelectModeHunk && self.c.Helpers().Staging.IsChangeLine(view, opts.Y) { self.selectHunkAround(opts.Y) } else { @@ -1010,6 +1024,25 @@ func (self *MainViewController) selectClickedDiffLine(opts gocui.ViewMouseBindin return nil } +// onDragInFocusedView extends a range selection as the mouse is dragged after a click, +// anchored at the line the click landed on (DragAnchorViewLine) — not wherever the click +// left the selection, since a click can select a whole hunk whose far end would otherwise +// become the anchor. Dragging turns hunk mode off: you get a plain range from the clicked +// line to the line under the cursor, which gocui has already moved here. A no-op when the +// view holds no selectable diff. +func (self *MainViewController) onDragInFocusedView(gocui.ViewMouseBindingOpts) error { + view := self.context.GetView() + if !self.isDiffView() || !view.Highlight { + return nil + } + sel := self.sel() + sel.Mode = context.DiffSelectModeRange + sel.RangeIsSticky = false + sel.UserEnabledHunkMode = false + view.SetRangeSelectStart(self.context.DragAnchorViewLine()) + return nil +} + func (self *MainViewController) openSearch() error { if manager := self.c.GetViewBufferManagerForView(self.context.GetView()); manager != nil { manager.ReadToEnd(func() {