mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-25 18:52:39 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92ade69cd6 | ||
|
|
eccd3337d7 |
@@ -107,7 +107,7 @@ function createScreenRestorer(
|
||||
|
||||
return {
|
||||
async restore(snapshot: Awaited<ReturnType<typeof benchmarkSnapshot>>) {
|
||||
drain.restoreSnapshot(snapshot);
|
||||
drain.restoreText(renderTerminalSnapshotToAnsi(snapshot));
|
||||
await drain.flush();
|
||||
frameRecorder.frames[0]?.();
|
||||
return screen;
|
||||
@@ -116,6 +116,38 @@ function createScreenRestorer(
|
||||
}
|
||||
|
||||
describe("native terminal output drain", () => {
|
||||
test.each([41, 35])(
|
||||
"keeps a restored shell cursor beside its prompt after replay at %i rows",
|
||||
async (rows) => {
|
||||
const source = createNativeHeadlessTerminal({ rows: 41, cols: 49 });
|
||||
await source.write(Array.from({ length: 16 }, (_, i) => `probe ${i}\r\n`).join("") + "$ ");
|
||||
const snapshot = source.getState();
|
||||
const terminal = createNativeHeadlessTerminal({ rows, cols: 49 });
|
||||
const frameRecorder = createFrameRecorder();
|
||||
const drain = createNativeTerminalOutputDrain({
|
||||
write: (chunk) => terminal.write(chunk),
|
||||
reset: () => terminal.reset(),
|
||||
getViewportState: () => terminal.getViewportState(),
|
||||
onPaint: () => {},
|
||||
scheduleFrame: frameRecorder.scheduleFrame,
|
||||
cancelFrame: frameRecorder.cancelFrame,
|
||||
});
|
||||
try {
|
||||
drain.restoreText(renderTerminalSnapshotToAnsi(snapshot));
|
||||
await drain.flush();
|
||||
terminal.resize({ rows: 41, cols: 49 });
|
||||
const restored = terminal.getState();
|
||||
const promptRow = restored.grid.findIndex((row) => rowText(row) === "$ ".trimEnd());
|
||||
expect(promptRow).toBeGreaterThanOrEqual(0);
|
||||
expect(restored.cursor).toEqual({ row: promptRow, col: 2 });
|
||||
} finally {
|
||||
drain.dispose();
|
||||
terminal.dispose();
|
||||
source.dispose();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test("drains large bursts without waiting one frame per 2KB parse slice", async () => {
|
||||
const writes: string[] = [];
|
||||
const parseSamples: Array<{ parsedChars: number; queuedChars: number }> = [];
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import React, { act } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { expect, test } from "vitest";
|
||||
import { TerminalGridView } from "./terminal-grid-view.native";
|
||||
import type { TerminalViewportState } from "./headless-terminal-state";
|
||||
import type { TerminalGridCellMetrics } from "./terminal-grid-metrics";
|
||||
|
||||
test("measures a single font cell before the terminal has its viewport columns", async () => {
|
||||
const container = document.createElement("div");
|
||||
container.style.cssText = "width: 400px; height: 700px; display: flex";
|
||||
document.body.appendChild(container);
|
||||
const root = createRoot(container);
|
||||
const measurements: TerminalGridCellMetrics[] = [];
|
||||
const state: TerminalViewportState = {
|
||||
rows: 1,
|
||||
cols: 1,
|
||||
firstRow: 0,
|
||||
oldestRow: 0,
|
||||
newestRow: 0,
|
||||
grid: [[{ char: " " }]],
|
||||
cursor: { row: 0, col: 0 },
|
||||
};
|
||||
const style = { flex: 1 };
|
||||
const recordMeasurement = (metrics: TerminalGridCellMetrics) => measurements.push(metrics);
|
||||
try {
|
||||
act(() =>
|
||||
root.render(
|
||||
React.createElement(TerminalGridView, {
|
||||
state,
|
||||
fontSize: 12,
|
||||
style,
|
||||
onCellMetricsChange: recordMeasurement,
|
||||
}),
|
||||
),
|
||||
);
|
||||
await expect.poll(() => measurements.length).toBeGreaterThan(0);
|
||||
const sample = [...container.querySelectorAll("div")].find(
|
||||
(element) => element.textContent === "mmmmmmmmmm" && element.childElementCount === 0,
|
||||
);
|
||||
expect(sample).toBeDefined();
|
||||
// Native Text wraps to its containing block. Font measurement must fit even
|
||||
// when the terminal starts with one column, before layout determines its size.
|
||||
expect(sample!.parentElement!.getBoundingClientRect().width).toBeGreaterThanOrEqual(
|
||||
sample!.getBoundingClientRect().width,
|
||||
);
|
||||
// The first measurement is used to size the emulator before restore data arrives.
|
||||
// A wrapped ten-character sample reports a fraction of a cell and multiple lines.
|
||||
expect(measurements[0].cellWidth).toBeGreaterThan(5);
|
||||
expect(measurements[0].cellHeight).toBeLessThan(24);
|
||||
} finally {
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
}
|
||||
});
|
||||
@@ -407,10 +407,11 @@ export function TerminalGridView({
|
||||
|
||||
return (
|
||||
<View onLayout={handleContainerLayout} pointerEvents="none" style={containerStyle}>
|
||||
{/* Measure against the viewport, independently of the grid width derived from these metrics. */}
|
||||
<Text onLayout={handleMeasure} pointerEvents="none" style={measureStyle}>
|
||||
{MEASURE_TEXT}
|
||||
</Text>
|
||||
<View style={gridStyle}>
|
||||
<Text onLayout={handleMeasure} pointerEvents="none" style={measureStyle}>
|
||||
{MEASURE_TEXT}
|
||||
</Text>
|
||||
{rows.map((row) => (
|
||||
<MemoTerminalGridRow
|
||||
key={row.index}
|
||||
|
||||
Reference in New Issue
Block a user