Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions emain/emain-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ export class WaveBrowserWindow extends BaseWindow {
await Promise.all([p1, p2]);
} else {
console.log("reusing an existing tab, calling wave-init", tabView.waveTabId);
const p1 = this.repositionTabsSlowly(35);
const p1 = this.repositionTabsSlowly(35, true);
const p2 = tabView.webContents.send("wave-init", tabView.savedInitOpts); // reinit
await Promise.all([p1, p2]);
}
Expand All @@ -452,13 +452,16 @@ export class WaveBrowserWindow extends BaseWindow {
}, 30);
}

private async repositionTabsSlowly(delayMs: number) {
private async repositionTabsSlowly(delayMs: number, skipIntermediate: boolean = false) {
const activeTabView = this.activeTabView;
const winBounds = this.getContentBounds();
if (activeTabView == null) {
return;
}
if (activeTabView.isOnScreen()) {
if (skipIntermediate || activeTabView.isOnScreen()) {
// For already-initialized tabs (skipIntermediate=true) or tabs already on screen,
// go directly to final bounds to avoid an intermediate resize that causes
// xterm.js buffer reflow and scroll position loss.
activeTabView.setBounds({
x: 0,
y: 0,
Expand Down
6 changes: 6 additions & 0 deletions frontend/app/view/term/osc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,12 @@ export function handleOsc16162Command(data: string, blockId: string, loaded: boo
case "R":
globalStore.set(termWrap.shellIntegrationStatusAtom, null);
if (terminal.buffer.active.type === "alternate") {
// Save scroll position before alternate buffer exit (only if user scrolled up)
const normalBuffer = terminal.buffer.normal;
const isAtBottom = normalBuffer.baseY >= normalBuffer.length - terminal.rows;
if (!isAtBottom) {
termWrap.savedBaseY = normalBuffer.baseY;
}
terminal.write("\x1b[?1049l");
}
break;
Expand Down
29 changes: 28 additions & 1 deletion frontend/app/view/term/termwrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ export class TermWrap {
lastPasteData: string = "";
lastPasteTime: number = 0;

// Scroll position preservation across buffer swaps (alt screen exit)
savedBaseY: number | null = null;

constructor(
tabId: string,
blockId: string,
Expand Down Expand Up @@ -197,6 +200,19 @@ export class TermWrap {
this.heldData = [];
this.handleResize_debounced = debounce(50, this.handleResize.bind(this));
this.terminal.open(this.connectElem);

// Restore scroll position after alternate buffer exit (buffer swap)
this.toDispose.push(
this.terminal.buffer.onBufferChange(() => {
if (this.savedBaseY !== null) {
const restoreY = this.savedBaseY;
this.savedBaseY = null;
requestAnimationFrame(() => {
this.terminal.scrollToLine(restoreY);
});
}
})
);
this.handleResize();
const pasteHandler = this.pasteHandler.bind(this);
this.connectElem.addEventListener("paste", pasteHandler, true);
Expand Down Expand Up @@ -465,8 +481,19 @@ export class TermWrap {
handleResize() {
const oldRows = this.terminal.rows;
const oldCols = this.terminal.cols;
// Save scroll position before fit (only if user scrolled up from bottom)
const buffer = this.terminal.buffer.active;
const wasAtBottom = buffer.baseY >= buffer.length - this.terminal.rows;
const fitSavedY = wasAtBottom ? null : buffer.baseY;
this.fitAddon.fit();
if (oldRows !== this.terminal.rows || oldCols !== this.terminal.cols) {
// Only restore scroll if rows/cols actually changed (reflow happened)
const dimsChanged = oldRows !== this.terminal.rows || oldCols !== this.terminal.cols;
if (fitSavedY !== null && dimsChanged) {
requestAnimationFrame(() => {
this.terminal.scrollToLine(fitSavedY);
});
}
if (dimsChanged) {
const termSize: TermSize = { rows: this.terminal.rows, cols: this.terminal.cols };
RpcApi.ControllerInputCommand(TabRpcClient, { blockid: this.blockId, termsize: termSize });
}
Expand Down