fix(editor): restore an evicted tab's exact scroll offset (#2324)

Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-11-09 17:26:35 +08:00
1 джерело 5a48beab07
коміт 2955cdcf9c
2 files changed
+31 -3

No files matched your search

+22
Переглянути файл
@@ -347,6 +347,28 @@ describe("DocumentManager + slots", () => {
for (const slot of slots) slot.dispose();
manager.stop();
});
it("restores a destroyed kept view at its exact scroll offset, not its top line", async () => {
const { manager } = setup({ "/p/a.txt": "line\n".repeat(500) });
const model = await manager.acquire("/p/a.txt");
const slot = new EditorSlot(model, "t0", host);
const view = slot.mount(document.body);
Object.defineProperty(view.scrollDOM, "scrollTop", { value: 137, configurable: true });
view.scrollDOM.dispatchEvent(new Event("scroll"));
slot.unmount();
const expected = view.scrollSnapshot().value as { range: unknown; yMargin: number; isSnapshot: boolean };
const others = Array.from({ length: 8 }, (_, i) => new EditorSlot(model, `t${i + 1}`, host));
for (const other of others) {
other.mount(document.body);
other.unmount();
}
const restored = (slot as unknown as { scroll: { value: typeof expected } }).scroll.value;
expect(restored.isSnapshot).toBe(true);
expect(restored.range).toEqual(expected.range);
expect(restored.yMargin).toBe(expected.yMargin);
for (const s of [slot, ...others]) s.dispose();
manager.stop();
});
});
describe("save cleanup", () => {
+9 -3
Переглянути файл
@@ -242,9 +242,8 @@ export class EditorSlot {
this.kept = null;
const i = keptSlots.indexOf(this);
if (i >= 0) keptSlots.splice(i, 1);
const top = topLineAt(view, this.scrollPos.top);
this.savedTopLine = top;
this.scroll = EditorView.scrollIntoView(view.state.doc.line(top + 1).from, { y: "start" });
this.savedTopLine = topLineAt(view, this.scrollPos.top);
this.scroll = scrollSnapshotAt(view, this.scrollPos);
this.stored = view.state;
view.destroy();
}
@@ -399,6 +398,13 @@ function topLineOf(view: EditorView): number {
return topLineAt(view, view.scrollDOM.scrollTop);
}
/** `view.scrollSnapshot()` at a tracked offset: a detached view's scrollDOM reports 0. */
function scrollSnapshotAt(view: EditorView, pos: { top: number; left: number }): StateEffect<unknown> {
const atPos = Object.create(view) as EditorView;
Object.defineProperty(atPos, "scrollDOM", { value: { scrollTop: pos.top, scrollLeft: pos.left } });
return atPos.scrollSnapshot();
}
function topLineAt(view: EditorView, scrollTop: number): number {
const block = view.lineBlockAtHeight(Math.max(0, scrollTop));
return view.state.doc.lineAt(block.from).number - 1;