Skip to content

Instantly share code, notes, and snippets.

@JonathanTurnock
Created December 24, 2025 16:09
Show Gist options
  • Select an option

  • Save JonathanTurnock/181aafec9bd0a43e9856c6e1b6b6690f to your computer and use it in GitHub Desktop.

Select an option

Save JonathanTurnock/181aafec9bd0a43e9856c6e1b6b6690f to your computer and use it in GitHub Desktop.
Bun INK Fullscreen App
import { Box, render, Text, useStdout } from "ink";
import { useCallback, useEffect, useState } from "react";
import { debounceTime, filter, firstValueFrom, fromEvent, map } from "rxjs";
export function useScreenSize() {
const { stdout } = useStdout();
const getSize = useCallback(() => ({ height: stdout.rows, width: stdout.columns }), [stdout]);
const [size, setSize] = useState(getSize);
useEffect(() => {
function onResize() {
setSize(getSize());
}
stdout.on("resize", onResize);
return () => {
stdout.off("resize", onResize);
};
}, [stdout, getSize]);
return size;
}
export default function App(props: { width: number; height: number }) {
return (
<Box height={props.height} width={props.width} borderStyle={"single"}>
<Box flexDirection={"column"}>
<Text>Hello</Text>
<Box>
<Text bold>My Fullscreen TUI</Text>
</Box>
<Box flexGrow={1}>
<Text>Content area (fills remaining space)</Text>
</Box>
</Box>
</Box>
);
}
function write(data: string | Uint8Array): Promise<void> {
return new Promise((resolve, reject) => {
process.stdout.write(data, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
if (process.stdin.isTTY && process.stdin.setRawMode) {
process.stdin.setRawMode(true);
}
process.stdin.resume();
process.stdin.setEncoding("utf8");
export enum Keys {
CTRL_C = "\u0003",
UP_ARROW = "\u001b[A",
DOWN_ARROW = "\u001b[B",
E = "e",
D = "d",
R = "r",
C = "c",
ESCAPE = "\u001b",
}
export enum ANSIEscapeSequences {
ALTERNATE_SCREEN_BUFFER_ENABLE = "\x1b[?1049h",
ALTERNATE_SCREEN_BUFFER_DISABLE = "\x1b[?1049l",
CLEAR_SCREEN = "\x1b[2J\x1b[H",
}
const keyFilter = (filterKey: Keys) => filter((key) => key === filterKey);
const key$ = fromEvent(process.stdin, "data").pipe(map((keyBuffer) => (keyBuffer as Buffer).toString() as Keys));
const exit$ = key$.pipe(keyFilter(Keys.CTRL_C));
const windowSize$ = fromEvent(process.stdout, "resize").pipe(debounceTime(50));
export async function startTui(onDestroy?: () => void) {
console.log("Starting TUI...");
await new Promise((resolve) => setTimeout(resolve, 500));
await write(ANSIEscapeSequences.ALTERNATE_SCREEN_BUFFER_ENABLE);
const inst = render(null);
async function reRender() {
const [width, height] = process.stdout.getWindowSize();
inst.clear();
await write(ANSIEscapeSequences.CLEAR_SCREEN);
inst.rerender(<App height={height} width={width} />);
}
const sxn = windowSize$.subscribe(reRender);
await reRender();
await firstValueFrom(exit$);
inst.unmount();
sxn.unsubscribe();
onDestroy?.();
await write(ANSIEscapeSequences.ALTERNATE_SCREEN_BUFFER_DISABLE);
}
@JonathanTurnock
Copy link
Author

JonathanTurnock commented Dec 24, 2025

Giving issues with fullscreen :(

Bun 1.3.5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment