| 1 | /** |
| 2 | * ANSI escape sequences can be used to style text in a tty or perform other |
| 3 | * interactions. this file contains many constants for escape codes, as well as |
| 4 | * functions for ones that take an argument, such as `cursorUp`. |
| 5 | * |
| 6 | * note that this file only produces escape sequences, and does not yet feature |
| 7 | * detection or fallback code. |
| 8 | * |
| 9 | * @module ansi |
| 10 | */ |
| 11 | |
| 12 | /** resets all ansi styles */ |
| 13 | export const reset: string = `\x1b[0m`; |
| 14 | |
| 15 | /** |
| 16 | * puts the text in bold. can be combined with `dim` and colors. |
| 17 | * reset with `resetWeight`. |
| 18 | */ |
| 19 | export const bold: string = `\x1b[1m`; |
| 20 | /** |
| 21 | * puts the text a more faded color, dimming it. can be combined with `bold` to |
| 22 | * produce bold-dim text. dim can be combined with any color to make a dimmed |
| 23 | * version of that color. reset with `resetWeight`. |
| 24 | */ |
| 25 | export const dim: string = `\x1b[2m`; |
| 26 | /** resets `bold` and `dim` */ |
| 27 | export const resetWeight: string = `\x1b[22m`; |
| 28 | |
| 29 | /** |
| 30 | * swaps the foreground and background colors, which reads as a highlight in |
| 31 | * any color scheme. reset with `resetInverse`. |
| 32 | */ |
| 33 | export const inverse: string = `\x1b[7m`; |
| 34 | /** resets `inverse` */ |
| 35 | export const resetInverse: string = `\x1b[27m`; |
| 36 | |
| 37 | /** resets foreground color */ |
| 38 | export const fgReset: string = `\x1b[39m`; |
| 39 | /** sets the foreground color to black. on light mode terminals, this color may |
| 40 | * be visible. */ |
| 41 | export const fgBlack: string = `\x1b[30m`; |
| 42 | /** sets the foreground color to red */ |
| 43 | export const fgRed: string = `\x1b[31m`; |
| 44 | /** sets the foreground color to green */ |
| 45 | export const fgGreen: string = `\x1b[32m`; |
| 46 | /** sets the foreground color to yellow */ |
| 47 | export const fgYellow: string = `\x1b[33m`; |
| 48 | /** sets the foreground color to blue */ |
| 49 | export const fgBlue: string = `\x1b[34m`; |
| 50 | /** sets the foreground color to magenta */ |
| 51 | export const fgMagenta: string = `\x1b[35m`; |
| 52 | /** sets the foreground color to cyan */ |
| 53 | export const fgCyan: string = `\x1b[36m`; |
| 54 | /** |
| 55 | * sets the foreground color to (dark) white / grey / gray. this color is |
| 56 | * brighter than `fgBrightBlack`. consider `dim` for greyed out text and |
| 57 | * `fgReset` to set the default foreground color. |
| 58 | */ |
| 59 | export const fgWhite: string = `\x1b[37m`; |
| 60 | /** |
| 61 | * sets the foreground color to bright black / grey / gray. consider `dim` for |
| 62 | * greyed out text. |
| 63 | */ |
| 64 | export const fgBrightBlack: string = `\x1b[90m`; |
| 65 | /** sets the foreground color to bright red */ |
| 66 | export const fgBrightRed: string = `\x1b[91m`; |
| 67 | /** sets the foreground color to bright green */ |
| 68 | export const fgBrightGreen: string = `\x1b[92m`; |
| 69 | /** sets the foreground color to bright yellow */ |
| 70 | export const fgBrightYellow: string = `\x1b[93m`; |
| 71 | /** sets the foreground color to bright blue */ |
| 72 | export const fgBrightBlue: string = `\x1b[94m`; |
| 73 | /** sets the foreground color to bright magenta */ |
| 74 | export const fgBrightMagenta: string = `\x1b[95m`; |
| 75 | /** sets the foreground color to bright cyan */ |
| 76 | export const fgBrightCyan: string = `\x1b[96m`; |
| 77 | /** |
| 78 | * sets the foreground color to bright white. consider `dim` for greyed out |
| 79 | * text and `fgReset` to set the default foreground color. |
| 80 | */ |
| 81 | export const fgBrightWhite: string = `\x1b[97m`; |
| 82 | /** sets the foreground color to a color in the 256 color palette. */ |
| 83 | export function fg256(n: number): string { |
| 84 | return `\x1b[38;5;${n}m`; |
| 85 | } |
| 86 | /** sets the foreground color to an rgb color. */ |
| 87 | export function fgRgb(r: number, g: number, b: number): string { |
| 88 | return `\x1b[38;2;${r};${g};${b}m`; |
| 89 | } |
| 90 | |
| 91 | /** resets the cell background color. */ |
| 92 | export const bgReset: string = `\x1b[49m`; |
| 93 | /** sets the cell background to black. consider `bgReset` to clear the background. */ |
| 94 | export const bgBlack: string = `\x1b[40m`; |
| 95 | /** sets the cell background to red. */ |
| 96 | export const bgRed: string = `\x1b[41m`; |
| 97 | /** sets the cell background to green. */ |
| 98 | export const bgGreen: string = `\x1b[42m`; |
| 99 | /** sets the cell background to yellow. */ |
| 100 | export const bgYellow: string = `\x1b[43m`; |
| 101 | /** sets the cell background to blue. */ |
| 102 | export const bgBlue: string = `\x1b[44m`; |
| 103 | /** sets the cell background to magenta. */ |
| 104 | export const bgMagenta: string = `\x1b[45m`; |
| 105 | /** sets the cell background to cyan. */ |
| 106 | export const bgCyan: string = `\x1b[46m`; |
| 107 | /** |
| 108 | * sets the cell background to (dark) white / grey / gray. this color is |
| 109 | * brighter than `bgBrightBlack`. |
| 110 | */ |
| 111 | export const bgWhite: string = `\x1b[47m`; |
| 112 | /** |
| 113 | * sets the cell background to bright black. consider `bgReset` to clear the |
| 114 | * background. |
| 115 | */ |
| 116 | export const bgBrightBlack: string = `\x1b[100m`; |
| 117 | /** sets the cell background to bright red. */ |
| 118 | export const bgBrightRed: string = `\x1b[101m`; |
| 119 | /** sets the cell background to bright green. */ |
| 120 | export const bgBrightGreen: string = `\x1b[102m`; |
| 121 | /** sets the cell background to bright yellow. */ |
| 122 | export const bgBrightYellow: string = `\x1b[103m`; |
| 123 | /** sets the cell background to bright blue. */ |
| 124 | export const bgBrightBlue: string = `\x1b[104m`; |
| 125 | /** sets the cell background to bright magenta. */ |
| 126 | export const bgBrightMagenta: string = `\x1b[105m`; |
| 127 | /** sets the cell background to bright cyan. */ |
| 128 | export const bgBrightCyan: string = `\x1b[106m`; |
| 129 | /** sets the cell background to bright white. */ |
| 130 | export const bgBrightWhite: string = `\x1b[107m`; |
| 131 | /** sets the background color to a color in the 256 color palette. */ |
| 132 | export function bg256(n: number): string { |
| 133 | return `\x1b[48;5;${n}m`; |
| 134 | } |
| 135 | /** sets the background color to an rgb color. */ |
| 136 | export function bgRgb(r: number, g: number, b: number): string { |
| 137 | return `\x1b[48;2;${r};${g};${b}m`; |
| 138 | } |
| 139 | |
| 140 | // cursor management |
| 141 | |
| 142 | /** save the position of the cursor, restored with {@linkcode cursorRestore}. */ |
| 143 | export const cursorSave: string = "\x1b7"; |
| 144 | /** restore the position of the cursor, saved with {@linkcode cursorSave}. */ |
| 145 | export const cursorRestore: string = "\x1b8"; |
| 146 | |
| 147 | /** move the cursor up `n` units. */ |
| 148 | export function cursorUp(n: number): string { |
| 149 | return n ? `\x1b[${n}A` : ""; |
| 150 | } |
| 151 | /** move the cursor down `n` units. */ |
| 152 | export function cursorDown(n: number): string { |
| 153 | return n ? `\x1b[${n}B` : ""; |
| 154 | } |
| 155 | /** move the cursor right `n` units. */ |
| 156 | export function cursorRight(n: number): string { |
| 157 | return n ? `\x1b[${n}C` : ""; |
| 158 | } |
| 159 | /** move the cursor right `n` units. */ |
| 160 | export function cursorLeft(n: number): string { |
| 161 | return n ? `\x1b[${n}D` : ""; |
| 162 | } |
| 163 | /** move the cursor to the top left of the screen. */ |
| 164 | export const cursorHome: string = "\x1b[H"; |
| 165 | /** move the cursor to the start of it's current line. */ |
| 166 | export const startOfLine: string = "\r"; |
| 167 | /** move the cursor to the start of the next line. */ |
| 168 | export const startOfNextLine: string = "\n"; |
| 169 | |
| 170 | // erasure |
| 171 | /** clear from and including the current cell right to the end of the line. */ |
| 172 | export const clearToEndOfLine: string = "\x1b[K"; |
| 173 | /** clear from and including the current cell left to the start of the line. */ |
| 174 | export const clearToStartOfLine: string = "\x1b[1K"; |
| 175 | /** clear the entire line that the cursor is on without moving it. */ |
| 176 | export const clearFullLine: string = "\x1b[2K"; |
| 177 | /** clear from and including the current cell to the end of the screen. */ |
| 178 | export const clearToEndOfScreen: string = "\x1b[0J"; |
| 179 | |
| 180 | /** |
| 181 | * Begin Synchronized Output |
| 182 | * https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036 |
| 183 | */ |
| 184 | export const syncStart: string = "\x1b[?2026h"; |
| 185 | /** End Synchronized Output */ |
| 186 | export const syncEnd: string = "\x1b[?2026l"; |
| 187 | |
| 188 | /** |
| 189 | * Apply ANSI styles to text. `code` is a concatenation of zero or more styles, |
| 190 | * which will be cleared as required. |
| 191 | */ |
| 192 | export function style(code: string, text: string): string { |
| 193 | code = mergeStyles(code); |
| 194 | if (!code) return text; |
| 195 | const fg = /[\[;][39]/.test(code); |
| 196 | const bg = /[\[;](?:4|10)/.test(code); |
| 197 | const bold = /[\[;](?:1)[;m]/.test(code); |
| 198 | const dim = /[\[;](?:2)[;m]/.test(code); |
| 199 | return code + text |
| 200 | + mergeStyles( |
| 201 | (fg ? fgReset : "") |
| 202 | + (bg ? bgReset : "") |
| 203 | + (bold ? resetWeight : "") |
| 204 | + (dim ? resetWeight : ""), |
| 205 | ); |
| 206 | } |
| 207 | |
| 208 | /** |
| 209 | * Given a string of multiple style ansi codes, combines all of them. |
| 210 | * Must be used on a string of only ansi style codes. |
| 211 | */ |
| 212 | export function mergeStyles(code: string): string { |
| 213 | let styles = []; |
| 214 | while (code.length) { |
| 215 | if (!code.startsWith("\x1b[")) { |
| 216 | throw new Error("invalid style string: " + JSON.stringify(code)); |
| 217 | } |
| 218 | const end = code.indexOf("m"); |
| 219 | if (end === -1) { |
| 220 | throw new Error("invalid style string: " + JSON.stringify(code)); |
| 221 | } |
| 222 | styles.push(code.slice(2, end)); |
| 223 | code = code.slice(end + 1); |
| 224 | } |
| 225 | return `\x1b[${styles.join(";")}m`; |
| 226 | } |
| 227 | |
| 228 | /** |
| 229 | * Convert a string with ansi text into a string where all the codes have been |
| 230 | * turned into their original names. |
| 231 | */ |
| 232 | export function debugAnsi(text: string): string { |
| 233 | // deno-fmt-ignore |
| 234 | const verbaitim: (keyof typeof self)[] = [ |
| 235 | "fgCyan", |
| 236 | "fgBlue", |
| 237 | "bgReset", |
| 238 | "bgBlack", |
| 239 | "bgRed", |
| 240 | "bgGreen", |
| 241 | "bgYellow", |
| 242 | "bgBlue", |
| 243 | "bgMagenta", |
| 244 | "bgCyan", |
| 245 | "bgWhite", |
| 246 | "bgBrightBlack", |
| 247 | "bgBrightRed", |
| 248 | "bgBrightGreen", |
| 249 | "bgBrightYellow", |
| 250 | "bgBrightBlue", |
| 251 | "bgBrightMagenta", |
| 252 | "bgBrightCyan", |
| 253 | "bgBrightWhite", |
| 254 | "fgReset", |
| 255 | "fgBlack", |
| 256 | "fgRed", |
| 257 | "fgGreen", |
| 258 | "fgYellow", |
| 259 | "fgMagenta", |
| 260 | "fgWhite", |
| 261 | "fgBrightBlack", |
| 262 | "fgBrightRed", |
| 263 | "fgBrightGreen", |
| 264 | "fgBrightYellow", |
| 265 | "fgBrightBlue", |
| 266 | "fgBrightMagenta", |
| 267 | "fgBrightCyan", |
| 268 | "fgBrightWhite", |
| 269 | "clearToEndOfLine", |
| 270 | "clearToStartOfLine", |
| 271 | "clearFullLine", |
| 272 | "clearToEndOfScreen", |
| 273 | "syncStart", |
| 274 | "syncEnd", |
| 275 | "bold", |
| 276 | "dim", |
| 277 | "resetWeight", |
| 278 | "cursorSave", |
| 279 | "cursorRestore", |
| 280 | ]; |
| 281 | for (const key of verbaitim) { |
| 282 | text = text.replaceAll( |
| 283 | String(self[key]), |
| 284 | style(key.includes("cursor") ? fgBlue : fgCyan, `\${${key}}`), |
| 285 | ); |
| 286 | } |
| 287 | text = text.replace(/\x1b\[(\d+)A/g, style(fgYellow, "$${cursorUp($1)}")); |
| 288 | text = text.replace(/\x1b\[(\d+)B/g, style(fgYellow, "$${cursorDown($1)}")); |
| 289 | text = text.replace(/\x1b\[(\d+)C/g, style(fgYellow, "$${cursorRight($1)}")); |
| 290 | text = text.replace(/\x1b\[(\d+)D/g, style(fgYellow, "$${cursorLeft($1)}")); |
| 291 | text = text.replace(/\n/g, style(fgRed, "\\n")); |
| 292 | text = text.replace(/\r/g, style(fgMagenta, "\\r")); |
| 293 | return text; |
| 294 | } |
| 295 | |
| 296 | /** Measure the visible length of a string, ignoring ansi codes. */ |
| 297 | export function widthInTerminal(str: string): number { |
| 298 | segmenter ??= new Intl.Segmenter(); |
| 299 | let width = 0; |
| 300 | if (str.includes("\x1b")) str = str.replace(ansiEscapes, ""); |
| 301 | for (let i = 0, { length } = str; i < length; i += 1) { |
| 302 | const code = str.charCodeAt(i); |
| 303 | if (code < 32) continue; |
| 304 | if (code < 127) { |
| 305 | width += 1; |
| 306 | continue; |
| 307 | } |
| 308 | // the remainder of the string is measured with the segmenter, which |
| 309 | // handles ascii correctly via segmentWidth (controls are zero-width). |
| 310 | for (const { segment } of segmenter.segment(str.slice(i))) { |
| 311 | width += segmentWidth(segment); |
| 312 | } |
| 313 | return width; |
| 314 | } |
| 315 | return width; |
| 316 | } |
| 317 | |
| 318 | /** |
| 319 | * Like `str.slice(0, columns)`, but respecting width in a terminal. |
| 320 | * preserves ansi escape sequences. |
| 321 | */ |
| 322 | export function trimForTerminal(str: string, columns: number): string { |
| 323 | segmenter ??= new Intl.Segmenter(); |
| 324 | let width = 0; |
| 325 | let ansi = false; |
| 326 | for (let i = 0, { length } = str; i < length; i += 1) { |
| 327 | const code = str.charCodeAt(i); |
| 328 | if (code < 32) { |
| 329 | if (code === 0x1b) { |
| 330 | const esc = str.slice(i).match(ansiEscapes); |
| 331 | if (esc) i += esc[0].length - 1, ansi = true; |
| 332 | } |
| 333 | continue; |
| 334 | } |
| 335 | if (code < 127) { |
| 336 | width += 1; |
| 337 | if (width >= columns) { |
| 338 | const result = str.slice(0, i + 1); |
| 339 | return ansi ? result + "\x1b[0m" : result; |
| 340 | } |
| 341 | continue; |
| 342 | } |
| 343 | // non-ascii path |
| 344 | let out = str.slice(0, i); |
| 345 | str = str.slice(i); |
| 346 | const segments = segmenter.segment(str); |
| 347 | { |
| 348 | const { segment } = UNWRAP(segments.containing(0)); |
| 349 | width += segmentWidth(segment); |
| 350 | if (width > columns) return ansi ? out + "\x1b[0m" : out; |
| 351 | out += segment; |
| 352 | i = segment.length; |
| 353 | } |
| 354 | for ({ length } = str; i < length; i += 1) { |
| 355 | const code = str.charCodeAt(i); |
| 356 | if (code === 0x1b) { |
| 357 | const esc = str.slice(i).match(ansiEscapes); |
| 358 | if (esc) { |
| 359 | i += esc[0].length - 1, ansi = true; |
| 360 | out += esc[0]; |
| 361 | continue; |
| 362 | } |
| 363 | } |
| 364 | const { segment } = UNWRAP(segments.containing(i)); |
| 365 | width += segmentWidth(segment); |
| 366 | if (width > columns) return ansi ? out + "\x1b[0m" : out; |
| 367 | out += segment; |
| 368 | } |
| 369 | return out; |
| 370 | } |
| 371 | return str; |
| 372 | } |
| 373 | |
| 374 | // derived from sindresorhus's wonderful `string-width` |
| 375 | // https://github.com/sindresorhus/string-width/blob/42e7b697393a9b9ff732f9bd3ab1db87c3c208b2/index.js |
| 376 | // https://github.com/sindresorhus/get-east-asian-width/blob/6aee3824b5e2ade910a38ed93cf9d89b7b5a8e5b/index.js |
| 377 | // MIT license |
| 378 | let segmenter: Intl.Segmenter | null = null; |
| 379 | const ansiEscapes = |
| 380 | /[\u001B\u009B][[\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d\/\#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d\/\#&.:=?%@~_]*)*)?(?:\u0007|\u001B\u005C|\u009C))|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g; |
| 381 | const zeroWidthClusterRegex = /^(?:\p{Default_Ignorable_Code_Point}|\p{Control}|\p{Mark}|\p{Surrogate})+$/v; |
| 382 | const leadingNonPrintingRegex = /^[\p{Default_Ignorable_Code_Point}\p{Control}\p{Format}\p{Mark}\p{Surrogate}]+/v; |
| 383 | const rgiEmojiRegex = /^\p{RGI_Emoji}$/v; |
| 384 | |
| 385 | /** remove all ansi escape sequences */ |
| 386 | export function strip(text: string): string { |
| 387 | return text.replace(ansiEscapes, ""); |
| 388 | } |
| 389 | |
| 390 | /** |
| 391 | * ansi text as html, for a browser. the sixteen colors are `var(--ansi0)` to |
| 392 | * `var(--ansi15)`, which {@linkcode htmlColors} defines. no element spans a |
| 393 | * line break, so the html splits into lines where the text does. |
| 394 | */ |
| 395 | export function toHtml(text: string): string { |
| 396 | const style = plainStyle(); |
| 397 | let html = ""; |
| 398 | let last = 0; |
| 399 | for (const match of text.matchAll(sequences)) { |
| 400 | html += formatRun(text.slice(last, match.index), style); |
| 401 | last = match.index + match[0].length; |
| 402 | if (match[2] === "m") applySgr(style, (match[1] || "0").split(/[;:]/).map(Number)); |
| 403 | } |
| 404 | return html + formatRun(text.slice(last), style); |
| 405 | } |
| 406 | |
| 407 | /** the colors that {@linkcode toHtml} refers to, as css declarations that follow `color-scheme` */ |
| 408 | export const htmlColors: string = "--ansi0:light-dark(#000,#666);--ansi1:#cd3131;" |
| 409 | + "--ansi2:light-dark(#00a000,#0dbc79);--ansi3:light-dark(#949800,#e5e510);" |
| 410 | + "--ansi4:light-dark(#0451a5,#2472c8);--ansi5:#bc3fbc;--ansi6:light-dark(#0598bc,#11a8cd);" |
| 411 | + "--ansi7:light-dark(#555,#e5e5e5);--ansi8:#666;--ansi9:light-dark(#cd3131,#f14c4c);" |
| 412 | + "--ansi10:light-dark(#14ce14,#23d18b);--ansi11:light-dark(#b5ba00,#f5f543);" |
| 413 | + "--ansi12:light-dark(#0451a5,#3b8eea);--ansi13:light-dark(#bc05bc,#d670d6);" |
| 414 | + "--ansi14:light-dark(#0598bc,#29b8db);--ansi15:light-dark(#a5a5a5,#e5e5e5);"; |
| 415 | |
| 416 | interface HtmlStyle { |
| 417 | fg: string; |
| 418 | bg: string; |
| 419 | bold: boolean; |
| 420 | dim: boolean; |
| 421 | italic: boolean; |
| 422 | underline: boolean; |
| 423 | inverse: boolean; |
| 424 | } |
| 425 | const plainStyle = (): HtmlStyle => ({ |
| 426 | fg: "", |
| 427 | bg: "", |
| 428 | bold: false, |
| 429 | dim: false, |
| 430 | italic: false, |
| 431 | underline: false, |
| 432 | inverse: false, |
| 433 | }); |
| 434 | |
| 435 | const sequences = /\x1b(?:\[([\x30-\x3f]*)[\x20-\x2f]*([\x40-\x7e])|\][^\x07\x1b]*(?:\x07|\x1b\\)?|.)/g; |
| 436 | |
| 437 | function formatRun(text: string, { fg, bg, bold, dim, italic, underline, inverse }: HtmlStyle) { |
| 438 | if (!text) return ""; |
| 439 | // inverse swaps the colors, the default ones included |
| 440 | const color = inverse ? bg || "Canvas" : fg; |
| 441 | const background = inverse ? fg || "CanvasText" : bg; |
| 442 | const css = (color && `color:${color};`) + (background && `background:${background};`) |
| 443 | + (bold ? "font-weight:bold;" : "") + (dim ? "opacity:.65;" : "") |
| 444 | + (italic ? "font-style:italic;" : "") + (underline ? "text-decoration:underline;" : ""); |
| 445 | if (!css) return escapeHtmlContent(text); |
| 446 | return text.split("\n") |
| 447 | .map((line) => line && `<span style="${css}">${escapeHtmlContent(line)}</span>`) |
| 448 | .join("\n"); |
| 449 | } |
| 450 | |
| 451 | function applySgr(style: HtmlStyle, codes: number[]) { |
| 452 | for (let i = 0; i < codes.length; i += 1) { |
| 453 | const code = UNWRAP(codes[i]); |
| 454 | if (code === 0) Object.assign(style, plainStyle()); |
| 455 | else if (code === 1) style.bold = true; |
| 456 | else if (code === 2) style.dim = true; |
| 457 | else if (code === 22) style.bold = style.dim = false; |
| 458 | else if (code === 3 || code === 23) style.italic = code === 3; |
| 459 | else if (code === 4 || code === 24) style.underline = code === 4; |
| 460 | else if (code === 7 || code === 27) style.inverse = code === 7; |
| 461 | else if (code === 39) style.fg = ""; |
| 462 | else if (code === 49) style.bg = ""; |
| 463 | else if ((code >= 30 && code <= 37) || (code >= 90 && code <= 97)) { |
| 464 | style.fg = `var(--ansi${code % 10 + (code >= 90 ? 8 : 0)})`; |
| 465 | } else if ((code >= 40 && code <= 47) || (code >= 100 && code <= 107)) { |
| 466 | style.bg = `var(--ansi${code % 10 + (code >= 100 ? 8 : 0)})`; |
| 467 | } else if (code === 38 || code === 48) { |
| 468 | const indexed = codes[i + 1] === 5; |
| 469 | const [r = 0, g = 0, b = 0] = codes.slice(i + 2, i + 5); |
| 470 | const color = indexed ? color256(r) : `rgb(${r} ${g} ${b})`; |
| 471 | i += indexed ? 2 : 4; |
| 472 | if (code === 38) style.fg = color; |
| 473 | else style.bg = color; |
| 474 | } |
| 475 | } |
| 476 | } |
| 477 | |
| 478 | function color256(n: number) { |
| 479 | if (n < 16) return `var(--ansi${n})`; |
| 480 | if (n >= 232) return `rgb(${Array(3).fill(8 + (n - 232) * 10).join(" ")})`; |
| 481 | n -= 16; |
| 482 | const level = (c: number) => c && 55 + c * 40; |
| 483 | return `rgb(${level(Math.floor(n / 36))} ${level(Math.floor(n / 6) % 6)} ${level(n % 6)})`; |
| 484 | } |
| 485 | |
| 486 | function segmentWidth(segment: string) { |
| 487 | if (zeroWidthClusterRegex.test(segment)) return 0; |
| 488 | if (rgiEmojiRegex.test(segment)) return 2; |
| 489 | |
| 490 | // deno-fmt-ignore |
| 491 | const list = [ |
| 492 | 0x3000, |
| 493 | 0x231A, |
| 494 | 0x231B, |
| 495 | 0x2329, |
| 496 | 0x232A, |
| 497 | 0x23F0, |
| 498 | 0x23F3, |
| 499 | 0x25FD, |
| 500 | 0x25FE, |
| 501 | 0x2614, |
| 502 | 0x2615, |
| 503 | 0x267F, |
| 504 | 0x2693, |
| 505 | 0x26A1, |
| 506 | 0x26AA, |
| 507 | 0x26AB, |
| 508 | 0x26BD, |
| 509 | 0x26BE, |
| 510 | 0x26C4, |
| 511 | 0x26C5, |
| 512 | 0x26CE, |
| 513 | 0x26D4, |
| 514 | 0x26EA, |
| 515 | 0x26F2, |
| 516 | 0x26F3, |
| 517 | 0x26F5, |
| 518 | 0x26FA, |
| 519 | 0x26FD, |
| 520 | 0x2705, |
| 521 | 0x270A, |
| 522 | 0x270B, |
| 523 | 0x2728, |
| 524 | 0x274C, |
| 525 | 0x274E, |
| 526 | 0x2757, |
| 527 | 0x27B0, |
| 528 | 0x27BF, |
| 529 | 0x2B1B, |
| 530 | 0x2B1C, |
| 531 | 0x2B50, |
| 532 | 0x2B55, |
| 533 | 0x1AFFD, |
| 534 | 0x1AFFE, |
| 535 | 0x1B132, |
| 536 | 0x1B155, |
| 537 | 0x1F004, |
| 538 | 0x1F0CF, |
| 539 | 0x1F18E, |
| 540 | 0x1F250, |
| 541 | 0x1F251, |
| 542 | 0x1F3F4, |
| 543 | 0x1F440, |
| 544 | 0x1F57A, |
| 545 | 0x1F595, |
| 546 | 0x1F596, |
| 547 | 0x1F5A4, |
| 548 | 0x1F6CC, |
| 549 | 0x1F6EB, |
| 550 | 0x1F6EC, |
| 551 | 0x1F7F0, |
| 552 | 0x1FAC8, |
| 553 | ]; |
| 554 | const x = segment.replace(leadingNonPrintingRegex, "").codePointAt(0) ?? 0; |
| 555 | return 1 + +( |
| 556 | // full |
| 557 | x >= 0xFF01 && x <= 0xFF60 || x >= 0xFFE0 && x <= 0xFFE6 |
| 558 | // wide |
| 559 | || x >= 0x1100 && x <= 0x115F || x >= 0x23E9 && x <= 0x23EC |
| 560 | || x >= 0x2630 && x <= 0x2637 || x >= 0x2648 && x <= 0x2653 |
| 561 | || x >= 0x268A && x <= 0x268F || x >= 0x2753 && x <= 0x2755 |
| 562 | || x >= 0x2795 && x <= 0x2797 || x >= 0x2E80 && x <= 0x2E99 |
| 563 | || x >= 0x2E9B && x <= 0x2EF3 || x >= 0x2F00 && x <= 0x2FD5 |
| 564 | || x >= 0x2FF0 && x <= 0x2FFF || x >= 0x3001 && x <= 0x303E |
| 565 | || x >= 0x3041 && x <= 0x3096 || x >= 0x3099 && x <= 0x30FF |
| 566 | || x >= 0x3105 && x <= 0x312F || x >= 0x3131 && x <= 0x318E |
| 567 | || x >= 0x3190 && x <= 0x31E5 || x >= 0x31EF && x <= 0x321E |
| 568 | || x >= 0x3220 && x <= 0x3247 || x >= 0x3250 && x <= 0xA48C |
| 569 | || x >= 0xA490 && x <= 0xA4C6 || x >= 0xA960 && x <= 0xA97C |
| 570 | || x >= 0xAC00 && x <= 0xD7A3 || x >= 0xF900 && x <= 0xFAFF |
| 571 | || x >= 0xFE10 && x <= 0xFE19 || x >= 0xFE30 && x <= 0xFE52 |
| 572 | || x >= 0xFE54 && x <= 0xFE66 || x >= 0xFE68 && x <= 0xFE6B |
| 573 | || x >= 0x16FE0 && x <= 0x16FE4 || x >= 0x16FF0 && x <= 0x16FF6 |
| 574 | || x >= 0x17000 && x <= 0x18CD5 || x >= 0x18CFF && x <= 0x18D1E |
| 575 | || x >= 0x18D80 && x <= 0x18DF2 || x >= 0x1AFF0 && x <= 0x1AFF3 |
| 576 | || x >= 0x1AFF5 && x <= 0x1AFFB || x >= 0x1B150 && x <= 0x1B152 |
| 577 | || x >= 0x1B164 && x <= 0x1B167 || x >= 0x1B170 && x <= 0x1B2FB |
| 578 | || x >= 0x1D300 && x <= 0x1D356 || x >= 0x1D360 && x <= 0x1D376 |
| 579 | || x >= 0x1F191 && x <= 0x1F19A || x >= 0x1F200 && x <= 0x1F202 |
| 580 | || x >= 0x1F210 && x <= 0x1F23B || x >= 0x1F240 && x <= 0x1F248 |
| 581 | || x >= 0x1F260 && x <= 0x1F265 || x >= 0x1F300 && x <= 0x1F320 |
| 582 | || x >= 0x1F32D && x <= 0x1F335 || x >= 0x1F337 && x <= 0x1F37C |
| 583 | || x >= 0x1F37E && x <= 0x1F393 || x >= 0x1F3A0 && x <= 0x1F3CA |
| 584 | || x >= 0x1F3CF && x <= 0x1F3D3 || x >= 0x1F3E0 && x <= 0x1F3F0 |
| 585 | || x >= 0x1F3F8 && x <= 0x1F43E || x >= 0x1F442 && x <= 0x1F4FC |
| 586 | || x >= 0x1F4FF && x <= 0x1F53D || x >= 0x1F54B && x <= 0x1F54E |
| 587 | || x >= 0x1F550 && x <= 0x1F567 || x >= 0x1F5FB && x <= 0x1F64F |
| 588 | || x >= 0x1F680 && x <= 0x1F6C5 || x >= 0x1F6D0 && x <= 0x1F6D2 |
| 589 | || x >= 0x1F6D5 && x <= 0x1F6D8 || x >= 0x1F6DC && x <= 0x1F6DF |
| 590 | || x >= 0x1F6F4 && x <= 0x1F6FC || x >= 0x1F7E0 && x <= 0x1F7EB |
| 591 | || x >= 0x1F90C && x <= 0x1F93A || x >= 0x1F93C && x <= 0x1F945 |
| 592 | || x >= 0x1F947 && x <= 0x1F9FF || x >= 0x1FA70 && x <= 0x1FA7C |
| 593 | || x >= 0x1FA80 && x <= 0x1FA8A || x >= 0x1FA8E && x <= 0x1FAC6 |
| 594 | || x >= 0x1FACD && x <= 0x1FADC || x >= 0x1FADF && x <= 0x1FAEA |
| 595 | || x >= 0x1FAEF && x <= 0x1FAF8 || x >= 0x20000 && x <= 0x2FFFD |
| 596 | || x >= 0x30000 && x <= 0x3FFFD || x >= 0x1B000 && x <= 0x1B122 |
| 597 | || list.includes(x) |
| 598 | ); |
| 599 | } |
| 600 | |
| 601 | import { UNWRAP } from "../assert.ts"; |
| 602 | import { escapeHtmlContent } from "../string.ts"; |
| 603 | import * as self from "./ansi.ts"; |