Browse Source

Fix #3793: stop accidental text selection after clicking a reference

Clicking a reference left AvaloniaEdit's selection-drag mode stuck, so a
subsequent mouse move (with no button held) extended a selection.

AvaloniaEdit's SelectionMouseHandler captures the pointer and enters
selection mode on press, and extends the selection on every move while that
mode is active -- it keys off the mode, not whether a button is down. Only
its bubble-phase PointerReleased handler resets the mode and releases the
capture, and that handler early-returns when the event is already handled.
The reference-click handler ran in the tunnel phase and marked the release
handled before AvaloniaEdit saw it, so AvaloniaEdit's cleanup never ran.

Move the release handler to the bubble phase so it runs after AvaloniaEdit
has reset its state. AvaloniaEdit subscribes from the TextArea constructor,
before this view attaches its handlers, so the ordering is deterministic and
navigation can stay synchronous.

Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3796/head
Christoph Wille 3 weeks ago
parent
commit
b153291cdf
  1. 17
      ILSpy.Tests/Editor/ReferenceClickTests.cs
  2. 20
      ILSpy/TextView/DecompilerTextView.axaml.cs

17
ILSpy.Tests/Editor/ReferenceClickTests.cs

@ -115,4 +115,21 @@ public class ReferenceClickTests @@ -115,4 +115,21 @@ public class ReferenceClickTests
navigated.Should().BeTrue("a click without dragging follows the link");
}
[AvaloniaTest]
public async Task Click_On_A_Link_Then_Moving_The_Mouse_Does_Not_Select_Text()
{
var (window, view, tab) = await SetupAsync();
var (_, point) = FindVisibleReference(window, view, tab);
tab.NavigateRequested += _ => { };
// A stationary click navigates; the gesture must end cleanly so that afterwards moving
// the mouse with no button held does not extend a selection (issue #3793).
window.MouseDown(point, MouseButton.Left);
window.MouseUp(point, MouseButton.Left);
window.MouseMove(point + new Point(60, 0));
view.Editor.TextArea.Selection.IsEmpty.Should().BeTrue(
"moving the mouse after a click, with no button held, must not select text");
}
}

20
ILSpy/TextView/DecompilerTextView.axaml.cs

@ -157,15 +157,22 @@ namespace ICSharpCode.ILSpy.TextView @@ -157,15 +157,22 @@ namespace ICSharpCode.ILSpy.TextView
// Reference navigation fires on pointer-RELEASE without drag (WPF parity: the WPF
// view used TextArea.PreviewMouseDown/Up the same way), so a press-and-drag over a
// link starts a text selection instead of navigating away. Tunnel routing mirrors
// WPF's Preview events and sees the gesture before AvaloniaEdit's own handlers.
// link starts a text selection instead of navigating away. The press handler only
// records the start position, so tunnel routing (before AvaloniaEdit consumes the
// press) is fine.
Editor.TextArea.AddHandler(InputElement.PointerPressedEvent,
OnTextAreaPointerPressedForReferenceClick,
RoutingStrategies.Tunnel,
handledEventsToo: true);
// The release handler must run AFTER AvaloniaEdit's own PointerReleased: AvaloniaEdit's
// SelectionMouseHandler captures the pointer and enters selection mode on press, and
// only its release handler resets that mode and releases the capture. AvaloniaEdit
// subscribes in the bubble phase from the TextArea constructor (before this view adds
// its handlers), so a bubble handler here is guaranteed to run after it. handledEventsToo
// keeps us running even though AvaloniaEdit marks the release handled.
Editor.TextArea.AddHandler(InputElement.PointerReleasedEvent,
OnTextAreaPointerReleasedForReferenceClick,
RoutingStrategies.Tunnel,
RoutingStrategies.Bubble,
handledEventsToo: true);
}
@ -200,9 +207,10 @@ namespace ICSharpCode.ILSpy.TextView @@ -200,9 +207,10 @@ namespace ICSharpCode.ILSpy.TextView
ClearLocalReferenceMarks();
return;
}
// Cancel the caret-click selection AvaloniaEdit started on press, and stop its
// release processing, so the navigation's caret move doesn't grow a selection
// between the old anchor and the new position.
// AvaloniaEdit's own release handler has already reset its selection mode and released
// the pointer capture (it runs first in the bubble phase). Clear the empty caret-click
// selection it leaves behind, then navigate; marking the release handled stops it from
// bubbling further now that it has been consumed as a link click.
Editor.TextArea.ClearSelection();
e.Handled = true;
OnReferenceClicked(segment);

Loading…
Cancel
Save