Download src/ink/root.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 4.6 kB
-
https://huggingface.co/chenbhao/codev/resolve/main/src/ink/root.ts
- Command line
-
hf download hf://chenbhao/codev/src/ink/root.ts
-
curl -L -o root.ts https://huggingface.co/chenbhao/codev/resolve/main/src/ink/root.ts
4.6 kB
| import type { ReactNode } from 'react' | |
| import { logForDebugging } from 'src/utils/debug.js' | |
| import { Stream } from 'stream' | |
| import type { FrameEvent } from './frame.js' | |
| import Ink, { type Options as InkOptions } from './ink.js' | |
| import instances from './instances.js' | |
| export type RenderOptions = { | |
| /** | |
| * Output stream where app will be rendered. | |
| * | |
| * @default process.stdout | |
| */ | |
| stdout?: NodeJS.WriteStream | |
| /** | |
| * Input stream where app will listen for input. | |
| * | |
| * @default process.stdin | |
| */ | |
| stdin?: NodeJS.ReadStream | |
| /** | |
| * Error stream. | |
| * @default process.stderr | |
| */ | |
| stderr?: NodeJS.WriteStream | |
| /** | |
| * Configure whether Ink should listen to Ctrl+C keyboard input and exit the app. This is needed in case `process.stdin` is in raw mode, because then Ctrl+C is ignored by default and process is expected to handle it manually. | |
| * | |
| * @default true | |
| */ | |
| exitOnCtrlC?: boolean | |
| /** | |
| * Patch console methods to ensure console output doesn't mix with Ink output. | |
| * | |
| * @default true | |
| */ | |
| patchConsole?: boolean | |
| /** | |
| * Called after each frame render with timing and flicker information. | |
| */ | |
| onFrame?: (event: FrameEvent) => void | |
| } | |
| export type Instance = { | |
| /** | |
| * Replace previous root node with a new one or update props of the current root node. | |
| */ | |
| rerender: Ink['render'] | |
| /** | |
| * Manually unmount the whole Ink app. | |
| */ | |
| unmount: Ink['unmount'] | |
| /** | |
| * Returns a promise, which resolves when app is unmounted. | |
| */ | |
| waitUntilExit: Ink['waitUntilExit'] | |
| cleanup: () => void | |
| } | |
| /** | |
| * A managed Ink root, similar to react-dom's createRoot API. | |
| * Separates instance creation from rendering so the same root | |
| * can be reused for multiple sequential screens. | |
| */ | |
| export type Root = { | |
| render: (node: ReactNode) => void | |
| unmount: () => void | |
| waitUntilExit: () => Promise<void> | |
| } | |
| /** | |
| * Mount a component and render the output. | |
| */ | |
| export const renderSync = ( | |
| node: ReactNode, | |
| options?: NodeJS.WriteStream | RenderOptions, | |
| ): Instance => { | |
| const opts = getOptions(options) | |
| const inkOptions: InkOptions = { | |
| stdout: process.stdout, | |
| stdin: process.stdin, | |
| stderr: process.stderr, | |
| exitOnCtrlC: true, | |
| patchConsole: true, | |
| ...opts, | |
| } | |
| const instance: Ink = getInstance( | |
| inkOptions.stdout, | |
| () => new Ink(inkOptions), | |
| ) | |
| instance.render(node) | |
| return { | |
| rerender: instance.render, | |
| unmount() { | |
| instance.unmount() | |
| }, | |
| waitUntilExit: instance.waitUntilExit, | |
| cleanup: () => instances.delete(inkOptions.stdout), | |
| } | |
| } | |
| const wrappedRender = async ( | |
| node: ReactNode, | |
| options?: NodeJS.WriteStream | RenderOptions, | |
| ): Promise<Instance> => { | |
| // Preserve the microtask boundary that `await loadYoga()` used to provide. | |
| // Without it, the first render fires synchronously before async startup work | |
| // (e.g. useReplBridge notification state) settles, and the subsequent Static | |
| // write overwrites scrollback instead of appending below the logo. | |
| await Promise.resolve() | |
| const instance = renderSync(node, options) | |
| logForDebugging( | |
| `[render] first ink render: ${Math.round(process.uptime() * 1000)}ms since process start`, | |
| ) | |
| return instance | |
| } | |
| export default wrappedRender | |
| /** | |
| * Create an Ink root without rendering anything yet. | |
| * Like react-dom's createRoot — call root.render() to mount a tree. | |
| */ | |
| export async function createRoot({ | |
| stdout = process.stdout, | |
| stdin = process.stdin, | |
| stderr = process.stderr, | |
| exitOnCtrlC = true, | |
| patchConsole = true, | |
| onFrame, | |
| }: RenderOptions = {}): Promise<Root> { | |
| // See wrappedRender — preserve microtask boundary from the old WASM await. | |
| await Promise.resolve() | |
| const instance = new Ink({ | |
| stdout, | |
| stdin, | |
| stderr, | |
| exitOnCtrlC, | |
| patchConsole, | |
| onFrame, | |
| }) | |
| // Register in the instances map so that code that looks up the Ink | |
| // instance by stdout (e.g. external editor pause/resume) can find it. | |
| instances.set(stdout, instance) | |
| return { | |
| render: node => instance.render(node), | |
| unmount: () => instance.unmount(), | |
| waitUntilExit: () => instance.waitUntilExit(), | |
| } | |
| } | |
| const getOptions = ( | |
| stdout: NodeJS.WriteStream | RenderOptions | undefined = {}, | |
| ): RenderOptions => { | |
| if (stdout instanceof Stream) { | |
| return { | |
| stdout, | |
| stdin: process.stdin, | |
| } | |
| } | |
| return stdout | |
| } | |
| const getInstance = ( | |
| stdout: NodeJS.WriteStream, | |
| createInstance: () => Ink, | |
| ): Ink => { | |
| let instance = instances.get(stdout) | |
| if (!instance) { | |
| instance = createInstance() | |
| instances.set(stdout, instance) | |
| } | |
| return instance | |
| } | |