fix(editor): keep viewport when cursor was visible before execution

This commit is contained in:
弈秋
2026-10-06 18:57:10 +08:00
committed by GitHub
parent 6c380269e7
commit ae86a8c73c
3 changed files with 42 additions and 3 deletions
@@ -2754,7 +2754,12 @@ function scrollCursorIntoView() {
}
function beginExecutionViewportTracking() {
executionViewportOwnership.beginExecution();
// Snapshot visibility while the viewport still has its pre-execution size:
// once the results pane opens it shrinks the editor, and a cursor that was
// visible before execution must not be scrolled away by the shrink (#10480).
const currentView = view.value;
const cursorVisible = currentView ? isQueryEditorPositionVisible(currentView.state.selection.main.head, currentView.visibleRanges, currentView.viewport) : false;
executionViewportOwnership.beginExecution(cursorVisible);
}
function recordExecutionViewportInteraction() {
@@ -9,6 +9,10 @@ describe("QueryEditor execution routing", () => {
expect(queryEditorSource).toContain("createQueryEditorExecutionShortcutBindings(shortcuts.executeSql");
expect(queryEditorSource).not.toContain("forceCurrent");
});
it("snapshots pre-execution cursor visibility when execution viewport tracking starts", () => {
expect(queryEditorSource).toContain("beginExecution(cursorVisible)");
});
});
describe("QueryEditor execution viewport ownership", () => {
@@ -20,6 +24,28 @@ describe("QueryEditor execution viewport ownership", () => {
expect(ownership.consumeCompletionPreservation()).toBe(false);
});
it("preserves the viewport when the cursor was visible before execution (#10480)", () => {
const ownership = createQueryEditorExecutionViewportOwnership();
// Cmd+Enter path: no gutter request, no user interaction, but the cursor
// was comfortably visible in the pre-execution viewport. The results pane
// then shrinks the editor — that shrink must not scroll the cursor away.
ownership.beginExecution(true);
expect(ownership.consumeCompletionPreservation()).toBe(true);
expect(ownership.consumeCompletionPreservation()).toBe(false);
});
it("still centers the cursor when it was off-screen before execution", () => {
const ownership = createQueryEditorExecutionViewportOwnership();
// #5281 contract: a cursor that was already out of sight before execution
// gets centered once the results pane has taken its space.
ownership.beginExecution(false);
expect(ownership.consumeCompletionPreservation()).toBe(false);
});
it("preserves the viewport once after user interaction during execution", () => {
const ownership = createQueryEditorExecutionViewportOwnership();
@@ -14,6 +14,7 @@ export function createQueryEditorExecutionViewportOwnership() {
let acceptedRequestId: number | undefined;
let executionActive = false;
let userInteractedDuringExecution = false;
let cursorVisibleBeforeExecution = false;
return {
beginRequest(): number {
@@ -33,18 +34,24 @@ export function createQueryEditorExecutionViewportOwnership() {
acceptedRequestId = requestId;
return true;
},
beginExecution() {
// Captured while the editor viewport still has its pre-execution size: once
// the results pane opens it shrinks the editor, and a cursor that was
// comfortably visible before then can fall outside the smaller viewport,
// which must not be read as "the user cannot see their cursor" (#10480).
beginExecution(cursorVisible = false) {
executionActive = true;
userInteractedDuringExecution = false;
cursorVisibleBeforeExecution = cursorVisible;
},
recordUserInteraction() {
if (executionActive) userInteractedDuringExecution = true;
},
consumeCompletionPreservation(): boolean {
const preserveViewport = acceptedRequestId !== undefined || userInteractedDuringExecution;
const preserveViewport = acceptedRequestId !== undefined || userInteractedDuringExecution || cursorVisibleBeforeExecution;
acceptedRequestId = undefined;
executionActive = false;
userInteractedDuringExecution = false;
cursorVisibleBeforeExecution = false;
return preserveViewport;
},
reset() {
@@ -52,6 +59,7 @@ export function createQueryEditorExecutionViewportOwnership() {
acceptedRequestId = undefined;
executionActive = false;
userInteractedDuringExecution = false;
cursorVisibleBeforeExecution = false;
},
};
}