diff --git a/src/lib/util/ansi.ts b/src/lib/util/ansi.ts index 475223f..6490732 100644 --- a/src/lib/util/ansi.ts +++ b/src/lib/util/ansi.ts @@ -1,6 +1,6 @@ -import { tryCatch, type Result } from '@maxmorozoff/try-catch-tuple'; +import { type Result, tryCatch } from '@maxmorozoff/try-catch-tuple' -const encoder = new TextEncoder(); +const encoder = new TextEncoder() export const ANSI = { // Text Styles @@ -51,7 +51,7 @@ export const ANSI = { CLEAR_TO_END: '\x1B[0J', // From cursor to end of screen CLEAR_TO_START: '\x1B[1J', // From cursor to beginning of screen CLEAR_BELOW: '\x1B[J', -}; +} export const ANSI_BUFFERS = { // Static code buffers @@ -95,7 +95,7 @@ export const ANSI_BUFFERS = { CLEAR_TO_END: encoder.encode(ANSI.CLEAR_TO_END), CLEAR_TO_START: encoder.encode(ANSI.CLEAR_TO_START), CLEAR_BELOW: encoder.encode(ANSI.CLEAR_BELOW), -}; +} export const ANSI_DYNAMIC = { // Cursor Control @@ -104,49 +104,141 @@ export const ANSI_DYNAMIC = { CURSOR_DOWN: (x = 1) => encoder.encode(ANSI.CURSOR_DOWN(x)), CURSOR_FORWARD: (x = 1) => encoder.encode(ANSI.CURSOR_FORWARD(x)), CURSOR_BACK: (x = 1) => encoder.encode(ANSI.CURSOR_BACK(x)), -}; +} -type AnsiBufferKey = keyof typeof ANSI_BUFFERS; +type AnsiBufferKey = keyof typeof ANSI_BUFFERS export async function writeAnsi( codes: (AnsiBufferKey | Uint8Array)[], text?: string, - autoReset: boolean = true + autoReset: boolean = true, ): Promise> { - const buffers: Uint8Array[] = []; + const buffers: Uint8Array[] = [] // Add requested ANSI codes for (const code of codes) { if (code instanceof Uint8Array) { - buffers.push(code); + buffers.push(code) } else { - buffers.push(ANSI_BUFFERS[code]); + buffers.push(ANSI_BUFFERS[code]) } } // Add optional text if (text) { - buffers.push(encoder.encode(text)); + buffers.push(encoder.encode(text)) } // Auto-reset if requested if (autoReset && !codes.includes('RESET')) { - buffers.push(ANSI_BUFFERS.RESET); + buffers.push(ANSI_BUFFERS.RESET) } // Combine all buffers into single write const combined = new Uint8Array( - buffers.reduce((acc, buf) => acc + buf.length, 0) - ); + buffers.reduce((acc, buf) => acc + buf.length, 0), + ) - let offset = 0; + let offset = 0 for (const buf of buffers) { - combined.set(buf, offset); - offset += buf.length; + combined.set(buf, offset) + offset += buf.length } - return tryCatch(() => Bun.write(Bun.stdout, combined)); + // return tryCatch(() => Bun.write(Bun.stdout, combined)) + return tryCatch(() => process.stdout.write(combined)) +} + +export async function writeAnsi2( + codes: (AnsiBufferKey | Uint8Array)[], + text?: string, + autoReset: boolean = true, +): Promise> { + const buffers: Uint8Array[] = [] + + // Add requested ANSI codes + for (const code of codes) { + if (code instanceof Uint8Array) { + buffers.push(code) + } else { + buffers.push(ANSI_BUFFERS[code]) + } + } + + // Add optional text + if (text) { + buffers.push(encoder.encode(text)) + } + + // Auto-reset if requested + if (autoReset && !codes.includes('RESET')) { + buffers.push(ANSI_BUFFERS.RESET) + } + + // Combine all buffers into single write + const combined = new Uint8Array( + buffers.reduce((acc, buf) => acc + buf.length, 0), + ) + + let offset = 0 + for (const buf of buffers) { + combined.set(buf, offset) + offset += buf.length + } + + return Bun.write(Bun.stdout, combined) + // return tryCatch(() => process.stdout.write(combined)) } // For CommonJS compatibility -export default { ANSI, ANSI_BUFFERS, writeAnsi }; +export default {ANSI, ANSI_BUFFERS, writeAnsi} + +const testNum = 5 +// const testCase = [['BG_WHITE', 'BLACK'], 'This is some text'] +const testCase = + `${ANSI_BUFFERS.BG_WHITE}${ANSI_BUFFERS.BLACK}This is some text${ANSI_BUFFERS.RESET}` +const testCase2 = `${ANSI.BG_WHITE}${ANSI.BLACK}This is some text` + +const bench = async (x, y, n) => { + const a = Bun.nanoseconds() + + let i = 0 + while (i < n) { + await x(y) + i++ + } + const b = Bun.nanoseconds() + return writeAnsi2(['YELLOW'], `${Math.round((b - a) / 1000000)}ms`) +} + +const out = async (a, b, c) => { + const one = await a + const two = await b + const three = await c + + const log = x => async y => console.log(x, await y) + const diff = x => y => { + if (y < x) return Math.round(((y - x) / y) * 100) + return Math.round(((y - x) / x) * 100) + } + + writeAnsi(['RESET', 'CLEAR_SCREEN', ANSI_DYNAMIC.CURSOR_TO(0, 0)]) + + console.log('') + console.log('---------------') + console.log('Output in milliseconds') + console.log('---------------') + log('ANSI: ')(!one ? '' : one) + log('ANSI_BUFFERS: ')(!two ? '' : two) + log('THREE: ')(!three ? '' : three) + log(`${diff(one)(two)}%`)('faster') +} +out( + bench(x => Bun.write(Bun.stdout, x), testCase, testNum), + bench(x => Bun.write(Bun.stdout, x), testCase2, testNum), + bench( + x => writeAnsi2(...x), + [['BG_WHITE', 'BLACK'], 'This is some text'], + testNum, + ), +)