basant307/AI_Governance_Project
048
1import stringWidth from 'string-width';2import stripAnsi from 'strip-ansi';3import ansiStyles from 'ansi-styles';4 5const ANSI_ESCAPE = '\u001B';6const ANSI_ESCAPE_CSI = '\u009B';7const ESCAPES = new Set([8 ANSI_ESCAPE,9 ANSI_ESCAPE_CSI,10]);11 12const ANSI_ESCAPE_BELL = '\u0007';13const ANSI_CSI = '[';14const ANSI_OSC = ']';15const ANSI_SGR_TERMINATOR = 'm';16const ANSI_SGR_RESET = 0;17const ANSI_SGR_RESET_FOREGROUND = 39;18const ANSI_SGR_RESET_BACKGROUND = 49;19const ANSI_SGR_RESET_UNDERLINE_COLOR = 59;20const ANSI_SGR_FOREGROUND_EXTENDED = 38;21const ANSI_SGR_BACKGROUND_EXTENDED = 48;22const ANSI_SGR_UNDERLINE_COLOR_EXTENDED = 58;23const ANSI_SGR_COLOR_MODE_256 = 5;24const ANSI_SGR_COLOR_MODE_RGB = 2;25const ANSI_ESCAPE_LINK = `${ANSI_OSC}8;;`;26const ANSI_ESCAPE_REGEX = new RegExp(`^\\u001B(?:\\${ANSI_CSI}(?<sgr>[0-9;]*)${ANSI_SGR_TERMINATOR}|${ANSI_ESCAPE_LINK}(?<uri>[^\\u0007\\u001B]*)(?:\\u0007|\\u001B\\\\))`);27const ANSI_ESCAPE_CSI_REGEX = new RegExp(`^\\u009B(?<sgr>[0-9;]*)${ANSI_SGR_TERMINATOR}`);28const ANSI_SGR_MODIFIER_CLOSE_CODES = new Set(ansiStyles.codes.values());29ANSI_SGR_MODIFIER_CLOSE_CODES.delete(ANSI_SGR_RESET);30 31const segmenter = new Intl.Segmenter();32const getGraphemes = string => Array.from(segmenter.segment(string), ({segment}) => segment);33const TAB_SIZE = 8;34 35const wrapAnsiCode = code => `${ANSI_ESCAPE}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;36const wrapAnsiHyperlink = url => `${ANSI_ESCAPE}${ANSI_ESCAPE_LINK}${url}${ANSI_ESCAPE_BELL}`;37 38const getSgrTokens = sgrParameters => {39 const codes = sgrParameters.split(';').map(sgrParameter => sgrParameter === '' ? ANSI_SGR_RESET : Number.parseInt(sgrParameter, 10));40 const sgrTokens = [];41 42 for (let index = 0; index < codes.length; index++) {43 const code = codes[index];44 45 if (!Number.isFinite(code)) {46 continue;47 }48 49 if (50 (51 code === ANSI_SGR_FOREGROUND_EXTENDED52 || code === ANSI_SGR_BACKGROUND_EXTENDED53 || code === ANSI_SGR_UNDERLINE_COLOR_EXTENDED54 )55 ) {56 if (index + 1 >= codes.length) {57 break;58 }59 60 const mode = codes[index + 1];61 62 if (mode === ANSI_SGR_COLOR_MODE_256 && Number.isFinite(codes[index + 2])) {63 sgrTokens.push([code, mode, codes[index + 2]]);64 index += 2;65 continue;66 }67 68 const red = codes[index + 2];69 const green = codes[index + 3];70 const blue = codes[index + 4];71 if (72 mode === ANSI_SGR_COLOR_MODE_RGB73 && Number.isFinite(red)74 && Number.isFinite(green)75 && Number.isFinite(blue)76 ) {77 sgrTokens.push([code, mode, red, green, blue]);78 index += 4;79 continue;80 }81 82 break;83 }84 85 sgrTokens.push([code]);86 }87 88 return sgrTokens;89};90 91const removeActiveStyle = (activeStyles, family) => {92 const activeStyleIndex = activeStyles.findIndex(activeStyle => activeStyle.family === family);93 94 if (activeStyleIndex !== -1) {95 activeStyles.splice(activeStyleIndex, 1);96 }97};98 99const upsertActiveStyle = (activeStyles, nextActiveStyle) => {100 removeActiveStyle(activeStyles, nextActiveStyle.family);101 activeStyles.push(nextActiveStyle);102};103 104const removeModifierStylesByClose = (activeStyles, closeCode) => {105 for (let index = activeStyles.length - 1; index >= 0; index--) {106 const activeStyle = activeStyles[index];107 if (activeStyle.family.startsWith('modifier-') && activeStyle.close === closeCode) {108 activeStyles.splice(index, 1);109 }110 }111};112 113const getColorStyle = (code, sgrToken) => {114 if ((code >= 30 && code <= 37) || (code >= 90 && code <= 97) || (code === ANSI_SGR_FOREGROUND_EXTENDED && sgrToken.length > 1)) {115 return {116 family: 'foreground',117 open: sgrToken.join(';'),118 close: ANSI_SGR_RESET_FOREGROUND,119 };120 }121 122 if ((code >= 40 && code <= 47) || (code >= 100 && code <= 107) || (code === ANSI_SGR_BACKGROUND_EXTENDED && sgrToken.length > 1)) {123 return {124 family: 'background',125 open: sgrToken.join(';'),126 close: ANSI_SGR_RESET_BACKGROUND,127 };128 }129 130 if (code === ANSI_SGR_UNDERLINE_COLOR_EXTENDED && sgrToken.length > 1) {131 return {132 family: 'underlineColor',133 open: sgrToken.join(';'),134 close: ANSI_SGR_RESET_UNDERLINE_COLOR,135 };136 }137};138 139const applySgrResetCode = (code, activeStyles) => {140 if (code === ANSI_SGR_RESET) {141 activeStyles.length = 0;142 return true;143 }144 145 if (code === ANSI_SGR_RESET_FOREGROUND) {146 removeActiveStyle(activeStyles, 'foreground');147 return true;148 }149 150 if (code === ANSI_SGR_RESET_BACKGROUND) {151 removeActiveStyle(activeStyles, 'background');152 return true;153 }154 155 if (code === ANSI_SGR_RESET_UNDERLINE_COLOR) {156 removeActiveStyle(activeStyles, 'underlineColor');157 return true;158 }159 160 if (ANSI_SGR_MODIFIER_CLOSE_CODES.has(code)) {161 removeModifierStylesByClose(activeStyles, code);162 return true;163 }164 165 return false;166};167 168const applySgrToken = (sgrToken, activeStyles) => {169 const [code] = sgrToken;170 171 if (applySgrResetCode(code, activeStyles)) {172 return;173 }174 175 const colorStyle = getColorStyle(code, sgrToken);176 if (colorStyle) {177 upsertActiveStyle(activeStyles, colorStyle);178 return;179 }180 181 const close = ansiStyles.codes.get(code);182 if (close !== undefined && close !== ANSI_SGR_RESET) {183 upsertActiveStyle(activeStyles, {184 family: `modifier-${code}`,185 open: sgrToken.join(';'),186 close,187 });188 }189};190 191const applySgrParameters = (sgrParameters, activeStyles) => {192 for (const sgrToken of getSgrTokens(sgrParameters)) {193 applySgrToken(sgrToken, activeStyles);194 }195};196 197const applySgrResets = (sgrParameters, activeStyles) => {198 for (const sgrToken of getSgrTokens(sgrParameters)) {199 const [code] = sgrToken;200 applySgrResetCode(code, activeStyles);201 }202};203 204const applyLeadingSgrResets = (string, activeStyles) => {205 let remainder = string;206 207 while (remainder.length > 0) {208 if (remainder.startsWith(ANSI_ESCAPE) && remainder[1] !== '\\') {209 const match = ANSI_ESCAPE_REGEX.exec(remainder);210 if (!match) {211 break;212 }213 214 if (match.groups.sgr !== undefined) {215 applySgrResets(match.groups.sgr, activeStyles);216 }217 218 remainder = remainder.slice(match[0].length);219 continue;220 }221 222 if (remainder.startsWith(ANSI_ESCAPE_CSI)) {223 const match = ANSI_ESCAPE_CSI_REGEX.exec(remainder);224 if (!match || match.groups.sgr === undefined) {225 break;226 }227 228 applySgrResets(match.groups.sgr, activeStyles);229 remainder = remainder.slice(match[0].length);230 continue;231 }232 233 break;234 }235};236 237const getClosingSgrSequence = activeStyles => [...activeStyles].reverse().map(activeStyle => wrapAnsiCode(activeStyle.close)).join('');238const getOpeningSgrSequence = activeStyles => activeStyles.map(activeStyle => wrapAnsiCode(activeStyle.open)).join('');239 240// Calculate the length of words split on ' ', ignoring241// the extra characters added by ANSI escape codes242const wordLengths = string => string.split(' ').map(word => stringWidth(word));243 244// Wrap a long word across multiple rows245// ANSI escape codes do not count towards length246const wrapWord = (rows, word, columns) => {247 const characters = getGraphemes(word);248 249 let isInsideEscape = false;250 let isInsideLinkEscape = false;251 let visible = stringWidth(stripAnsi(rows.at(-1)));252 253 for (const [index, character] of characters.entries()) {254 const characterLength = stringWidth(character);255 256 if (visible + characterLength <= columns) {257 rows[rows.length - 1] += character;258 } else {259 rows.push(character);260 visible = 0;261 }262 263 if (ESCAPES.has(character) && !(isInsideLinkEscape && character === ANSI_ESCAPE && characters[index + 1] === '\\')) {264 isInsideEscape = true;265 266 const ansiEscapeLinkCandidate = characters.slice(index + 1, index + 1 + ANSI_ESCAPE_LINK.length).join('');267 isInsideLinkEscape = ansiEscapeLinkCandidate === ANSI_ESCAPE_LINK;268 }269 270 if (isInsideEscape) {271 if (isInsideLinkEscape) {272 if (273 character === ANSI_ESCAPE_BELL274 || (character === '\\' && index > 0 && characters[index - 1] === ANSI_ESCAPE) // ST terminator (ESC \)275 ) {276 isInsideEscape = false;277 isInsideLinkEscape = false;278 }279 } else if (character === ANSI_SGR_TERMINATOR) {280 isInsideEscape = false;281 }282 283 continue;284 }285 286 visible += characterLength;287 288 if (visible === columns && index < characters.length - 1) {289 rows.push('');290 visible = 0;291 }292 }293 294 // It's possible that the last row we copy over is only295 // ANSI escape characters, handle this edge-case296 if (!visible && rows.at(-1).length > 0 && rows.length > 1) {297 rows[rows.length - 2] += rows.pop();298 }299};300 301// Trims spaces from a string ignoring invisible sequences302const stringVisibleTrimSpacesRight = string => {303 const words = string.split(' ');304 let last = words.length;305 306 while (last > 0) {307 if (stringWidth(words[last - 1]) > 0) {308 break;309 }310 311 last--;312 }313 314 if (last === words.length) {315 return string;316 }317 318 return words.slice(0, last).join(' ') + words.slice(last).join('');319};320 321const expandTabs = line => {322 if (!line.includes('\t')) {323 return line;324 }325 326 const segments = line.split('\t');327 let visible = 0;328 let expandedLine = '';329 330 for (const [index, segment] of segments.entries()) {331 expandedLine += segment;332 visible += stringWidth(segment);333 334 if (index < segments.length - 1) {335 const spaces = TAB_SIZE - (visible % TAB_SIZE);336 expandedLine += ' '.repeat(spaces);337 visible += spaces;338 }339 }340 341 return expandedLine;342};343 344// The wrap-ansi module can be invoked in either 'hard' or 'soft' wrap mode.345//346// 'hard' will never allow a string to take up more than columns characters.347//348// 'soft' allows long words to expand past the column length.349const exec = (string, columns, options = {}) => {350 if (options.trim !== false && string.trim() === '') {351 return '';352 }353 354 let returnValue = '';355 let escapeUrl;356 const activeStyles = [];357 358 const lengths = wordLengths(string);359 let rows = [''];360 361 for (const [index, word] of string.split(' ').entries()) {362 if (options.trim !== false) {363 rows[rows.length - 1] = rows.at(-1).trimStart();364 }365 366 let rowLength = stringWidth(rows.at(-1));367 368 if (index !== 0) {369 if (rowLength >= columns && (options.wordWrap === false || options.trim === false)) {370 // If we start with a new word but the current row length equals the length of the columns, add a new row371 rows.push('');372 rowLength = 0;373 }374 375 if (rowLength > 0 || options.trim === false) {376 rows[rows.length - 1] += ' ';377 rowLength++;378 }379 }380 381 // In 'hard' wrap mode, the length of a line is never allowed to extend past 'columns'382 if (options.hard && options.wordWrap !== false && lengths[index] > columns) {383 const remainingColumns = columns - rowLength;384 const breaksStartingThisLine = 1 + Math.floor((lengths[index] - remainingColumns - 1) / columns);385 const breaksStartingNextLine = Math.floor((lengths[index] - 1) / columns);386 if (breaksStartingNextLine < breaksStartingThisLine) {387 rows.push('');388 }389 390 wrapWord(rows, word, columns);391 continue;392 }393 394 if (rowLength + lengths[index] > columns && rowLength > 0 && lengths[index] > 0) {395 if (options.wordWrap === false && rowLength < columns) {396 wrapWord(rows, word, columns);397 continue;398 }399 400 rows.push('');401 }402 403 if (rowLength + lengths[index] > columns && options.wordWrap === false) {404 wrapWord(rows, word, columns);405 continue;406 }407 408 rows[rows.length - 1] += word;409 }410 411 if (options.trim !== false) {412 rows = rows.map(row => stringVisibleTrimSpacesRight(row));413 }414 415 const preString = rows.join('\n');416 const pre = getGraphemes(preString);417 418 // We need to keep a separate index as `String#slice()` works on Unicode code units, while `pre` is an array of grapheme clusters.419 let preStringIndex = 0;420 421 for (const [index, character] of pre.entries()) {422 returnValue += character;423 424 if (character === ANSI_ESCAPE && pre[index + 1] !== '\\') {425 const {groups} = ANSI_ESCAPE_REGEX.exec(preString.slice(preStringIndex)) || {groups: {}};426 if (groups.sgr !== undefined) {427 applySgrParameters(groups.sgr, activeStyles);428 } else if (groups.uri !== undefined) {429 escapeUrl = groups.uri.length === 0 ? undefined : groups.uri;430 }431 } else if (character === ANSI_ESCAPE_CSI) {432 const {groups} = ANSI_ESCAPE_CSI_REGEX.exec(preString.slice(preStringIndex)) || {groups: {}};433 if (groups.sgr !== undefined) {434 applySgrParameters(groups.sgr, activeStyles);435 }436 }437 438 if (pre[index + 1] === '\n') {439 if (escapeUrl) {440 returnValue += wrapAnsiHyperlink('');441 }442 443 returnValue += getClosingSgrSequence(activeStyles);444 } else if (character === '\n') {445 const openingStyles = [...activeStyles];446 applyLeadingSgrResets(preString.slice(preStringIndex + 1), openingStyles);447 returnValue += getOpeningSgrSequence(openingStyles);448 449 if (escapeUrl) {450 returnValue += wrapAnsiHyperlink(escapeUrl);451 }452 }453 454 preStringIndex += character.length;455 }456 457 return returnValue;458};459 460// For each newline, invoke the method separately461export default function wrapAnsi(string, columns, options) {462 return String(string)463 .normalize()464 .replaceAll('\r\n', '\n')465 .split('\n')466 .map(line => exec(expandTabs(line), columns, options))467 .join('\n');468}469 