diff --git a/src/DiffEngineViewer.Tests/ReselectTests.cs b/src/DiffEngineViewer.Tests/ReselectTests.cs
new file mode 100644
index 00000000..1556a4cc
--- /dev/null
+++ b/src/DiffEngineViewer.Tests/ReselectTests.cs
@@ -0,0 +1,73 @@
+///
+/// Selecting the entry that is already selected. A left click on the highlighted row, a right
+/// click to open its menu, and a focus naming it all arrive as a selection, and the reader is part
+/// way down a comparison while they do.
+///
+public class ReselectTests
+{
+ [Test]
+ public async Task Opening_the_menu_on_the_entry_being_read_keeps_the_scroll()
+ {
+ var state = Scrolled();
+ var row = VisibleRowOf(state, state.Selected);
+
+ var opened = ViewerSession.OpenMenu(state, row);
+
+ await Assert.That(opened.Menu).IsNotNull();
+ await Assert.That(opened.ScrollTop).IsEqualTo(state.ScrollTop);
+ }
+
+ [Test]
+ public async Task Focusing_the_entry_being_read_keeps_the_scroll()
+ {
+ var state = Scrolled();
+
+ var focused = ViewerSession.SelectKey(state, state.Current!.Key);
+
+ await Assert.That(focused.ScrollTop).IsEqualTo(state.ScrollTop);
+ }
+
+ ///
+ /// A different entry is a different comparison, so that one does start at the top.
+ ///
+ [Test]
+ public async Task Selecting_another_entry_starts_at_its_top()
+ {
+ var state = Scrolled();
+
+ var selected = ViewerSession.SelectKey(state, state.Queue[0].Key);
+
+ await Assert.That(selected.Current!.Key).IsEqualTo(state.Queue[0].Key);
+ await Assert.That(selected.ScrollTop).IsEqualTo(0);
+ }
+
+ static SessionState Scrolled()
+ {
+ var state = Fixtures.Inline(
+ Fixtures.Patch("A.cs", 1, null, Fixtures.Long(true)),
+ Fixtures.Patch("B.cs", 2, null, Fixtures.Long(true)));
+ state = ViewerSession.SelectKey(state, state.Queue[1].Key);
+ state = ViewerSession.Apply(state, CommandKind.PageDown);
+ if (state.ScrollTop == 0)
+ {
+ throw new("The entry did not scroll, so nothing below asserts anything.");
+ }
+
+ return state;
+ }
+
+ static int VisibleRowOf(SessionState state, int entry)
+ {
+ var visible = QueueProjection.Visible(state, ScreenBuilder.BodyRows(state), out _);
+ for (var index = 0; index < visible.Count; index++)
+ {
+ if (visible[index].Kind == QueueRowKind.Entry &&
+ visible[index].EntryIndex == entry)
+ {
+ return index;
+ }
+ }
+
+ throw new($"Entry {entry} is not on screen.");
+ }
+}
diff --git a/src/DiffEngineViewer/ViewerSession.cs b/src/DiffEngineViewer/ViewerSession.cs
index d59969e7..671441af 100644
--- a/src/DiffEngineViewer/ViewerSession.cs
+++ b/src/DiffEngineViewer/ViewerSession.cs
@@ -890,6 +890,14 @@ static SessionState Select(SessionState state, int index)
return state;
}
+ // Already the entry on screen. Selecting it is what a click on it does, what a right click
+ // opening its menu does, and what a focus naming it does, and none of those asks to be
+ // taken back to the top of what is being read.
+ if (index == state.Selected)
+ {
+ return Clamp(state);
+ }
+
return Clamp(state with
{
Selected = index,