diff --git a/src/lib/util/ansi.ts b/src/lib/util/ansi.ts index 6490732..1d6fffe 100644 --- a/src/lib/util/ansi.ts +++ b/src/lib/util/ansi.ts @@ -1,6 +1,9 @@ -import { type Result, tryCatch } from '@maxmorozoff/try-catch-tuple' +import { nanoseconds } from 'bun' const encoder = new TextEncoder() +const emptyBytes = encoder.encode('') +type Writer = (x: Uint8Array | Array) => Promise +const writer: Writer = x => Bun.write(Bun.stdout, x) export const ANSI = { // Text Styles @@ -108,137 +111,110 @@ export const ANSI_DYNAMIC = { type AnsiBufferKey = keyof typeof ANSI_BUFFERS -export async function writeAnsi( - codes: (AnsiBufferKey | Uint8Array)[], - text?: string, - autoReset: boolean = true, -): Promise> { - const buffers: Uint8Array[] = [] +type WriteAnsi = ( + c: Array<(AnsiBufferKey | Uint8Array)>, + t?: string, + a?: boolean, +) => Promise +const writeAnsi: WriteAnsi = async (codes, text?, autoReset = true) => { + const arr = [ + ...codes, + !text ? emptyBytes : encoder.encode(text), + autoReset ? ANSI_BUFFERS['RESET'] : emptyBytes, + ].map(x => x instanceof Uint8Array ? x : ANSI_BUFFERS[x]) - // 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 tryCatch(() => Bun.write(Bun.stdout, combined)) - return tryCatch(() => process.stdout.write(combined)) + return writer(Buffer.concat(arr)) } -export async function writeAnsi2( - codes: (AnsiBufferKey | Uint8Array)[], - text?: string, - autoReset: boolean = true, -): Promise> { - const buffers: Uint8Array[] = [] +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]) - // 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), - ) + // 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 buf of buffers) { - combined.set(buf, offset) - offset += buf.length + for (const buffer of buffers) { + combined.set(buffer, offset) + offset += buffer.length } - return Bun.write(Bun.stdout, combined) - // return tryCatch(() => process.stdout.write(combined)) + return writer(combined) } // For CommonJS compatibility 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 bench = async (x, n) => { const a = Bun.nanoseconds() - let i = 0 while (i < n) { - await x(y) + await x() i++ } const b = Bun.nanoseconds() - return writeAnsi2(['YELLOW'], `${Math.round((b - a) / 1000000)}ms`) + return (b - a) / 100000 } -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') +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', + ) } -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, - ), -) + +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()