Skip to content

Commit bd6d16f

Browse files
committed
Merge upstream/main to resolve PR #2940 conflicts
2 parents c713c0d + cfbdc25 commit bd6d16f

2 files changed

Lines changed: 47 additions & 0 deletions

File tree

frontend/app/view/term/term.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,9 @@ const TerminalView = ({ blockId, model }: ViewComponentProps<TermViewModel>) =>
311311
model.termRef.current = termWrap;
312312
setTermWrapInst(termWrap);
313313
const rszObs = new ResizeObserver(() => {
314+
if (termWrap.cachedAtBottomForResize == null) {
315+
termWrap.cachedAtBottomForResize = termWrap.wasRecentlyAtBottom();
316+
}
314317
termWrap.handleResize_debounced();
315318
});
316319
rszObs.observe(connectElemRef.current);

frontend/app/view/term/termwrap.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ export class TermWrap {
106106
// xterm.js paste() method triggers onData event, which can cause duplicate sends
107107
lastPasteData: string = "";
108108
lastPasteTime: number = 0;
109+
lastAtBottomTime: number = Date.now();
110+
lastScrollAtBottom: boolean = true;
111+
cachedAtBottomForResize: boolean | null = null;
109112

110113
constructor(
111114
tabId: string,
@@ -241,6 +244,19 @@ export class TermWrap {
241244
this.connectElem.removeEventListener("paste", pasteHandler, true);
242245
},
243246
});
247+
const viewportElem = this.connectElem.querySelector(".xterm-viewport") as HTMLElement;
248+
if (viewportElem) {
249+
const scrollHandler = () => {
250+
const atBottom = viewportElem.scrollTop + viewportElem.clientHeight >= viewportElem.scrollHeight - 20;
251+
this.setAtBottom(atBottom);
252+
};
253+
viewportElem.addEventListener("scroll", scrollHandler);
254+
this.toDispose.push({
255+
dispose: () => {
256+
viewportElem.removeEventListener("scroll", scrollHandler);
257+
},
258+
});
259+
}
244260
}
245261

246262
getZoneId(): string {
@@ -509,8 +525,29 @@ export class TermWrap {
509525
}
510526
}
511527

528+
setAtBottom(atBottom: boolean) {
529+
if (this.lastScrollAtBottom && !atBottom) {
530+
this.lastAtBottomTime = Date.now();
531+
}
532+
this.lastScrollAtBottom = atBottom;
533+
if (atBottom) {
534+
this.lastAtBottomTime = Date.now();
535+
}
536+
}
537+
538+
wasRecentlyAtBottom(): boolean {
539+
if (this.lastScrollAtBottom) {
540+
return true;
541+
}
542+
return Date.now() - this.lastAtBottomTime <= 1000;
543+
}
544+
512545
handleResize() {
513546
const oldTermSize = this.getTermSize();
547+
const atBottom = this.cachedAtBottomForResize ?? this.wasRecentlyAtBottom();
548+
if (!atBottom) {
549+
this.cachedAtBottomForResize = null;
550+
}
514551
this.fitAddon.fit();
515552
const newTermSize = this.getTermSize();
516553
if (!this.areTermSizesEqual(oldTermSize, newTermSize)) {
@@ -526,6 +563,13 @@ export class TermWrap {
526563
this.hasResized = true;
527564
this.resyncController("initial resize");
528565
}
566+
if (atBottom) {
567+
setTimeout(() => {
568+
this.cachedAtBottomForResize = null;
569+
this.terminal.scrollToBottom();
570+
this.setAtBottom(true);
571+
}, 20);
572+
}
529573
}
530574

531575
processAndCacheData() {

0 commit comments

Comments
 (0)