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), CLEAR_BELOW: encoder.encode(ANSI.CLEAR_BELOW),
} }
export const ANSI_DYNAMIC = { const ANSI_DYNAMIC = {
// Cursor Control // Cursor Control
CURSOR_TO: (x: number, y: number) => encoder.encode(ANSI.CURSOR_TO(x, y)), CURSOR_TO: (x: number, y: number) => encoder.encode(ANSI.CURSOR_TO(x, y)),
CURSOR_UP: (x = 1) => encoder.encode(ANSI.CURSOR_UP(x)), CURSOR_UP: (x = 1) => encoder.encode(ANSI.CURSOR_UP(x)),
@ -116,7 +116,7 @@ type WriteAnsi = (
t?: string, t?: string,
a?: boolean, a?: boolean,
) => Promise<number> ) => Promise<number>
const writeAnsi: WriteAnsi = async (codes, text?, autoReset = true) => { const writeAnsi: WriteAnsi = (codes, text?, autoReset = true) => {
const arr = [ const arr = [
...codes, ...codes,
!text ? emptyBytes : encoder.encode(text), !text ? emptyBytes : encoder.encode(text),
@ -126,95 +126,21 @@ const writeAnsi: WriteAnsi = async (codes, text?, autoReset = true) => {
return writer(Buffer.concat(arr)) return writer(Buffer.concat(arr))
} }
const writeAnsi1: WriteAnsi = async (codes, text?, autoReset = true) => { type AnsiText = (x: string) => Uint8Array
const buffers = [ const ansiText: AnsiText = x => encoder.encode(x)
...codes,
!text ? emptyBytes : encoder.encode(text),
autoReset ? ANSI_BUFFERS['RESET'] : emptyBytes,
].map(x => x instanceof Uint8Array ? x : ANSI_BUFFERS[x])
// Calculate total length and create combined buffer type WriteAnsiU = (x: Array<AnsiBufferKey | Uint8Array>) => Promise<number>
const totalLength = buffers.reduce((sum, buf) => sum + buf.length, 0) const writeAnsiU: WriteAnsiU = x =>
const combined = new Uint8Array(totalLength) writer(Buffer.concat(x.map(
x => x instanceof Uint8Array ? x : ANSI_BUFFERS[x],
// 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)
}
// For CommonJS compatibility // For CommonJS compatibility
export default {ANSI, ANSI_BUFFERS, writeAnsi} export default {
ANSI,
const bench = async (x, n) => { ANSI_BUFFERS,
const a = Bun.nanoseconds() ANSI_DYNAMIC,
let i = 0 ansiText,
while (i < n) { writeAnsi,
await x() writeAnsiU,
i++
} }
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()