UNPKG

@visulima/string

Version:

Functions for manipulating strings.

551 lines (541 loc) 17 kB
'use strict'; Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } }); const constants = require('./constants-nihvIvk5.cjs'); const getStringWidth = require('../get-string-width.cjs'); var __defProp$5 = Object.defineProperty; var __name$5 = (target, value) => __defProp$5(target, "name", { value, configurable: true }); class AnsiStateTracker { static { __name$5(this, "AnsiStateTracker"); } activeForeground = null; activeBackground = null; // Track other formatting (bold, italic, etc.) activeFormatting = []; /** * Processes an escape sequence and updates the internal state * @param sequence - The escape sequence to process */ // eslint-disable-next-line sonarjs/cognitive-complexity processEscape(sequence) { const match = /\x1B\[(\d+)m/.exec(sequence); if (!match) { return; } const code = Number.parseInt(match[1], 10); switch (code) { case 0: { this.activeForeground = null; this.activeBackground = null; this.activeFormatting = []; break; } case 39: { this.activeForeground = null; break; } case 49: { this.activeBackground = null; break; } default: { if (code >= 30 && code <= 37 || code >= 90 && code <= 97) { this.activeForeground = sequence; } else if (code >= 40 && code <= 47 || code >= 100 && code <= 107) { this.activeBackground = sequence; } else if ([1, 2, 3, 4, 7, 8, 9].includes(code)) { this.activeFormatting.push(sequence); } else if ([22, 23, 24, 27, 28, 29].includes(code)) { const formatResetMap = { 22: "[1m", // Reset bold 23: "[3m", // Reset italic 24: "[4m", // Reset underline 27: "[7m", // Reset inverse 28: "[8m", // Reset hidden 29: "[9m" // Reset strikethrough }; const formatToRemove = formatResetMap[code]; if (formatToRemove) { this.activeFormatting = this.activeFormatting.filter((fmt) => !fmt.includes(formatToRemove)); } } } } } /** * Gets all active escape sequences to apply * @returns String with all active escapes */ getActiveEscapes() { return [this.activeBackground, this.activeForeground, ...this.activeFormatting].filter(Boolean).join(""); } } var __defProp$4 = Object.defineProperty; var __name$4 = (target, value) => __defProp$4(target, "name", { value, configurable: true }); const checkEscapeSequence = /* @__PURE__ */ __name$4((chars, index) => { if (!constants.ESCAPES.has(chars[index])) { return { isInsideEscape: false, isInsideLinkEscape: false }; } const isInsideEscape = true; const possibleLink = chars.slice(index + 1, index + 1 + constants.ANSI_ESCAPE_LINK.length).join(""); const isInsideLinkEscape = possibleLink === constants.ANSI_ESCAPE_LINK; return { isInsideEscape, isInsideLinkEscape }; }, "checkEscapeSequence"); const processAnsiString = /* @__PURE__ */ __name$4((string, options = {}) => { const stateTracker = new AnsiStateTracker(); let currentText = ""; let isInsideEscape = false; let isInsideLinkEscape = false; let escapeBuffer = ""; let currentUrl = ""; let isInHyperlink = false; const chars = [...string]; for (let index = 0; index < chars.length; index++) { const character = chars[index]; if (character && constants.ESCAPES.has(character)) { if (currentText) { const width2 = options.getWidth?.(currentText) ?? 0; const segment2 = { isEscapeSequence: false, isGrapheme: true, text: currentText, width: width2 }; if (isInHyperlink) { segment2.isHyperlink = true; segment2.hyperlinkUrl = currentUrl; } if (options.onSegment?.(segment2, stateTracker) === false) { return; } currentText = ""; } isInsideEscape = true; escapeBuffer = character; const escapeInfo = checkEscapeSequence(chars, index); isInsideLinkEscape = escapeInfo.isInsideLinkEscape; if (isInsideLinkEscape) { let urlEnd = index + 1; currentUrl = ""; while (urlEnd < chars.length) { const nextChar = chars[urlEnd]; if (nextChar === constants.ANSI_ESCAPE_BELL) { break; } currentUrl += nextChar; urlEnd++; } currentUrl = currentUrl.slice(4); const segment2 = { hyperlinkUrl: currentUrl, isEscapeSequence: true, isGrapheme: false, isHyperlink: true, isHyperlinkStart: true, width: 0 }; if (options.onSegment?.(segment2, stateTracker) === false) { return; } index = urlEnd; isInHyperlink = true; isInsideEscape = false; isInsideLinkEscape = false; escapeBuffer = ""; continue; } if (index + 1 < chars.length && chars[index + 1] === "\\" && isInHyperlink) { const segment2 = { isEscapeSequence: true, isGrapheme: false, isHyperlink: true, isHyperlinkEnd: true, width: 0 }; if (options.onSegment?.(segment2, stateTracker) === false) { return; } isInHyperlink = false; currentUrl = ""; index++; isInsideEscape = false; escapeBuffer = ""; continue; } } if (isInsideEscape) { if (escapeBuffer !== character) { escapeBuffer += character; } if (character === constants.ANSI_SGR_TERMINATOR) { isInsideEscape = false; stateTracker.processEscape(escapeBuffer); const segment2 = { isEscapeSequence: true, isGrapheme: false, text: escapeBuffer, width: 0 }; if (options.onSegment?.(segment2, stateTracker) === false) { return; } escapeBuffer = ""; } continue; } currentText += character; const width = options.getWidth?.(currentText) ?? 0; const segment = { isEscapeSequence: false, isGrapheme: true, text: currentText, width }; if (isInHyperlink) { segment.isHyperlink = true; segment.hyperlinkUrl = currentUrl; } if (options.onSegment?.(segment, stateTracker) === false) { return; } currentText = ""; } if (currentText) { const width = options.getWidth?.(currentText) ?? 0; const segment = { isEscapeSequence: false, isGrapheme: true, text: currentText, width }; if (isInHyperlink) { segment.isHyperlink = true; segment.hyperlinkUrl = currentUrl; } options.onSegment?.(segment, stateTracker); } if (escapeBuffer) { const segment = { isEscapeSequence: true, isGrapheme: false, text: escapeBuffer, width: 0 }; options.onSegment?.(segment, stateTracker); } }, "processAnsiString"); var __defProp$3 = Object.defineProperty; var __name$3 = (target, value) => __defProp$3(target, "name", { value, configurable: true }); const wrapAnsiCode = /* @__PURE__ */ __name$3((code) => { const escapeChar = constants.ESCAPES.values().next().value; return `${escapeChar}${constants.ANSI_CSI}${code}${constants.ANSI_SGR_TERMINATOR}`; }, "wrapAnsiCode"); var __defProp$2 = Object.defineProperty; var __name$2 = (target, value) => __defProp$2(target, "name", { value, configurable: true }); const wrapAnsiHyperlink = /* @__PURE__ */ __name$2((url) => { const escapeChar = constants.ESCAPES.values().next().value; return `${escapeChar}${constants.ANSI_ESCAPE_LINK}${url}${constants.ANSI_ESCAPE_BELL}`; }, "wrapAnsiHyperlink"); var __defProp$1 = Object.defineProperty; var __name$1 = (target, value) => __defProp$1(target, "name", { value, configurable: true }); const preserveAnsi = /* @__PURE__ */ __name$1((rawLines) => { if (rawLines.length === 0) { return ""; } if (rawLines.length === 1) { return rawLines[0]; } let returnValue = ""; let escapeCode; let escapeUrl; const preString = rawLines.join("\n"); const pre = [...preString]; let preStringIndex = 0; for (const [index, character] of pre.entries()) { returnValue += character; if (constants.ESCAPES.has(character)) { const match = constants.RE_ESCAPE_PATTERN.exec(preString.slice(preStringIndex)); const groups = match?.groups ?? {}; if (groups.code !== void 0) { const code2 = Number.parseFloat(groups.code); escapeCode = code2 === constants.END_CODE ? void 0 : code2; } else if (groups.uri !== void 0) { escapeUrl = groups.uri.length === 0 ? void 0 : groups.uri; } } const code = constants.ANSI_RESET_CODES.get(Number(escapeCode)); if (pre[index + 1] === "\n") { if (escapeUrl) { returnValue += wrapAnsiHyperlink(""); } if (escapeCode && code) { returnValue += wrapAnsiCode(code); } } else if (character === "\n") { if (escapeCode && code) { returnValue += wrapAnsiCode(escapeCode); } if (escapeUrl) { returnValue += wrapAnsiHyperlink(escapeUrl); } } preStringIndex += character.length; } return returnValue; }, "preserveAnsi"); var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); const resetAnsiAtLineBreak = /* @__PURE__ */ __name((currentLine) => { if (!currentLine.includes("\x1B")) { return currentLine; } let result = currentLine; if (currentLine.includes("\x1B[30m")) { result += "\x1B[39m"; } if (currentLine.includes("\x1B[42m")) { result += "\x1B[49m"; } return result; }, "resetAnsiAtLineBreak"); const stringVisibleTrimSpacesRight = /* @__PURE__ */ __name((string) => { const words = string.split(" "); let last = words.length; while (last > 0 && getStringWidth.getStringWidth(words[last - 1]) === 0) { last--; } if (last === words.length) { return string; } return words.slice(0, last).join(" ") + words.slice(last).join(""); }, "stringVisibleTrimSpacesRight"); const wrapWithBreakAtWidth = /* @__PURE__ */ __name((string, width, trim) => { if (string.length === 0) { return [""]; } if (width <= 0) { return [string]; } const rows = []; let currentLine = ""; let currentWidth = 0; const ansiTracker = new AnsiStateTracker(); let isInsideEscape = false; let isInsideLinkEscape = false; let escapeBuffer = ""; for (let index = 0; index < string.length; index++) { const char = string[index]; if (constants.ESCAPES.has(char)) { isInsideEscape = true; escapeBuffer = char; currentLine += char; const escapeInfo = checkEscapeSequence([...string], index); isInsideLinkEscape = escapeInfo.isInsideLinkEscape; continue; } if (isInsideEscape) { escapeBuffer += char; currentLine += char; if (isInsideLinkEscape) { if (char === constants.ANSI_ESCAPE_BELL) { isInsideEscape = isInsideLinkEscape = false; } } else if (char === constants.ANSI_SGR_TERMINATOR) { isInsideEscape = false; ansiTracker.processEscape(escapeBuffer); } continue; } const charWidth = getStringWidth.getStringWidth(char); const isSpace = char === " "; if (charWidth === 0) { currentLine += char; continue; } if (currentWidth + charWidth > width) { if (currentLine) { rows.push(resetAnsiAtLineBreak(currentLine)); } currentLine = ansiTracker.getActiveEscapes(); currentWidth = 0; if (isSpace && trim) { while (index < string.length && string[index] === " ") { index++; if (index >= string.length) { break; } } if (index < string.length) { index--; } continue; } } currentLine += char; currentWidth += charWidth; if (currentWidth === width && index < string.length - 1) { rows.push(resetAnsiAtLineBreak(currentLine)); currentLine = ansiTracker.getActiveEscapes(); currentWidth = 0; if (index + 1 < string.length && string[index + 1] === " " && trim) { index++; while (index < string.length && string[index] === " ") { index++; } index--; } } } if (currentLine) { rows.push(currentLine); } return trim ? rows.map((element) => stringVisibleTrimSpacesRight(element)) : rows; }, "wrapWithBreakAtWidth"); const wrapCharByChar = /* @__PURE__ */ __name((string, width, trim) => { if (string.length === 0) { return []; } const inputToProcess = trim ? string.trim() : string; if (inputToProcess.length === 0) { return []; } const rows = []; let currentLine = ""; let currentWidth = 0; processAnsiString(inputToProcess, { getWidth: getStringWidth.getStringWidth, onSegment: /* @__PURE__ */ __name((segment, stateTracker) => { if (segment.isEscapeSequence) { currentLine += segment.text; } else { const isSpace = segment.text === " "; if (segment.width === 0) { currentLine += segment.text; return true; } if (currentWidth + segment.width > width) { if (currentLine) { rows.push(resetAnsiAtLineBreak(currentLine)); } currentLine = stateTracker.getActiveEscapes(); currentWidth = 0; if (isSpace) { if (trim) { return true; } rows.push(stateTracker.getActiveEscapes() + segment.text); return true; } } currentLine += segment.text; currentWidth += segment.width; } return true; }, "onSegment") }); if (currentLine) { rows.push(currentLine); } return trim ? rows.map((row) => stringVisibleTrimSpacesRight(row)) : rows; }, "wrapCharByChar"); const wrapWithWordBoundaries = /* @__PURE__ */ __name((string, width, trim) => { if (string.length === 0) { return []; } const inputToProcess = trim ? string.trim() : string; if (inputToProcess.length === 0) { return []; } const tokens = inputToProcess.split(/(?=\s)|(?<=\s)/); const rows = []; let currentLine = ""; let currentWidth = 0; let index = 0; while (index < tokens.length) { const token = tokens[index]; const isSpace = /^\s+$/.test(token); const tokenVisibleWidth = getStringWidth.getStringWidth(token); if (token.length === 0) { index++; continue; } if (trim && isSpace && currentWidth === 0) { index++; continue; } if (currentWidth + tokenVisibleWidth > width && currentWidth > 0) { if (trim) { rows.push(stringVisibleTrimSpacesRight(currentLine)); } else { rows.push(currentLine); } currentLine = ""; currentWidth = 0; continue; } currentLine += token; currentWidth += tokenVisibleWidth; index++; } if (currentLine) { if (trim) { rows.push(stringVisibleTrimSpacesRight(currentLine)); } else { rows.push(currentLine); } } return rows; }, "wrapWithWordBoundaries"); const WrapMode = { /** * Breaks words at character boundaries to fit the width */ BREAK_AT_CHARACTERS: "BREAK_AT_CHARACTERS", /** * Preserves word boundaries, words are kept intact even if they exceed width */ PRESERVE_WORDS: "PRESERVE_WORDS", /** * Enforces strict adherence to the width limit by breaking at exact width */ STRICT_WIDTH: "STRICT_WIDTH" }; const wordWrap = /* @__PURE__ */ __name((string, options = {}) => { const { removeZeroWidthCharacters = true, trim = true, width = 80, wrapMode = WrapMode.PRESERVE_WORDS } = options; if (trim && string.trim() === "") { return ""; } let normalizedString = String(string).normalize("NFC").replaceAll("\r\n", "\n"); if (removeZeroWidthCharacters) { normalizedString = normalizedString.replaceAll(constants.RE_ZERO_WIDTH, ""); } const result = normalizedString.split("\n").map((line) => { if (trim && line.trim() === "") { return ""; } let wrappedLines; switch (wrapMode) { case WrapMode.STRICT_WIDTH: { wrappedLines = wrapWithBreakAtWidth(line, width, trim); break; } case WrapMode.BREAK_AT_CHARACTERS: { wrappedLines = wrapCharByChar(line, width, trim); break; } default: { wrappedLines = wrapWithWordBoundaries(line, width, trim); } } return preserveAnsi(wrappedLines); }); return result.join("\n"); }, "wordWrap"); exports.WrapMode = WrapMode; exports.wordWrap = wordWrap;