| 1 | import tty from "node:tty"; |
| 2 | |
| 3 | // eslint-disable-next-line no-warning-comments |
| 4 | // TODO: Use a better method when it's added to Node.js (https://github.com/nodejs/node/pull/40240) |
| 5 | // Lots of optionals here to support Deno. |
| 6 | const hasColors = tty?.WriteStream?.prototype?.hasColors?.() ?? false; |
| 7 | |
| 8 | const format = (open, close) => { |
| 9 | if (!hasColors) { |
| 10 | return (input) => input; |
| 11 | } |
| 12 | |
| 13 | const openCode = `\u001B[${open}m`; |
| 14 | const closeCode = `\u001B[${close}m`; |
| 15 | |
| 16 | return (input) => { |
| 17 | const string = input + ""; // eslint-disable-line no-implicit-coercion -- This is faster. |
| 18 | let index = string.indexOf(closeCode); |
| 19 | |
| 20 | if (index === -1) { |
| 21 | // Note: Intentionally not using string interpolation for performance reasons. |
| 22 | return openCode + string + closeCode; |
| 23 | } |
| 24 | |
| 25 | // Handle nested colors. |
| 26 | |
| 27 | // We could have done this, but it's too slow (as of Node.js 22). |
| 28 | // return openCode + string.replaceAll(closeCode, (close === 22 ? closeCode : '') + openCode) + closeCode; |
| 29 | |
| 30 | let result = openCode; |
| 31 | let lastIndex = 0; |
| 32 | |
| 33 | // SGR 22 resets both bold (1) and dim (2). When we encounter a nested |
| 34 | // close for styles that use 22, we need to re-open the outer style. |
| 35 | const reopenOnNestedClose = close === 22; |
| 36 | const replaceCode = (reopenOnNestedClose ? closeCode : "") + openCode; |
| 37 | |
| 38 | while (index !== -1) { |
| 39 | result += string.slice(lastIndex, index) + replaceCode; |
| 40 | lastIndex = index + closeCode.length; |
| 41 | index = string.indexOf(closeCode, lastIndex); |
| 42 | } |
| 43 | |
| 44 | result += string.slice(lastIndex) + closeCode; |
| 45 | |
| 46 | return result; |
| 47 | }; |
| 48 | }; |
| 49 | |
| 50 | export const reset = format(0, 0); |
| 51 | export const bold = format(1, 22); |
| 52 | export const dim = format(2, 22); |
| 53 | export const italic = format(3, 23); |
| 54 | export const underline = format(4, 24); |
| 55 | export const overline = format(53, 55); |
| 56 | export const inverse = format(7, 27); |
| 57 | export const hidden = format(8, 28); |
| 58 | export const strikethrough = format(9, 29); |
| 59 | |
| 60 | export const black = format(30, 39); |
| 61 | export const red = format(31, 39); |
| 62 | export const green = format(32, 39); |
| 63 | export const yellow = format(33, 39); |
| 64 | export const blue = format(34, 39); |
| 65 | export const magenta = format(35, 39); |
| 66 | export const cyan = format(36, 39); |
| 67 | export const white = format(37, 39); |
| 68 | export const gray = format(90, 39); |
| 69 | |
| 70 | export const bgBlack = format(40, 49); |
| 71 | export const bgRed = format(41, 49); |
| 72 | export const bgGreen = format(42, 49); |
| 73 | export const bgYellow = format(43, 49); |
| 74 | export const bgBlue = format(44, 49); |
| 75 | export const bgMagenta = format(45, 49); |
| 76 | export const bgCyan = format(46, 49); |
| 77 | export const bgWhite = format(47, 49); |
| 78 | export const bgGray = format(100, 49); |
| 79 | |
| 80 | export const redBright = format(91, 39); |
| 81 | export const greenBright = format(92, 39); |
| 82 | export const yellowBright = format(93, 39); |
| 83 | export const blueBright = format(94, 39); |
| 84 | export const magentaBright = format(95, 39); |
| 85 | export const cyanBright = format(96, 39); |
| 86 | export const whiteBright = format(97, 39); |
| 87 | |
| 88 | export const bgRedBright = format(101, 49); |
| 89 | export const bgGreenBright = format(102, 49); |
| 90 | export const bgYellowBright = format(103, 49); |
| 91 | export const bgBlueBright = format(104, 49); |
| 92 | export const bgMagentaBright = format(105, 49); |
| 93 | export const bgCyanBright = format(106, 49); |
| 94 | export const bgWhiteBright = format(107, 49); |