Skip to content

Commit ef8757e

Browse files
geevensinghCopilot
andcommitted
Skip ghost band on empty-space click to remove MouseUp jiggle
The optimistic ghost paints the band at the cursor's exact Y, but the editor's actual scroll lands at a line-snapped offset (floor(scrollOffset / lineHeight)), so the eventual settled band position is up to barHeight/totalLines away from the ghost. During drag that mismatch is invisible because the cursor is moving; on a stationary empty-space click it shows up as a one-pixel-to-many- pixel jiggle the moment the ghost clears on MouseUp. Fix: in OnMouseLeftButtonDown's empty-space branch, send the scroll request directly without setting _dragGhostBandTop. The natural ViewportState propagation (editor scrolls -> layout -> ScrollChanged -> Viewport update -> bar invalidate -> repaint) lands the band at its actual settled position on the next frame, which is exactly where MouseUp will leave it. No ghost = no jiggle. If a drag follows the click, the first MouseMove activates the ghost via EmitDragScroll on the existing smooth-drag path. The brief transition from natural-position-band to ghost-at-cursor is masked by ongoing motion. The band-grab drag case still ends with a line-snap mismatch on MouseUp, but it's masked by the motion of the drag itself; the user hasn't reported it as jiggle. AI-Local-Session: b53d5fe5-38cc-41dd-b987-3def5ef3f0d3 AI-Cloud-Session: 744dd836-01e5-48af-91db-daa8241a01d7 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 5d3157f commit ef8757e

2 files changed

Lines changed: 29 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,12 @@ body. Keep section headings exact and write notes in Markdown.
4141
indicator) recenters the indicator on the cursor and starts a
4242
drag in the same gesture — minimap-style "click anywhere to jump
4343
there", and a press-and-drag from empty space scrubs without
44-
needing a second click.
44+
needing a second click. Empty-space clicks land the indicator at
45+
its actual line-snapped position immediately rather than locking
46+
to the cursor on press and snapping on release — no more jiggle
47+
when the press and release land at the same cursor position but
48+
the editor's per-line scroll granularity doesn't divide the bar's
49+
pixel height evenly.
4550
- Language-aware syntax highlighting for TypeScript (`.ts`, `.tsx`),
4651
YAML (`.yaml`, `.yml`), Go (`.go`), Rust (`.rs`), Ruby (`.rb`),
4752
Bash / shell (`.sh`, `.bash`, `.zsh`), and TOML (`.toml`). Combined

DiffViewer/Rendering/HunkOverviewBar.cs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -363,15 +363,26 @@ protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
363363
bool inBand = band is not null && HunkOverviewBarGeometry.IsInsideBand(band, p);
364364

365365
// Empty press: minimap-style "click anywhere to jump there".
366-
// The band's center snaps to the cursor immediately, then any
367-
// continued motion drags from there — empty-space press and
368-
// empty-space-press-then-drag are the same gesture, sharing
369-
// the existing sticky-thumb path. Skipping PendingClick and
370-
// going straight to Dragging means the first scroll fires on
371-
// MouseDown (so a quick click lands the band on the cursor
372-
// even without any mouse movement). If there's no band yet
373-
// (no file loaded / pre-layout) we bubble instead — nothing
374-
// to center on.
366+
// Send the scroll request immediately so the band lands at the
367+
// cursor, but do NOT set the optimistic ghost band — let the
368+
// natural ViewportState propagation paint the band at its
369+
// actual settled (line-snapped) position on the next frame.
370+
// Using the ghost path here would lock the band visually to
371+
// the cursor's exact Y, then snap to the line-snapped position
372+
// on MouseUp when the ghost clears — visible as a jiggle on
373+
// smaller files where one line of editor scroll spans several
374+
// bar pixels. The ghost's value-add is smoothing fast drag
375+
// (decoupling the bar paint from editor scroll lag); a
376+
// stationary click has nothing to smooth.
377+
//
378+
// If the user follows up with motion, the first MouseMove
379+
// activates the ghost via EmitDragScroll and we're back on
380+
// the smooth-drag path. The transition is a small one-time
381+
// snap to cursor, masked by the motion that caused it.
382+
//
383+
// Still go directly into Dragging (skipping PendingClick) so
384+
// MouseUp doesn't try to fire a JumpToHunk — there's no hunk
385+
// to commit to, and the scroll has already been sent.
375386
if (idx < 0 && !inBand)
376387
{
377388
if (band is null) return;
@@ -382,7 +393,9 @@ protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
382393
_interaction = BarInteractionState.Dragging;
383394
ToolTip = null;
384395
CaptureMouse();
385-
EmitDragScroll(p);
396+
double targetBandTopY = p.Y - _bandDragOffsetFromTop;
397+
double fraction = ActualHeight <= 0 ? 0 : targetBandTopY / ActualHeight;
398+
_vm.RequestScrollByVerticalFraction(fraction);
386399
e.Handled = true;
387400
return;
388401
}

0 commit comments

Comments
 (0)