From 97c98e9d9acc31b86c6acafffa0dc9a0137bfbec Mon Sep 17 00:00:00 2001 From: Jacob Bolda Date: Thu, 28 May 2026 00:31:49 -0500 Subject: [PATCH 1/6] examples use `node:` api --- examples/README.md | 7 ++- examples/inline-regions/index.ts | 56 ++++++++++++++++-------- examples/keyboard/index.ts | 73 ++++++++++++++++++++++++++------ examples/keyboard/use-stdin.ts | 4 +- 4 files changed, 107 insertions(+), 33 deletions(-) diff --git a/examples/README.md b/examples/README.md index 212f7a0..169b2b4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,7 +6,8 @@ with information about your terminal, shell, operating system and any other information that could be pertinent to reproducing the issue. > [!NOTE] -> Run the commands in this document from the repository root. +> Run the commands in this document from the repository root. These examples use +> `node:` terminal APIs so the same files can be run with either Deno or Node. ## Prerequisites @@ -24,6 +25,8 @@ Run it with: ```sh deno run examples/keyboard/index.ts +# or +node examples/keyboard/index.ts ``` What it shows: @@ -48,6 +51,8 @@ Run it with: ```sh deno run examples/inline-regions/index.ts +# or +node examples/inline-regions/index.ts ``` What it shows: diff --git a/examples/inline-regions/index.ts b/examples/inline-regions/index.ts index 601dea8..922ecb1 100644 --- a/examples/inline-regions/index.ts +++ b/examples/inline-regions/index.ts @@ -8,7 +8,10 @@ * 4. Commit — restore cursor past region, advance with \n */ -import { main, type Operation, sleep, until } from "effection"; +import { Buffer } from "node:buffer"; +import { readSync } from "node:fs"; +import process from "node:process"; +import { ensure, main, type Operation, sleep, until } from "effection"; import { close, createInput, @@ -28,8 +31,23 @@ import { import { cursor, settings } from "../../settings.ts"; import { validated } from "../../validate.ts"; +function terminalSize(): { columns: number; rows: number } { + return process.stdout.isTTY + ? { + columns: process.stdout.columns ?? 80, + rows: process.stdout.rows ?? 24, + } + : { columns: 80, rows: 24 }; +} + +function setRawMode(enabled: boolean): void { + if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") { + process.stdin.setRawMode(enabled); + } +} + const encode = (s: string) => new TextEncoder().encode(s); -const write = (b: Uint8Array) => Deno.stdout.writeSync(b); +const write = (b: Uint8Array) => process.stdout.write(Buffer.from(b)); const GREEN = rgba(80, 250, 123); const GRAY = rgba(100, 100, 100); @@ -49,10 +67,10 @@ function* queryCursor(): Operation { let parser = yield* until(createInput({ escLatency: 100 })); write(DSR()); - let buf = new Uint8Array(32); + let buf = Buffer.allocUnsafe(32); while (true) { - let n = Deno.stdin.readSync(buf); - if (n === null) continue; + let n = readSync(process.stdin.fd, buf, 0, buf.length, null); + if (n === 0) continue; let result = parser.scan(buf.subarray(0, n)); for (let ev of result.events) { if (ev.type === "cursor") { @@ -62,16 +80,16 @@ function* queryCursor(): Operation { } } -function waitKey() { - let buf = new Uint8Array(32); +function waitKey(): void { + let buf = Buffer.allocUnsafe(32); while (true) { - let n = Deno.stdin.readSync(buf); - if (n === null) continue; + let n = readSync(process.stdin.fd, buf, 0, buf.length, null); + if (n === 0) continue; for (let i = 0; i < n; i++) { if (buf[i] === 0x03) { - Deno.stdin.setRaw(false); + setRawMode(false); write(SHOWCURSOR()); - Deno.exit(0); + process.exit(0); } } return; @@ -112,7 +130,7 @@ function* transaction( frames: number, interval: number, ): Operation { - let { columns } = Deno.consoleSize(); + let { columns } = terminalSize(); write(encode("\n".repeat(height))); @@ -142,17 +160,23 @@ function say(msg: string) { write(encode(msg + "\n")); } -function pause() { +function pause(): void { waitKey(); write(encode("\n")); } await main(function* () { - let { columns } = Deno.consoleSize(); - Deno.stdin.setRaw(true); + let { columns } = terminalSize(); + setRawMode(true); let tty = settings(cursor(false)); write(tty.apply); + yield* ensure(() => { + setRawMode(false); + write(CSI("0m")); + write(tty.revert); + }); + // Introduction say("Clayterm can render entire scenes, but it can also render"); say('"inline" for a streaming UI. This is useful for semi-interactive'); @@ -338,6 +362,4 @@ await main(function* () { write(CSI("0m")); write(encode("\n")); - write(tty.revert); - Deno.stdin.setRaw(false); }); diff --git a/examples/keyboard/index.ts b/examples/keyboard/index.ts index ef8fec8..7f12dfd 100644 --- a/examples/keyboard/index.ts +++ b/examples/keyboard/index.ts @@ -1,4 +1,6 @@ // deno-lint-ignore-file no-fallthrough +import { Buffer } from "node:buffer"; +import process from "node:process"; import { createChannel, each, @@ -33,6 +35,54 @@ import { import { useInput } from "./use-input.ts"; import { useStdin } from "./use-stdin.ts"; +function terminalSize(): { columns: number; rows: number } { + return process.stdout.isTTY + ? { + columns: process.stdout.columns ?? 80, + rows: process.stdout.rows ?? 24, + } + : { columns: 80, rows: 24 }; +} + +function setRawMode(enabled: boolean): void { + if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") { + process.stdin.setRawMode(enabled); + } +} + +function writeStdout(bytes: Uint8Array): void { + process.stdout.write(Buffer.from(bytes)); +} + +function equalBytes(a: Uint8Array, b: Uint8Array): boolean { + if (a.length !== b.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + return true; +} + +function equalSetting(a: Setting, b: Setting): boolean { + return equalBytes(a.apply, b.apply) && equalBytes(a.revert, b.revert); +} + +// Avoid rewriting terminal input modes on every mousemove. Deno's `node:` TTY +// compatibility layer on Windows is sensitive to that churn even when the +// effective settings are unchanged. +function updateFlagsIfChanged(current: Setting, next: Setting): Setting { + if (equalSetting(current, next)) { + return current; + } + + writeStdout(current.revert); + writeStdout(next.apply); + return next; +} + const active = rgba(60, 120, 220); const inactive = rgba(50, 50, 60); const on = rgba(40, 180, 80); @@ -563,11 +613,9 @@ function ttyFlags(ctx: AppContext): Setting { } await main(function* () { - let { columns, rows } = Deno.stdout.isTerminal() - ? Deno.consoleSize() - : { columns: 80, rows: 24 }; + let { columns, rows } = terminalSize(); - Deno.stdin.setRaw(true); + setRawMode(true); let stdin = yield* useStdin(); let input = useInput(stdin); @@ -575,22 +623,23 @@ await main(function* () { let term = yield* until(createTerm({ width: columns, height: rows })); let tty = settings(alternateBuffer(), cursor(false)); - Deno.stdout.writeSync(tty.apply); + writeStdout(tty.apply); let modality = recognizer(); let context = modality.next().value; let flags = ttyFlags(context); - Deno.stdout.writeSync(flags.apply); + writeStdout(flags.apply); yield* ensure(() => { - Deno.stdout.writeSync(flags.revert); - Deno.stdout.writeSync(tty.revert); + setRawMode(false); + writeStdout(flags.revert); + writeStdout(tty.revert); }); let { output } = term.render(keyboard(context)); - Deno.stdout.writeSync(output); + writeStdout(output); let pointer = { events: createChannel(), @@ -616,9 +665,7 @@ await main(function* () { context = { ...context, logged: prev }; } - Deno.stdout.writeSync(flags.revert); - flags = ttyFlags(context); - Deno.stdout.writeSync(flags.apply); + flags = updateFlagsIfChanged(flags, ttyFlags(context)); if (context["Capture mouse events"]) { if ("x" in event) { @@ -640,7 +687,7 @@ await main(function* () { yield* pointer.events.send(event); } - Deno.stdout.writeSync(output); + writeStdout(output); yield* each.next(); } diff --git a/examples/keyboard/use-stdin.ts b/examples/keyboard/use-stdin.ts index fc55e5d..7150ed5 100644 --- a/examples/keyboard/use-stdin.ts +++ b/examples/keyboard/use-stdin.ts @@ -8,12 +8,12 @@ import { type Stream, until, } from "effection"; +import process from "node:process"; export function useStdin(): Operation> { return resource(function* (provide) { let channel = createChannel(); - - let iterator = Deno.stdin.readable[Symbol.asyncIterator](); + let iterator = process.stdin.iterator() as AsyncIterator; yield* spawn(function* () { let next = yield* until(iterator.next()); From cfa7eadf03ab6624fb16fbc6ca303fb63b087e08 Mon Sep 17 00:00:00 2001 From: Jacob Bolda Date: Sun, 31 May 2026 00:02:19 -0500 Subject: [PATCH 2/6] move main to top --- examples/inline-regions/index.ts | 269 ++++++++++++++++--------------- examples/keyboard/index.ts | 162 +++++++++---------- 2 files changed, 216 insertions(+), 215 deletions(-) diff --git a/examples/inline-regions/index.ts b/examples/inline-regions/index.ts index 922ecb1..9d97e93 100644 --- a/examples/inline-regions/index.ts +++ b/examples/inline-regions/index.ts @@ -31,140 +31,6 @@ import { import { cursor, settings } from "../../settings.ts"; import { validated } from "../../validate.ts"; -function terminalSize(): { columns: number; rows: number } { - return process.stdout.isTTY - ? { - columns: process.stdout.columns ?? 80, - rows: process.stdout.rows ?? 24, - } - : { columns: 80, rows: 24 }; -} - -function setRawMode(enabled: boolean): void { - if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") { - process.stdin.setRawMode(enabled); - } -} - -const encode = (s: string) => new TextEncoder().encode(s); -const write = (b: Uint8Array) => process.stdout.write(Buffer.from(b)); - -const GREEN = rgba(80, 250, 123); -const GRAY = rgba(100, 100, 100); -const CYAN = rgba(139, 233, 253); - -const RED = rgba(255, 0, 0); -const ORANGE = rgba(255, 153, 0); -const YELLOW = rgba(255, 255, 0); -const NGREEN = rgba(51, 255, 0); -const BLUE = rgba(0, 153, 255); -const VIOLET = rgba(102, 0, 255); -const RAINBOW = [RED, ORANGE, YELLOW, NGREEN, BLUE, VIOLET]; - -const BRAILLE = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; - -function* queryCursor(): Operation { - let parser = yield* until(createInput({ escLatency: 100 })); - write(DSR()); - - let buf = Buffer.allocUnsafe(32); - while (true) { - let n = readSync(process.stdin.fd, buf, 0, buf.length, null); - if (n === 0) continue; - let result = parser.scan(buf.subarray(0, n)); - for (let ev of result.events) { - if (ev.type === "cursor") { - return ev; - } - } - } -} - -function waitKey(): void { - let buf = Buffer.allocUnsafe(32); - while (true) { - let n = readSync(process.stdin.fd, buf, 0, buf.length, null); - if (n === 0) continue; - for (let i = 0; i < n; i++) { - if (buf[i] === 0x03) { - setRawMode(false); - write(SHOWCURSOR()); - process.exit(0); - } - } - return; - } -} - -function box(msg: string, fg: number, border: number): Op[] { - return [ - open("root", { - layout: { width: grow(), height: grow(), direction: "ttb" }, - }), - open("box", { - layout: { - width: grow(), - height: grow(), - direction: "ttb", - padding: { left: 1 }, - alignY: 2, - }, - border: { - color: border, - left: 1, - right: 1, - top: 1, - bottom: 1, - }, - cornerRadius: { tl: 1, tr: 1, bl: 1, br: 1 }, - }), - text(msg, { color: fg }), - close(), - close(), - ]; -} - -function* transaction( - height: number, - renderFrame: (frame: number) => Op[], - frames: number, - interval: number, -): Operation { - let { columns } = terminalSize(); - - write(encode("\n".repeat(height))); - - let pos = yield* queryCursor(); - /** 1-based terminal row where the region starts */ - let row = pos.row - height + 1; - - write(ESC("7")); - let tty = settings(cursor(false)); - write(tty.apply); - - let term = validated( - yield* until(createTerm({ width: columns, height })), - ); - for (let i = 0; i < frames; i++) { - let result = term.render(renderFrame(i), { row }); - write(new Uint8Array(result.output)); - yield* sleep(interval); - } - - write(tty.revert); - write(ESC("8")); - write(encode("\n")); -} - -function say(msg: string) { - write(encode(msg + "\n")); -} - -function pause(): void { - waitKey(); - write(encode("\n")); -} - await main(function* () { let { columns } = terminalSize(); setRawMode(true); @@ -172,6 +38,7 @@ await main(function* () { write(tty.apply); yield* ensure(() => { + // SGR reset sequence setRawMode(false); write(CSI("0m")); write(tty.revert); @@ -363,3 +230,137 @@ await main(function* () { write(CSI("0m")); write(encode("\n")); }); + +function terminalSize(): { columns: number; rows: number } { + return process.stdout.isTTY + ? { + columns: process.stdout.columns ?? 80, + rows: process.stdout.rows ?? 24, + } + : { columns: 80, rows: 24 }; +} + +function setRawMode(enabled: boolean): void { + if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") { + process.stdin.setRawMode(enabled); + } +} + +const encode = (s: string) => new TextEncoder().encode(s); +const write = (b: Uint8Array) => process.stdout.write(Buffer.from(b)); + +const GREEN = rgba(80, 250, 123); +const GRAY = rgba(100, 100, 100); +const CYAN = rgba(139, 233, 253); + +const RED = rgba(255, 0, 0); +const ORANGE = rgba(255, 153, 0); +const YELLOW = rgba(255, 255, 0); +const NGREEN = rgba(51, 255, 0); +const BLUE = rgba(0, 153, 255); +const VIOLET = rgba(102, 0, 255); +const RAINBOW = [RED, ORANGE, YELLOW, NGREEN, BLUE, VIOLET]; + +const BRAILLE = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + +function* queryCursor(): Operation { + let parser = yield* until(createInput({ escLatency: 100 })); + write(DSR()); + + let buf = Buffer.allocUnsafe(32); + while (true) { + let n = readSync(process.stdin.fd, buf, 0, buf.length, null); + if (n === 0) continue; + let result = parser.scan(buf.subarray(0, n)); + for (let ev of result.events) { + if (ev.type === "cursor") { + return ev; + } + } + } +} + +function waitKey(): void { + let buf = Buffer.allocUnsafe(32); + while (true) { + let n = readSync(process.stdin.fd, buf, 0, buf.length, null); + if (n === 0) continue; + for (let i = 0; i < n; i++) { + if (buf[i] === 0x03) { + setRawMode(false); + write(SHOWCURSOR()); + process.exit(0); + } + } + return; + } +} + +function box(msg: string, fg: number, border: number): Op[] { + return [ + open("root", { + layout: { width: grow(), height: grow(), direction: "ttb" }, + }), + open("box", { + layout: { + width: grow(), + height: grow(), + direction: "ttb", + padding: { left: 1 }, + alignY: 2, + }, + border: { + color: border, + left: 1, + right: 1, + top: 1, + bottom: 1, + }, + cornerRadius: { tl: 1, tr: 1, bl: 1, br: 1 }, + }), + text(msg, { color: fg }), + close(), + close(), + ]; +} + +function* transaction( + height: number, + renderFrame: (frame: number) => Op[], + frames: number, + interval: number, +): Operation { + let { columns } = terminalSize(); + + write(encode("\n".repeat(height))); + + let pos = yield* queryCursor(); + /** 1-based terminal row where the region starts */ + let row = pos.row - height + 1; + + write(ESC("7")); + let tty = settings(cursor(false)); + write(tty.apply); + + let term = validated( + yield* until(createTerm({ width: columns, height })), + ); + for (let i = 0; i < frames; i++) { + let result = term.render(renderFrame(i), { row }); + write(new Uint8Array(result.output)); + yield* sleep(interval); + } + + write(tty.revert); + write(ESC("8")); + write(encode("\n")); +} + +function say(msg: string) { + write(encode(msg + "\n")); +} + +function pause(): void { + waitKey(); + write(encode("\n")); +} diff --git a/examples/keyboard/index.ts b/examples/keyboard/index.ts index 7f12dfd..b0e20ab 100644 --- a/examples/keyboard/index.ts +++ b/examples/keyboard/index.ts @@ -35,6 +35,87 @@ import { import { useInput } from "./use-input.ts"; import { useStdin } from "./use-stdin.ts"; +await main(function* () { + let { columns, rows } = terminalSize(); + + setRawMode(true); + + let stdin = yield* useStdin(); + let input = useInput(stdin); + + let term = yield* until(createTerm({ width: columns, height: rows })); + + let tty = settings(alternateBuffer(), cursor(false)); + writeStdout(tty.apply); + + let modality = recognizer(); + let context = modality.next().value; + + let flags = ttyFlags(context); + writeStdout(flags.apply); + + yield* ensure(() => { + setRawMode(false); + writeStdout(flags.revert); + writeStdout(tty.revert); + }); + + let { output } = term.render(keyboard(context)); + + writeStdout(output); + + let pointer = { + events: createChannel(), + state: undefined as { x: number; y: number; down: boolean } | undefined, + }; + + for (let event of yield* each(merge(input, pointer.events))) { + if (event.type === "keydown" && event.ctrl && event.key === "c") { + break; + } + if (event.type === "pointerenter") { + context.entered.add(event.id); + } + if (event.type === "pointerleave") { + context.entered.delete(event.id); + } + + let prev = context.logged; + context = modality.next(event).value; + if (context.event && context.log[context.event.type as keyof EventFilter]) { + context = { ...context, logged: context.event }; + } else { + context = { ...context, logged: prev }; + } + + flags = updateFlagsIfChanged(flags, ttyFlags(context)); + + if (context["Capture mouse events"]) { + if ("x" in event) { + pointer.state = { + x: event.x, + y: event.y, + down: event.type === "mousedown", + }; + } + } else { + pointer.state = undefined; + } + + let { output, events } = term.render(keyboard(context), { + pointer: pointer.state, + }); + + for (let event of events) { + yield* pointer.events.send(event); + } + + writeStdout(output); + + yield* each.next(); + } +}); + function terminalSize(): { columns: number; rows: number } { return process.stdout.isTTY ? { @@ -612,87 +693,6 @@ function ttyFlags(ctx: AppContext): Setting { return settings(...parts); } -await main(function* () { - let { columns, rows } = terminalSize(); - - setRawMode(true); - - let stdin = yield* useStdin(); - let input = useInput(stdin); - - let term = yield* until(createTerm({ width: columns, height: rows })); - - let tty = settings(alternateBuffer(), cursor(false)); - writeStdout(tty.apply); - - let modality = recognizer(); - let context = modality.next().value; - - let flags = ttyFlags(context); - writeStdout(flags.apply); - - yield* ensure(() => { - setRawMode(false); - writeStdout(flags.revert); - writeStdout(tty.revert); - }); - - let { output } = term.render(keyboard(context)); - - writeStdout(output); - - let pointer = { - events: createChannel(), - state: undefined as { x: number; y: number; down: boolean } | undefined, - }; - - for (let event of yield* each(merge(input, pointer.events))) { - if (event.type === "keydown" && event.ctrl && event.key === "c") { - break; - } - if (event.type === "pointerenter") { - context.entered.add(event.id); - } - if (event.type === "pointerleave") { - context.entered.delete(event.id); - } - - let prev = context.logged; - context = modality.next(event).value; - if (context.event && context.log[context.event.type as keyof EventFilter]) { - context = { ...context, logged: context.event }; - } else { - context = { ...context, logged: prev }; - } - - flags = updateFlagsIfChanged(flags, ttyFlags(context)); - - if (context["Capture mouse events"]) { - if ("x" in event) { - pointer.state = { - x: event.x, - y: event.y, - down: event.type === "mousedown", - }; - } - } else { - pointer.state = undefined; - } - - let { output, events } = term.render(keyboard(context), { - pointer: pointer.state, - }); - - for (let event of events) { - yield* pointer.events.send(event); - } - - writeStdout(output); - - yield* each.next(); - } -}); - function* recognizer(): Iterator { let current: AppContext = { mode: "input", From 4c7b779afb4b6a2a554cfffefcae830f5ce4752f Mon Sep 17 00:00:00 2001 From: Jacob Bolda Date: Sun, 31 May 2026 00:46:42 -0500 Subject: [PATCH 3/6] direct to examples folder --- README.md | 2 +- examples/README.md | 16 +++++++++++++++- examples/inline-regions/index.ts | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7e2fd70..18a6afb 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ for consumers. ### Examples -The application in this demo uses Clayterm for all layout and input parsing +See this keyboard example and more in the [examples folder](examples/README.md). This demo uses Clayterm for all layout and input parsing. #### Keyboard Events diff --git a/examples/README.md b/examples/README.md index 169b2b4..a0c2eba 100644 --- a/examples/README.md +++ b/examples/README.md @@ -43,6 +43,20 @@ Related files: events - `examples/keyboard/use-stdin.ts` adapts stdin into a byte stream for the demo +#### Keyboard Events + +The input parser decodes raw terminal bytes into structured events. Here you can +see each key event as the string "hello world" is typed. + +![Keyboard events demo](keyboard/keyboard-key-events.gif) + +#### Pointer Events + +Here we see hover styles applied to UI elements in response to the pointer +state. Clay drives the hit testing; no manual coordinate math required. + +![Pointer events demo](keyboard/keyboard-pointer-events.gif) + ## Inline Regions Path: `examples/inline-regions/index.ts` @@ -58,7 +72,7 @@ node examples/inline-regions/index.ts What it shows: - rendering animated regions into normal terminal scrollback -- querying cursor position with DSR to place later frames correctly +- querying cursor position with Device Status Report (DSR) to place later frames correctly - updating a previously allocated region without taking over the whole screen - small animated demos including a spinner, a progress bar, and a nyan-cat-style sequence diff --git a/examples/inline-regions/index.ts b/examples/inline-regions/index.ts index 9d97e93..2be060d 100644 --- a/examples/inline-regions/index.ts +++ b/examples/inline-regions/index.ts @@ -3,7 +3,7 @@ * * Shows the region lifecycle: * 1. Allocate space with raw newlines - * 2. DSR — queries cursor position to compute `top` + * 2. Device Status Report (DSR) — queries cursor position to compute `top` * 3. CUP mode (all frames) — renders at `top` * 4. Commit — restore cursor past region, advance with \n */ From a56b7b12075f40c6ae16c3beda709fa52aee1d80 Mon Sep 17 00:00:00 2001 From: Jacob Bolda Date: Sun, 31 May 2026 00:48:49 -0500 Subject: [PATCH 4/6] fmt --- README.md | 3 ++- examples/README.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 18a6afb..4d55961 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ for consumers. ### Examples -See this keyboard example and more in the [examples folder](examples/README.md). This demo uses Clayterm for all layout and input parsing. +See this keyboard example and more in the [examples folder](examples/README.md). +This demo uses Clayterm for all layout and input parsing. #### Keyboard Events diff --git a/examples/README.md b/examples/README.md index a0c2eba..9009edc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -72,7 +72,8 @@ node examples/inline-regions/index.ts What it shows: - rendering animated regions into normal terminal scrollback -- querying cursor position with Device Status Report (DSR) to place later frames correctly +- querying cursor position with Device Status Report (DSR) to place later frames + correctly - updating a previously allocated region without taking over the whole screen - small animated demos including a spinner, a progress bar, and a nyan-cat-style sequence From 9ded66cf638bf2729b3b5d5bbf8937e9227b5ced Mon Sep 17 00:00:00 2001 From: Jacob Bolda Date: Sun, 31 May 2026 02:57:45 -0500 Subject: [PATCH 5/6] fix const ordering --- examples/inline-regions/index.ts | 62 +++++++++++++++++++--------- examples/keyboard/index.ts | 70 ++++++++++++++++---------------- 2 files changed, 77 insertions(+), 55 deletions(-) diff --git a/examples/inline-regions/index.ts b/examples/inline-regions/index.ts index 2be060d..6968ff4 100644 --- a/examples/inline-regions/index.ts +++ b/examples/inline-regions/index.ts @@ -31,6 +31,23 @@ import { import { cursor, settings } from "../../settings.ts"; import { validated } from "../../validate.ts"; +const encode = (s: string) => new TextEncoder().encode(s); +const write = (b: Uint8Array) => process.stdout.write(Buffer.from(b)); + +const GREEN = rgba(80, 250, 123); +const GRAY = rgba(100, 100, 100); +const CYAN = rgba(139, 233, 253); + +const RED = rgba(255, 0, 0); +const ORANGE = rgba(255, 153, 0); +const YELLOW = rgba(255, 255, 0); +const NGREEN = rgba(51, 255, 0); +const BLUE = rgba(0, 153, 255); +const VIOLET = rgba(102, 0, 255); +const RAINBOW = [RED, ORANGE, YELLOW, NGREEN, BLUE, VIOLET]; + +const BRAILLE = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + await main(function* () { let { columns } = terminalSize(); setRawMode(true); @@ -246,30 +263,25 @@ function setRawMode(enabled: boolean): void { } } -const encode = (s: string) => new TextEncoder().encode(s); -const write = (b: Uint8Array) => process.stdout.write(Buffer.from(b)); - -const GREEN = rgba(80, 250, 123); -const GRAY = rgba(100, 100, 100); -const CYAN = rgba(139, 233, 253); - -const RED = rgba(255, 0, 0); -const ORANGE = rgba(255, 153, 0); -const YELLOW = rgba(255, 255, 0); -const NGREEN = rgba(51, 255, 0); -const BLUE = rgba(0, 153, 255); -const VIOLET = rgba(102, 0, 255); -const RAINBOW = [RED, ORANGE, YELLOW, NGREEN, BLUE, VIOLET]; - -const BRAILLE = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; - function* queryCursor(): Operation { let parser = yield* until(createInput({ escLatency: 100 })); write(DSR()); let buf = Buffer.allocUnsafe(32); while (true) { - let n = readSync(process.stdin.fd, buf, 0, buf.length, null); + let n: number; + try { + n = readSync(process.stdin.fd, buf, 0, buf.length, null); + } catch (error) { + if ( + error && typeof error === "object" && + ("code" in error && (error.code === "EAGAIN" || error.code === "EINTR")) + ) { + continue; + } + throw error; + } + if (n === 0) continue; let result = parser.scan(buf.subarray(0, n)); for (let ev of result.events) { @@ -283,7 +295,19 @@ function* queryCursor(): Operation { function waitKey(): void { let buf = Buffer.allocUnsafe(32); while (true) { - let n = readSync(process.stdin.fd, buf, 0, buf.length, null); + let n: number; + try { + n = readSync(process.stdin.fd, buf, 0, buf.length, null); + } catch (error) { + if ( + error && typeof error === "object" && + ("code" in error && (error.code === "EAGAIN" || error.code === "EINTR")) + ) { + continue; + } + throw error; + } + if (n === 0) continue; for (let i = 0; i < n; i++) { if (buf[i] === 0x03) { diff --git a/examples/keyboard/index.ts b/examples/keyboard/index.ts index b0e20ab..45bffb0 100644 --- a/examples/keyboard/index.ts +++ b/examples/keyboard/index.ts @@ -35,6 +35,40 @@ import { import { useInput } from "./use-input.ts"; import { useStdin } from "./use-stdin.ts"; +const active = rgba(60, 120, 220); +const inactive = rgba(50, 50, 60); +const on = rgba(40, 180, 80); +const label = rgba(220, 220, 220); +const dim = rgba(100, 100, 120); +const highlight = rgba(255, 220, 80); + +const KEY_W = 5; +const GAP = 1; +const hovered = rgba(80, 80, 100); + +const flagNames: + (keyof Omit)[] = [ + "Disambiguate escape codes", + "Report event types", + "Report alternate keys", + "Report all keys as escapes", + "Report associated text", + ]; + +const logEntries: { key: string; name: keyof EventFilter }[] = [ + { key: "a", name: "keydown" }, + { key: "b", name: "keyup" }, + { key: "c", name: "keyrepeat" }, + { key: "d", name: "mousedown" }, + { key: "e", name: "mouseup" }, + { key: "f", name: "mousemove" }, + { key: "g", name: "wheel" }, + { key: "h", name: "resize" }, + { key: "i", name: "pointerenter" }, + { key: "j", name: "pointerleave" }, + { key: "k", name: "pointerclick" }, +]; + await main(function* () { let { columns, rows } = terminalSize(); @@ -164,16 +198,6 @@ function updateFlagsIfChanged(current: Setting, next: Setting): Setting { return next; } -const active = rgba(60, 120, 220); -const inactive = rgba(50, 50, 60); -const on = rgba(40, 180, 80); -const label = rgba(220, 220, 220); -const dim = rgba(100, 100, 120); -const highlight = rgba(255, 220, 80); - -const KEY_W = 5; -const GAP = 1; - interface KeyDef { label: string; code: string; @@ -189,8 +213,6 @@ function matches(k: KeyDef, event: InputEvent | PointerEvent): boolean { event.code.toUpperCase() === k.code.toUpperCase(); } -const hovered = rgba(80, 80, 100); - function key(ops: Op[], k: KeyDef, ctx: AppContext): void { let pressed = ctx.event && matches(k, ctx.event); let hover = ctx.entered.has(`key:${k.code}`); @@ -459,30 +481,6 @@ function toggle(ops: Op[], enabled: boolean, name: string): void { ); } -const flagNames: - (keyof Omit)[] = - [ - "Disambiguate escape codes", - "Report event types", - "Report alternate keys", - "Report all keys as escapes", - "Report associated text", - ]; - -const logEntries: { key: string; name: keyof EventFilter }[] = [ - { key: "a", name: "keydown" }, - { key: "b", name: "keyup" }, - { key: "c", name: "keyrepeat" }, - { key: "d", name: "mousedown" }, - { key: "e", name: "mouseup" }, - { key: "f", name: "mousemove" }, - { key: "g", name: "wheel" }, - { key: "h", name: "resize" }, - { key: "i", name: "pointerenter" }, - { key: "j", name: "pointerleave" }, - { key: "k", name: "pointerclick" }, -]; - function logToggle( ops: Op[], entries: typeof logEntries, From f65d1729e5c0da5c019a57e46003f8e226d8b239 Mon Sep 17 00:00:00 2001 From: Jacob Bolda Date: Sun, 31 May 2026 11:43:59 -0500 Subject: [PATCH 6/6] fmt again --- examples/keyboard/index.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/keyboard/index.ts b/examples/keyboard/index.ts index 45bffb0..bf7a7c8 100644 --- a/examples/keyboard/index.ts +++ b/examples/keyboard/index.ts @@ -47,13 +47,14 @@ const GAP = 1; const hovered = rgba(80, 80, 100); const flagNames: - (keyof Omit)[] = [ - "Disambiguate escape codes", - "Report event types", - "Report alternate keys", - "Report all keys as escapes", - "Report associated text", - ]; + (keyof Omit)[] = + [ + "Disambiguate escape codes", + "Report event types", + "Report alternate keys", + "Report all keys as escapes", + "Report associated text", + ]; const logEntries: { key: string; name: keyof EventFilter }[] = [ { key: "a", name: "keydown" },