const arrayUniq = require('array-uniq') const ansiRegex = require('ansi-regex') const superSplit = require('super-split') const stripAnsi = require('strip-ansi') const ansiTags = require('./ansi-seqs-to-ansi-tags') const decorators = require('./ansi-tags-to-decorator-names') const meassureTextArea = plainText => { const lines = plainText.split('\n') const rows = lines.length let columns = 0 lines.forEach(line => { const len = line.length if (len > columns) { columns = len } }) return {columns, rows} } // Atomize // Splits text into "words" by sticky delimiters [ANSI Escape Seq, \n] // Eg: words = ['\u001b[37m', 'Line 1', '\n', 'Line 2', '\u001b[39m'] const atomize = text => { const ansies = arrayUniq(text.match(ansiRegex())) const words = superSplit(text, ansies.concat(['\n'])) return {ansies, words} } const parse = ansi => { const plainText = stripAnsi(ansi) const textArea = meassureTextArea(plainText) const result = { raw: ansi, plainText, textArea, chunks: [] } const { ansies, words } = atomize(ansi) const styleStack = { foregroundColor: [], backgroundColor: [], boldDim: [] } const getForegroundColor = () => { if (styleStack.foregroundColor.length > 0) { return styleStack.foregroundColor[styleStack.foregroundColor.length - 1] } return false } const getBackgroundColor = () => { if (styleStack.backgroundColor.length > 0) { return styleStack.backgroundColor[styleStack.backgroundColor.length - 1] } return false } const getDim = () => { return styleStack.boldDim.includes('dim') } const getBold = () => { return styleStack.boldDim.includes('bold') } const styleState = { italic: false, underline: false, inverse: false, hidden: false, strikethrough: false } let x = 0 let y = 0 let nAnsi = 0 let nPlain = 0 const bundle = (type, value) => { const chunk = { type, value, position: { x, y, n: nPlain, raw: nAnsi } } if (type === 'text' || type === 'ansi') { const style = {} const foregroundColor = getForegroundColor() const backgroundColor = getBackgroundColor() const dim = getDim() const bold = getBold() if (foregroundColor) { style.foregroundColor = foregroundColor } if (backgroundColor) { style.backgroundColor = backgroundColor } if (dim) { style.dim = dim } if (bold) { style.bold = bold } if (styleState.italic) { style.italic = true } if (styleState.underline) { style.underline = true } if (styleState.inverse) { style.inverse = true } if (styleState.strikethrough) { style.strikethrough = true } chunk.style = style } return chunk } words.forEach(word => { // Newline character if (word === '\n') { const chunk = bundle('newline', '\n') result.chunks.push(chunk) x = 0 y += 1 nAnsi += 1 nPlain += 1 return } // Text characters if (ansies.includes(word) === false) { const chunk = bundle('text', word) result.chunks.push(chunk) x += word.length nAnsi += word.length nPlain += word.length return } // ANSI Escape characters const ansiTag = ansiTags[word] const decorator = decorators[ansiTag] const color = ansiTag if (decorator === 'foregroundColorOpen') { styleStack.foregroundColor.push(color) } if (decorator === 'foregroundColorClose') { styleStack.foregroundColor.pop() } if (decorator === 'backgroundColorOpen') { styleStack.backgroundColor.push(color) } if (decorator === 'backgroundColorClose') { styleStack.backgroundColor.pop() } if (decorator === 'boldOpen') { styleStack.boldDim.push('bold') } if (decorator === 'dimOpen') { styleStack.boldDim.push('dim') } if (decorator === 'boldDimClose') { styleStack.boldDim.pop() } if (decorator === 'italicOpen') { styleState.italic = true } if (decorator === 'italicClose') { styleState.italic = false } if (decorator === 'underlineOpen') { styleState.underline = true } if (decorator === 'underlineClose') { styleState.underline = false } if (decorator === 'inverseOpen') { styleState.inverse = true } if (decorator === 'inverseClose') { styleState.inverse = false } if (decorator === 'strikethroughOpen') { styleState.strikethrough = true } if (decorator === 'strikethroughClose') { styleState.strikethrough = false } if (decorator === 'reset') { styleState.strikethrough = false styleState.inverse = false styleState.italic = false styleStack.boldDim = [] styleStack.backgroundColor = [] styleStack.foregroundColor = [] } const chunk = bundle('ansi', { tag: ansiTag, ansi: word, decorator }) result.chunks.push(chunk) nAnsi += word.length }) return result } module.exports = parse