Skip to content

Commit 84b45c5

Browse files
committed
Make ptywright wait revisions span listener registration
Recheck the session revision after once("update") so a PTY update between snapshot and sleep cannot be missed. Drop the silent CSI fallback, write encoded bytes through one helper, and run ptywright tests in CI.
1 parent 8266593 commit 84b45c5

5 files changed

Lines changed: 28 additions & 72 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ jobs:
8888
run: npx tsc -b
8989
- name: Build ptywright (native binding)
9090
run: npm run build --workspace @onkernel/ptywright
91+
- name: Ptywright tests
92+
run: npm test --workspace @onkernel/ptywright
9193
- name: CLI unit tests
9294
env:
9395
PTYWRIGHT_REQUIRED: "1"

‎packages/ptywright/native/src/addon.cc‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,6 @@ Napi::Value EncodeSpecialKey(const Napi::CallbackInfo &info) {
196196
&len);
197197
if (result != 0) {
198198
ThrowGhosttyError(env, "ptywright_ghostty_terminal_encode_special_key", result);
199-
ptywright_ghostty_free_bytes(bytes);
200199
return env.Undefined();
201200
}
202201
if (bytes == nullptr || len == 0) {

‎packages/ptywright/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"build:native": "node ./scripts/build-ghostty.mjs && node-gyp rebuild --directory native",
1919
"clean": "tsc -b --clean",
2020
"clean:native": "node-gyp clean --directory native",
21-
"test": "node --test dist/test/*.test.js"
21+
"test": "tsx --test src/test/*.test.ts"
2222
},
2323
"gypfile": false,
2424
"dependencies": {

‎packages/ptywright/src/session.ts‎

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export class PtySession {
6565
this.transcript += data;
6666
const { replyBytes } = this.terminal.feed(data);
6767
if (replyBytes && replyBytes.length > 0) {
68-
this.pty.write(Buffer.from(replyBytes).toString("utf8"));
68+
this.writeBytes(replyBytes);
6969
}
7070
this.noteUpdate();
7171
});
@@ -94,8 +94,7 @@ export class PtySession {
9494

9595
pressKey(key: SpecialKey): void {
9696
this.ensureOpen();
97-
const bytes = this.terminal.encodeSpecialKey(key);
98-
this.pty.write(Buffer.from(bytes).toString("latin1"));
97+
this.writeBytes(this.terminal.encodeSpecialKey(key));
9998
}
10099

101100
resize(cols: number, rows: number): void {
@@ -151,10 +150,7 @@ export class PtySession {
151150
if (this.exitedAt) {
152151
throw this.buildWaitError(description, snapshot, new Error("process exited before condition was satisfied"));
153152
}
154-
if (this.revision !== seen) {
155-
continue;
156-
}
157-
await waitForUpdate(this.events, controller.signal);
153+
await waitForUpdate(this.events, controller.signal, undefined, () => this.revision !== seen);
158154
}
159155
} catch (error) {
160156
if (controller.signal.aborted) {
@@ -189,10 +185,7 @@ export class PtySession {
189185
if (this.exitedAt) {
190186
return snapshot;
191187
}
192-
if (this.revision !== seen) {
193-
continue;
194-
}
195-
await waitForUpdate(this.events, controller.signal, stableForMs);
188+
await waitForUpdate(this.events, controller.signal, stableForMs, () => this.revision !== seen);
196189
}
197190
} catch (error) {
198191
if (controller.signal.aborted) {
@@ -213,13 +206,7 @@ export class PtySession {
213206
try {
214207
while (!this.exitedAt) {
215208
const seen = this.revision;
216-
if (this.exitedAt) {
217-
break;
218-
}
219-
if (this.revision !== seen) {
220-
continue;
221-
}
222-
await waitForUpdate(this.events, controller.signal);
209+
await waitForUpdate(this.events, controller.signal, undefined, () => this.revision !== seen || Boolean(this.exitedAt));
223210
}
224211
return this.status();
225212
} catch (error) {
@@ -284,6 +271,13 @@ export class PtySession {
284271
this.events.emit("update");
285272
}
286273

274+
private writeBytes(bytes: Uint8Array): void {
275+
if (bytes.length === 0) {
276+
return;
277+
}
278+
this.pty.write(Buffer.from(bytes).toString("utf8"));
279+
}
280+
287281
private ensureOpen(): void {
288282
if (this.closed) {
289283
throw new Error("session already closed");
@@ -356,7 +350,12 @@ function createWaitController(options?: WaitOptions): { signal: AbortSignal; cle
356350
};
357351
}
358352

359-
async function waitForUpdate(events: EventEmitter, signal: AbortSignal, timeoutMs?: number): Promise<void> {
353+
async function waitForUpdate(
354+
events: EventEmitter,
355+
signal: AbortSignal,
356+
timeoutMs?: number,
357+
alreadyChanged?: () => boolean,
358+
): Promise<void> {
360359
if (signal.aborted) {
361360
throw abortReason(signal);
362361
}
@@ -381,6 +380,11 @@ async function waitForUpdate(events: EventEmitter, signal: AbortSignal, timeoutM
381380

382381
events.once("update", onUpdate);
383382
signal.addEventListener("abort", onAbort, { once: true });
383+
if (alreadyChanged?.()) {
384+
cleanup();
385+
resolve();
386+
return;
387+
}
384388
if (timeoutMs !== undefined) {
385389
timer = setTimeout(() => {
386390
cleanup();

‎packages/ptywright/src/terminal.ts‎

Lines changed: 2 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,4 @@
1-
import {
2-
KeyArrowDown,
3-
KeyArrowLeft,
4-
KeyArrowRight,
5-
KeyArrowUp,
6-
KeyBacktab,
7-
KeyDelete,
8-
KeyEnd,
9-
KeyEscape,
10-
KeyHome,
11-
KeyInsert,
12-
KeyPageDown,
13-
KeyPageUp,
14-
SPECIAL_KEY_KIND,
15-
type SpecialKey,
16-
type SpecialKeyName,
17-
} from "./keys";
1+
import { SPECIAL_KEY_KIND, type SpecialKey } from "./keys";
182
import { loadNativeBinding, type NativeSnapshot, type NativeTerminalHandle } from "./native-loader";
193

204
export interface CreateTerminalOptions {
@@ -88,11 +72,7 @@ export class TerminalSurface {
8872
if (key.kind !== SPECIAL_KEY_KIND) {
8973
throw new Error("encodeSpecialKey expects a SpecialKey");
9074
}
91-
const encoded = this.native.encodeSpecialKey(key.name);
92-
if (encoded && encoded.length > 0) {
93-
return encoded;
94-
}
95-
return Buffer.from(legacySpecialKey(key.name), "latin1");
75+
return this.native.encodeSpecialKey(key.name) ?? new Uint8Array();
9676
}
9777

9878
dispose(): void {
@@ -128,35 +108,6 @@ function normalizeSnapshot(snapshot: NativeSnapshot): TerminalSnapshot {
128108
};
129109
}
130110

131-
function legacySpecialKey(name: SpecialKeyName): string {
132-
switch (name) {
133-
case "arrow_up":
134-
return KeyArrowUp;
135-
case "arrow_down":
136-
return KeyArrowDown;
137-
case "arrow_left":
138-
return KeyArrowLeft;
139-
case "arrow_right":
140-
return KeyArrowRight;
141-
case "home":
142-
return KeyHome;
143-
case "end":
144-
return KeyEnd;
145-
case "page_up":
146-
return KeyPageUp;
147-
case "page_down":
148-
return KeyPageDown;
149-
case "insert":
150-
return KeyInsert;
151-
case "delete":
152-
return KeyDelete;
153-
case "escape":
154-
return KeyEscape;
155-
case "backtab":
156-
return KeyBacktab;
157-
}
158-
}
159-
160111
function splitLines(text: string): string[] {
161112
if (!text) {
162113
return [];

0 commit comments

Comments
 (0)