Stabilize initial terminal sizing

This commit is contained in:
GitHub Copilot
2026-01-27 23:27:02 +00:00
parent 5e84d00a30
commit cb6ea35c94
3 changed files with 51 additions and 36 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "textual-webterm" name = "textual-webterm"
version = "0.3.21" version = "0.3.22"
description = "Serve terminal sessions over the web" description = "Serve terminal sessions over the web"
authors = ["Will McGugan <will@textualize.io>"] authors = ["Will McGugan <will@textualize.io>"]
license = "MIT" license = "MIT"
File diff suppressed because one or more lines are too long
+34 -19
View File
@@ -59,6 +59,7 @@ class WebTerminal {
private reconnectAttempts = 0; private reconnectAttempts = 0;
private maxReconnectAttempts = 5; private maxReconnectAttempts = 5;
private reconnectDelay = 1000; private reconnectDelay = 1000;
private resizeEventsEnabled = false;
constructor(container: HTMLElement, wsUrl: string, config: TerminalConfig = {}) { constructor(container: HTMLElement, wsUrl: string, config: TerminalConfig = {}) {
this.element = container; this.element = container;
@@ -114,6 +115,9 @@ class WebTerminal {
// Handle resize // Handle resize
this.terminal.onResize(({ cols, rows }) => { this.terminal.onResize(({ cols, rows }) => {
if (!this.resizeEventsEnabled) {
return;
}
this.send(["resize", { width: cols, height: rows }]); this.send(["resize", { width: cols, height: rows }]);
}); });
@@ -135,11 +139,7 @@ class WebTerminal {
if (!("fonts" in document)) { if (!("fonts" in document)) {
return; return;
} }
const fontSet = document.fonts; document.fonts.ready
if (fontSet.status === "loaded") {
return;
}
fontSet.ready
.then(() => this.scheduleFit()) .then(() => this.scheduleFit())
.catch(() => { .catch(() => {
// Ignore font readiness errors; resize observer will handle future resizes. // Ignore font readiness errors; resize observer will handle future resizes.
@@ -180,25 +180,40 @@ class WebTerminal {
this.element.classList.remove("-disconnected"); this.element.classList.remove("-disconnected");
// Send initial size. // Send initial size.
// Important: avoid sending a bogus early resize before fonts/layout settle, // Important: the PTY hard-wraps output based on its initial cols/rows.
// otherwise the PTY will hard-wrap output at the wrong column count. // If we send a resize before fonts/layout settle, the initial cols can be
this.fit(); // too small and the shell will wrap permanently.
const sendResize = () => { this.resizeEventsEnabled = false;
const dims = this.fitAddon.proposeDimensions();
if (dims) { const init = () => {
const fallback = { cols: 132, rows: 45 };
const maxAttempts = 120;
const attemptFitAndResize = (attempt: number) => {
const dims = this.fitAddon.proposeDimensions();
if (!dims) {
if (attempt < maxAttempts) {
window.requestAnimationFrame(() => attemptFitAndResize(attempt + 1));
return;
}
this.terminal.resize(fallback.cols, fallback.rows);
this.resizeEventsEnabled = true;
this.send(["resize", { width: fallback.cols, height: fallback.rows }]);
return;
}
this.terminal.resize(dims.cols, dims.rows);
this.resizeEventsEnabled = true;
this.send(["resize", { width: dims.cols, height: dims.rows }]); this.send(["resize", { width: dims.cols, height: dims.rows }]);
} };
window.requestAnimationFrame(() => attemptFitAndResize(0));
}; };
if ("fonts" in document) { if ("fonts" in document) {
document.fonts.ready document.fonts.ready.then(init).catch(init);
.then(() => {
this.scheduleFit();
sendResize();
})
.catch(() => sendResize());
} else { } else {
sendResize(); init();
} }
// Focus terminal // Focus terminal