UNPKG

@visulima/string

Version:

Functions for manipulating strings.

420 lines (415 loc) 14 kB
'use strict'; Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } }); const constants = require('./packem_shared/constants-nihvIvk5.cjs'); const getStringWidth = require('./get-string-width.cjs'); const LRUCache = require('./packem_shared/LRUCache-Crb-m7cw.cjs'); var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); const segmentCache = new LRUCache(100); const defaultSegmenter = new Intl.Segmenter("en", { granularity: "grapheme" }); const ANSI_RESET_CODES = /* @__PURE__ */ new Set([ "0", // Full reset "22", // Bold reset "23", // Italic reset "24", // Underline reset "27", // Inverse reset "28", // Hidden reset "29", // Strikethrough reset "39", // Foreground color reset "49" // Background color reset ]); const STYLE_CODES = /* @__PURE__ */ new Set([ "1", // Bold "3", // Italic "4", // Underline "7", // Inverse "8", // Hidden "9" // Strikethrough ]); const FORMAT_RESET_CODES = /* @__PURE__ */ new Map([ ["22", "1"], // Bold reset ["23", "3"], // Italic reset ["24", "4"], // Underline reset ["27", "7"], // Inverse reset ["28", "8"], // Hidden reset ["29", "9"] // Strikethrough reset ]); const isForegroundColor = /* @__PURE__ */ __name((code) => code >= "30" && code <= "37" || code >= "90" && code <= "97" || code.startsWith("38;"), "isForegroundColor"); const isBackgroundColor = /* @__PURE__ */ __name((code) => code >= "40" && code <= "47" || code >= "100" && code <= "107" || code.startsWith("48;"), "isBackgroundColor"); const FORMAT_STYLES = [ { close: "\x1B[22m", open: "\x1B[1m" }, // Bold { close: "\x1B[23m", open: "\x1B[3m" }, // Italic { close: "\x1B[24m", open: "\x1B[4m" }, // Underline { close: "\x1B[27m", open: "\x1B[7m" }, // Inverse { close: "\x1B[28m", open: "\x1B[8m" }, // Hidden { close: "\x1B[29m", open: "\x1B[9m" } // Strikethrough ]; const findFirstPositionOfAny = /* @__PURE__ */ __name((input, substrings) => { let firstPos = -1; for (const substring of substrings) { const pos = input.indexOf(substring); if (pos >= 0 && (firstPos === -1 || pos < firstPos)) { firstPos = pos; } } return firstPos; }, "findFirstPositionOfAny"); const processIntoStyledSegments = /* @__PURE__ */ __name((input, options) => { if (!input.includes("\x1B")) { return [ { after: "", before: "", content: input, visibleLength: getStringWidth.getStringWidth(input, options.width) } ]; } const cachedResult = segmentCache.get(input); if (cachedResult) { return cachedResult; } const parts = []; const matches = []; let lastIndex = 0; let match; constants.RE_ANSI.lastIndex = 0; while ((match = constants.RE_ANSI.exec(input)) !== null) { const matchedText = match[0]; parts.push(input.slice(lastIndex, match.index)); matches.push(matchedText); lastIndex = match.index + matchedText.length; } if (lastIndex < input.length) { parts.push(input.slice(lastIndex)); } const closingSequences = []; for (const ansi of matches) { if (ansi === "\x1B[0m" || ansi === "\x1B]8;;\x07") { closingSequences.push(ansi); continue; } if (ansi.startsWith("\x1B[") && ansi.endsWith("m")) { const code = ansi.slice(2, -1); if (ANSI_RESET_CODES.has(code)) { closingSequences.push(ansi); continue; } } } const activeStylesSet = /* @__PURE__ */ new Set(); const segments = []; for (const [index, text] of parts.entries()) { if (index > 0 && matches[index - 1]) { const ansi = matches[index - 1]; if (ansi === "\x1B[0m") { activeStylesSet.clear(); } else if (ansi === "\x1B]8;;\x07") { for (const style of activeStylesSet) { if (style.startsWith("\x1B]8;;") && !style.endsWith("\x1B]8;;\x07")) { activeStylesSet.delete(style); break; } } } else if (ansi.startsWith("\x1B[") && ansi.endsWith("m")) { const code = ansi.slice(2, -1); if (code === "39") { for (const style of activeStylesSet) { if (style.startsWith("\x1B[") && style.endsWith("m")) { const styleCode = style.slice(2, -1); if (isForegroundColor(styleCode)) { activeStylesSet.delete(style); break; } } } } else if (code === "49") { for (const style of activeStylesSet) { if (style.startsWith("\x1B[") && style.endsWith("m")) { const styleCode = style.slice(2, -1); if (isBackgroundColor(styleCode)) { activeStylesSet.delete(style); break; } } } } else if (FORMAT_RESET_CODES.has(code)) { const targetCode = FORMAT_RESET_CODES.get(code); for (const style of activeStylesSet) { if (style === `\x1B[${targetCode}m`) { activeStylesSet.delete(style); break; } } } else { if (isForegroundColor(code)) { for (const style of activeStylesSet) { if (style.startsWith("\x1B[") && style.endsWith("m")) { const styleCode = style.slice(2, -1); if (isForegroundColor(styleCode)) { activeStylesSet.delete(style); break; } } } } else if (isBackgroundColor(code)) { for (const style of activeStylesSet) { if (style.startsWith("\x1B[") && style.endsWith("m")) { const styleCode = style.slice(2, -1); if (isBackgroundColor(styleCode)) { activeStylesSet.delete(style); break; } } } } else if (STYLE_CODES.has(code)) { const targetStyle = `\x1B[${code}m`; if (activeStylesSet.has(targetStyle)) { activeStylesSet.delete(targetStyle); } } activeStylesSet.add(ansi); } } else if (ansi.startsWith("\x1B]8;;") && !ansi.endsWith("\x1B]8;;\x07")) { for (const style of activeStylesSet) { if (style.startsWith("\x1B]8;;") && !style.endsWith("\x1B]8;;\x07")) { activeStylesSet.delete(style); break; } } activeStylesSet.add(ansi); } } if (text === "") { continue; } let closingSequence = ""; if (activeStylesSet.size > 0) { if (closingSequences.includes("\x1B[0m")) { closingSequence = "\x1B[0m"; } else { const closingParts = []; let needsHyperlinkClose = false; for (const style of activeStylesSet) { if (style.startsWith("\x1B]8;;") && !style.endsWith("\x1B]8;;\x07")) { needsHyperlinkClose = true; break; } } if (needsHyperlinkClose && closingSequences.includes("\x1B]8;;\x07")) { closingParts.push("\x1B]8;;\x07"); } let needsForegroundClose = false; let needsBackgroundClose = false; for (const style of activeStylesSet) { if (style.startsWith("\x1B[") && style.endsWith("m")) { const styleCode = style.slice(2, -1); if (isForegroundColor(styleCode)) { needsForegroundClose = true; } else if (isBackgroundColor(styleCode)) { needsBackgroundClose = true; } } } if (needsForegroundClose && needsBackgroundClose) { const fgOpenPos = findFirstPositionOfAny(input, [ "\x1B[3", // Basic colors 30-37 "\x1B[9", // Bright colors 90-97 "\x1B[38;" // RGB/256 colors ]); const bgOpenPos = findFirstPositionOfAny(input, [ "\x1B[4", // Basic colors 40-47 "\x1B[10", // Bright colors 100-107 "\x1B[48;" // RGB/256 colors ]); if (fgOpenPos >= 0 && bgOpenPos >= 0) { if (fgOpenPos > bgOpenPos) { closingParts.push("\x1B[39m", "\x1B[49m"); } else { closingParts.push("\x1B[49m", "\x1B[39m"); } } else { closingParts.push("\x1B[39m", "\x1B[49m"); } } else { if (needsForegroundClose) { closingParts.push("\x1B[39m"); } if (needsBackgroundClose) { closingParts.push("\x1B[49m"); } } if (needsForegroundClose && needsBackgroundClose && input.includes("\x1B[39m") && input.includes("\x1B[49m")) { if (input.indexOf("\x1B[39m") < input.indexOf("\x1B[49m")) { closingParts.length -= needsForegroundClose && needsBackgroundClose ? 2 : needsForegroundClose || needsBackgroundClose ? 1 : 0; closingParts.push("\x1B[39m", "\x1B[49m"); } else if (input.indexOf("\x1B[49m") < input.indexOf("\x1B[39m")) { closingParts.length -= needsForegroundClose && needsBackgroundClose ? 2 : needsForegroundClose || needsBackgroundClose ? 1 : 0; closingParts.push("\x1B[49m", "\x1B[39m"); } } const activeFormatStyles = FORMAT_STYLES.filter((style) => activeStylesSet.has(style.open)).map((style) => { return { position: input.indexOf(style.open), style }; }).filter((item) => item.position >= 0).sort((a, b) => b.position - a.position); for (const item of activeFormatStyles) { if (closingSequences.includes(item.style.close)) { closingParts.push(item.style.close); } } closingSequence = closingParts.join(""); } } const activeStylesString = [...activeStylesSet].join(""); segments.push({ after: closingSequence, before: activeStylesString, content: text, visibleLength: getStringWidth.getStringWidth(text, { fullWidth: 1, wideWidth: 1 }) }); } segmentCache.set(input, segments); return segments; }, "processIntoStyledSegments"); const fastSlice = /* @__PURE__ */ __name((inputString, startIndex, endIndex, options) => { const graphemes = [...options.segmenter.segment(inputString)]; const result = []; let currentWidth = 0; let startFound = false; for (const grapheme of graphemes) { const graphemeWidth = getStringWidth.getStringWidth(grapheme.segment, options.width); const positionBefore = currentWidth; const positionAfter = currentWidth + graphemeWidth; if (!startFound) { if (positionBefore < startIndex) { currentWidth = positionAfter; continue; } else { startFound = true; } } if (positionBefore >= endIndex) { break; } if (positionAfter > endIndex) { break; } result.push(grapheme.segment); currentWidth = positionAfter; } return result.join(""); }, "fastSlice"); const slice = /* @__PURE__ */ __name((inputString, startIndex = 0, endIndex = Number.MAX_SAFE_INTEGER, options = {}) => { const config = { segmenter: defaultSegmenter, ...options }; if (startIndex >= endIndex || inputString === "") { return ""; } const inputStringWidth = getStringWidth.getStringWidth(inputString, config.width); if (startIndex === 0 && endIndex >= inputStringWidth) { return inputString; } if (startIndex < 0 || endIndex < 0) { throw new RangeError("Negative indices aren't supported"); } if (!inputString.includes("\x1B")) { if (/^[\u0000-\u007F]*$/.test(inputString)) { return inputString.slice(startIndex, endIndex); } return fastSlice(inputString, startIndex, endIndex, config); } constants.RE_VALID_ANSI_PAIRS.lastIndex = 0; constants.RE_VALID_HYPERLINKS.lastIndex = 0; if (!constants.RE_VALID_ANSI_PAIRS.test(inputString) && !constants.RE_VALID_HYPERLINKS.test(inputString)) { return inputString; } const segments = processIntoStyledSegments(inputString, config); if (segments.length === 0) { return ""; } let currentPos = 0; const visibleSegments = []; for (const [index, segment] of segments.entries()) { const segmentStart = currentPos; const segmentEnd = currentPos + segment.visibleLength; if (segmentEnd > startIndex && segmentStart < endIndex) { const visibleStart = Math.max(0, startIndex - segmentStart); const visibleEnd = Math.min(segment.visibleLength, endIndex - segmentStart); visibleSegments.push({ end: visibleEnd, index, segment, start: visibleStart }); } currentPos = segmentEnd; } if (visibleSegments.length === 0) { return ""; } const resultParts = []; const firstSegment = visibleSegments[0].segment; resultParts.push(firstSegment.before); for (let index = 0; index < visibleSegments.length; index++) { const { end, segment, start } = visibleSegments[index]; if (start === 0 && end === segment.visibleLength) { resultParts.push(segment.content); } else { const graphemes = [...config.segmenter.segment(segment.content)]; resultParts.push( graphemes.slice(start, end).map((entry) => entry.segment).join("") ); } if (index < visibleSegments.length - 1) { const nextSegment = visibleSegments[index + 1].segment; const segmentAfter = segment.after; const nextBefore = nextSegment.before; if (segmentAfter !== nextBefore && (segmentAfter || nextBefore)) { resultParts.push(segmentAfter, nextBefore); } } } resultParts.push(visibleSegments.at(-1).segment.after); return resultParts.join(""); }, "slice"); exports.slice = slice;