fix(terminal): refit after fonts load
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
export const scheduleTerminalFits = (deps: {
|
||||
fit: () => void;
|
||||
sendResize: () => void;
|
||||
fontsReady: Promise<unknown>;
|
||||
loadNerdFont: () => Promise<unknown>;
|
||||
raf: (cb: () => void) => void;
|
||||
isAborted: () => boolean;
|
||||
}): void => {
|
||||
const refit = () => {
|
||||
if (deps.isAborted()) return;
|
||||
deps.fit();
|
||||
deps.sendResize();
|
||||
};
|
||||
|
||||
deps.raf(refit);
|
||||
void deps.fontsReady.then(refit).catch(() => undefined);
|
||||
void deps
|
||||
.loadNerdFont()
|
||||
.then(refit)
|
||||
.catch(() => undefined);
|
||||
};
|
||||
@@ -6,6 +6,8 @@ import { useTheme } from 'next-themes';
|
||||
|
||||
import { Button } from '@spoon/ui';
|
||||
|
||||
import { scheduleTerminalFits } from './terminal-fit';
|
||||
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
|
||||
const TERMINAL_FONT =
|
||||
@@ -143,18 +145,8 @@ export const WorkspaceTerminal = ({
|
||||
term.loadAddon(fit);
|
||||
term.loadAddon(new WebLinksAddon());
|
||||
term.open(container);
|
||||
fit.fit();
|
||||
termRef.current = term;
|
||||
|
||||
// Pull in the Nerd Font icon glyphs (loaded lazily by unicode-range) and
|
||||
// repaint once ready so powerline/oh-my-posh/eza icons render.
|
||||
void document.fonts
|
||||
.load("16px 'Symbols Nerd Font Mono'", '\ue0b0')
|
||||
.then(() => {
|
||||
if (!isAborted()) term.refresh(0, term.rows - 1);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
|
||||
const sendResize = () => {
|
||||
if (ws?.readyState !== WebSocket.OPEN) return;
|
||||
ws.send(
|
||||
@@ -162,6 +154,26 @@ export const WorkspaceTerminal = ({
|
||||
);
|
||||
};
|
||||
|
||||
scheduleTerminalFits({
|
||||
fit: () => {
|
||||
try {
|
||||
fit.fit();
|
||||
} catch {
|
||||
// ignore transient layout errors
|
||||
}
|
||||
},
|
||||
sendResize,
|
||||
fontsReady: document.fonts.ready,
|
||||
loadNerdFont: () =>
|
||||
document.fonts
|
||||
.load("16px 'Symbols Nerd Font Mono'", '\ue0b0')
|
||||
.then(() => {
|
||||
if (!isAborted()) term.refresh(0, term.rows - 1);
|
||||
}),
|
||||
raf: (cb) => requestAnimationFrame(cb),
|
||||
isAborted,
|
||||
});
|
||||
|
||||
ws = new WebSocket(url);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
ws.onopen = () => {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { scheduleTerminalFits } from '@/components/agent-workspace/terminal-fit';
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
|
||||
const flush = async () => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
};
|
||||
|
||||
describe('scheduleTerminalFits', () => {
|
||||
test('fits in rAF and again after fonts.ready and nerd-font load', async () => {
|
||||
const fit = vi.fn();
|
||||
const sendResize = vi.fn();
|
||||
let rafCb: (() => void) | undefined;
|
||||
|
||||
scheduleTerminalFits({
|
||||
fit,
|
||||
sendResize,
|
||||
fontsReady: Promise.resolve(),
|
||||
loadNerdFont: () => Promise.resolve(),
|
||||
raf: (cb) => {
|
||||
rafCb = cb;
|
||||
},
|
||||
isAborted: () => false,
|
||||
});
|
||||
rafCb?.();
|
||||
await flush();
|
||||
|
||||
expect(fit).toHaveBeenCalledTimes(3);
|
||||
expect(sendResize).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
test('does nothing once aborted', async () => {
|
||||
const fit = vi.fn();
|
||||
|
||||
scheduleTerminalFits({
|
||||
fit,
|
||||
sendResize: vi.fn(),
|
||||
fontsReady: Promise.resolve(),
|
||||
loadNerdFont: () => Promise.resolve(),
|
||||
raf: (cb) => cb(),
|
||||
isAborted: () => true,
|
||||
});
|
||||
await flush();
|
||||
|
||||
expect(fit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user