Fix terminal right margin by using custom fit without scrollbar reservation

The FitAddon from ghostty-web reserves 15px for a scrollbar that doesn't
exist in canvas-based rendering. This caused a visible right margin gap.

Added custom fit() method that calculates terminal dimensions without
the scrollbar margin, using the full available container width.
This commit is contained in:
GitHub Copilot
2026-01-28 07:31:27 +00:00
parent 13912a18f8
commit 5a708efe56
2 changed files with 63 additions and 8 deletions
File diff suppressed because one or more lines are too long
+59 -4
View File
@@ -342,15 +342,15 @@ class WebTerminal {
private initialize(): void { private initialize(): void {
// Wait for fonts to load before fitting to ensure correct measurements // Wait for fonts to load before fitting to ensure correct measurements
this.waitForFonts().then(() => { this.waitForFonts().then(() => {
this.fitAddon.fit(); this.fit();
}); });
// Start observing resize immediately // Setup resize observer (we use our own fit method, not FitAddon's)
this.fitAddon.observeResize(); this.setupResizeObserver();
// Handle window resize (some browsers don't trigger ResizeObserver on window resize) // Handle window resize (some browsers don't trigger ResizeObserver on window resize)
window.addEventListener("resize", () => { window.addEventListener("resize", () => {
this.fitAddon.fit(); this.fit();
}); });
// Handle terminal input // Handle terminal input
@@ -480,6 +480,61 @@ class WebTerminal {
} }
} }
/**
* Custom fit method that doesn't reserve space for scrollbar.
* The FitAddon subtracts 15px for a scrollbar, but ghostty-web
* uses canvas rendering without a visible scrollbar.
*/
private fit(): void {
const renderer = (this.terminal as unknown as { renderer?: { getMetrics?: () => { width: number; height: number } } }).renderer;
if (!renderer?.getMetrics) {
// Fall back to FitAddon if we can't access renderer
this.fitAddon.fit();
return;
}
const metrics = renderer.getMetrics();
if (!metrics || metrics.width === 0 || metrics.height === 0) {
return;
}
const style = window.getComputedStyle(this.element);
const paddingTop = parseInt(style.paddingTop) || 0;
const paddingBottom = parseInt(style.paddingBottom) || 0;
const paddingLeft = parseInt(style.paddingLeft) || 0;
const paddingRight = parseInt(style.paddingRight) || 0;
const availableWidth = this.element.clientWidth - paddingLeft - paddingRight;
const availableHeight = this.element.clientHeight - paddingTop - paddingBottom;
if (availableWidth <= 0 || availableHeight <= 0) {
return;
}
const cols = Math.max(2, Math.floor(availableWidth / metrics.width));
const rows = Math.max(1, Math.floor(availableHeight / metrics.height));
if (cols !== this.terminal.cols || rows !== this.terminal.rows) {
this.terminal.resize(cols, rows);
}
}
/** Setup resize observer for container */
private setupResizeObserver(): void {
const resizeObserver = new ResizeObserver(() => {
// Debounce resize events
if (this.resizeDebounceTimer) {
clearTimeout(this.resizeDebounceTimer);
}
this.resizeDebounceTimer = window.setTimeout(() => {
this.fit();
}, 100);
});
resizeObserver.observe(this.element);
}
private resizeDebounceTimer: number | undefined;
/** Validate terminal dimensions */ /** Validate terminal dimensions */
private isValidSize(cols: number, rows: number): boolean { private isValidSize(cols: number, rows: number): boolean {
return cols >= 2 && cols <= 500 && rows >= 1 && rows <= 200; return cols >= 2 && cols <= 500 && rows >= 1 && rows <= 200;