Add writeAnsiU function and remove tests

`writeAnsi` is a convenient function that automatically encodes text and
automatically adds a reset.

`writeAnsiU` is a unary function that takes in an array of Uint8Arrays
and keys for ANSI_BUFFERS. There is no automatic text encoding, so an
`ansiText` function has been added. There is also no automatic reset.
This allows the function's logic to be simpler, resulting in 3-4 times
performance increase per Windows benchmarking. It is also more flexible
for complex formatting scenarios.

Both functions convienently allow strings that are keys of ANSI_BUFFERS
to be passed, and these are auto-matched. Both functions return a
Promise<number>, allowing for maximum performance but necessitating
error handling on the part of the consumer.

It is also important to `await` these functions when calling in a
specific order. This is an advantage of `writeAnsiU`, which allows for
longer, more complex prints in a single function call.
This commit is contained in:
Eric Rumsey 2025-06-02 10:59:48 -05:00
parent 4151d0335c
commit e83fcd0b4d

View File

@ -100,7 +100,7 @@ export const ANSI_BUFFERS = {
CLEAR_BELOW: encoder.encode(ANSI.CLEAR_BELOW),
}
export const ANSI_DYNAMIC = {
const ANSI_DYNAMIC = {
// Cursor Control
CURSOR_TO: (x: number, y: number) => encoder.encode(ANSI.CURSOR_TO(x, y)),
CURSOR_UP: (x = 1) => encoder.encode(ANSI.CURSOR_UP(x)),
@ -116,7 +116,7 @@ type WriteAnsi = (
t?: string,
a?: boolean,
) => Promise<number>
const writeAnsi: WriteAnsi = async (codes, text?, autoReset = true) => {
const writeAnsi: WriteAnsi = (codes, text?, autoReset = true) => {
const arr = [
...codes,
!text ? emptyBytes : encoder.encode(text),
@ -126,95 +126,21 @@ const writeAnsi: WriteAnsi = async (codes, text?, autoReset = true) => {
return writer(Buffer.concat(arr))
}
const writeAnsi1: WriteAnsi = async (codes, text?, autoReset = true) => {
const buffers = [
...codes,
!text ? emptyBytes : encoder.encode(text),
autoReset ? ANSI_BUFFERS['RESET'] : emptyBytes,
].map(x => x instanceof Uint8Array ? x : ANSI_BUFFERS[x])
type AnsiText = (x: string) => Uint8Array
const ansiText: AnsiText = x => encoder.encode(x)
// Calculate total length and create combined buffer
const totalLength = buffers.reduce((sum, buf) => sum + buf.length, 0)
const combined = new Uint8Array(totalLength)
// Copy all buffers into the combined array
let offset = 0
for (const buffer of buffers) {
combined.set(buffer, offset)
offset += buffer.length
}
return writer(combined)
}
type WriteAnsiU = (x: Array<AnsiBufferKey | Uint8Array>) => Promise<number>
const writeAnsiU: WriteAnsiU = x =>
writer(Buffer.concat(x.map(
x => x instanceof Uint8Array ? x : ANSI_BUFFERS[x],
)))
// For CommonJS compatibility
export default {ANSI, ANSI_BUFFERS, writeAnsi}
const bench = async (x, n) => {
const a = Bun.nanoseconds()
let i = 0
while (i < n) {
await x()
i++
export default {
ANSI,
ANSI_BUFFERS,
ANSI_DYNAMIC,
ansiText,
writeAnsi,
writeAnsiU,
}
const b = Bun.nanoseconds()
return (b - a) / 100000
}
const one = async () => {
await writeAnsi(
['BG_CYAN', 'BLACK', 'ITALIC'],
'This is the first option I want',
)
await writeAnsi(
['BG_BLUE', 'BLACK', 'BOLD'],
'This is the second option I want',
)
await writeAnsi(
['BG_YELLOW', 'BLACK', 'BLINK'],
'Now, this is the third and final option',
)
}
const two = async () => {
await writeAnsi1(
['BG_CYAN', 'BLACK', 'ITALIC'],
'This is the first option I want',
)
await writeAnsi1(
['BG_BLUE', 'BLACK', 'BOLD'],
'This is the second option I want',
)
await writeAnsi1(
['BG_YELLOW', 'BLACK', 'BLINK'],
'Now, this is the third and final option',
)
}
const test = async () => {
const testN = 100000
await bench(one, 100000)
await bench(two, 100000)
const testTwo = await bench(two, testN)
const testOne = await bench(one, testN)
await writeAnsi(
['RESET', ANSI_DYNAMIC.CURSOR_TO(0, 0)],
`${ANSI.CLEAR_SCREEN}\n\n`,
)
await writeAnsi(['BG_YELLOW', 'BLACK'], '---------------\n')
await writeAnsi(['BG_YELLOW', 'BOLD', 'BLACK'], '***************\n')
await writeAnsi(['BG_YELLOW', 'BLACK'], '---------------\n')
await writeAnsi(['BG_YELLOW', 'BLACK'], 'ONE:')
await writeAnsi(['BG_YELLOW', 'GREEN'], testOne.toString() + '\n')
await writeAnsi(['BG_YELLOW', 'BLACK'], 'TWO:')
await writeAnsi(['BG_YELLOW', 'GREEN'], testTwo.toString() + '\n')
await writeAnsi(['BG_YELLOW', 'BLACK'], '---------------\n')
await writeAnsi(['BG_YELLOW', 'BOLD', 'BLACK'], '***************\n')
await writeAnsi(['BG_YELLOW', 'BLACK'], '---------------')
await writeAnsi(['RESET'], '\n\n')
}
test()