slice-ansi
Advanced tools
Comparing version 5.0.0 to 6.0.0
210
index.js
@@ -0,105 +1,167 @@ | ||
import ansiStyles from 'ansi-styles'; | ||
import isFullwidthCodePoint from 'is-fullwidth-code-point'; | ||
import ansiStyles from 'ansi-styles'; | ||
const astralRegex = /^[\uD800-\uDBFF][\uDC00-\uDFFF]$/; | ||
// \x1b and \x9b | ||
const ESCAPES = new Set([27, 155]); | ||
const ESCAPES = [ | ||
'\u001B', | ||
'\u009B' | ||
]; | ||
const CODE_POINT_0 = '0'.codePointAt(0); | ||
const CODE_POINT_9 = '9'.codePointAt(0); | ||
const wrapAnsi = code => `${ESCAPES[0]}[${code}m`; | ||
const endCodesSet = new Set(); | ||
const endCodesMap = new Map(); | ||
for (const [start, end] of ansiStyles.codes) { | ||
endCodesSet.add(ansiStyles.color.ansi(end)); | ||
endCodesMap.set(ansiStyles.color.ansi(start), ansiStyles.color.ansi(end)); | ||
} | ||
const checkAnsi = (ansiCodes, isEscapes, endAnsiCode) => { | ||
let output = []; | ||
ansiCodes = [...ansiCodes]; | ||
function getEndCode(code) { | ||
if (endCodesSet.has(code)) { | ||
return code; | ||
} | ||
for (let ansiCode of ansiCodes) { | ||
const ansiCodeOrigin = ansiCode; | ||
if (ansiCode.includes(';')) { | ||
ansiCode = ansiCode.split(';')[0][0] + '0'; | ||
} | ||
if (endCodesMap.has(code)) { | ||
return endCodesMap.get(code); | ||
} | ||
const item = ansiStyles.codes.get(Number.parseInt(ansiCode, 10)); | ||
if (item) { | ||
const indexEscape = ansiCodes.indexOf(item.toString()); | ||
if (indexEscape === -1) { | ||
output.push(wrapAnsi(isEscapes ? item : ansiCodeOrigin)); | ||
} else { | ||
ansiCodes.splice(indexEscape, 1); | ||
} | ||
} else if (isEscapes) { | ||
output.push(wrapAnsi(0)); | ||
break; | ||
} else { | ||
output.push(wrapAnsi(ansiCodeOrigin)); | ||
} | ||
code = code.slice(2); | ||
if (code.includes(';')) { | ||
code = code[0] + '0'; | ||
} | ||
if (isEscapes) { | ||
output = output.filter((element, index) => output.indexOf(element) === index); | ||
const returnValue = ansiStyles.codes.get(Number.parseInt(code, 10)); | ||
if (returnValue) { | ||
return ansiStyles.color.ansi(returnValue); | ||
} | ||
if (endAnsiCode !== undefined) { | ||
const fistEscapeCode = wrapAnsi(ansiStyles.codes.get(Number.parseInt(endAnsiCode, 10))); | ||
// TODO: Remove the use of `.reduce` here. | ||
// eslint-disable-next-line unicorn/no-array-reduce | ||
output = output.reduce((current, next) => next === fistEscapeCode ? [next, ...current] : [...current, next], []); | ||
return ansiStyles.reset.open; | ||
} | ||
function findNumberIndex(string) { | ||
for (let index = 0; index < string.length; index++) { | ||
const codePoint = string.codePointAt(index); | ||
if (codePoint >= CODE_POINT_0 && codePoint <= CODE_POINT_9) { | ||
return index; | ||
} | ||
} | ||
return output.join(''); | ||
}; | ||
return -1; | ||
} | ||
export default function sliceAnsi(string, begin, end) { | ||
const characters = [...string]; | ||
const ansiCodes = []; | ||
function parseAnsiCode(string, offset) { | ||
string = string.slice(offset, offset + 19); | ||
const startIndex = findNumberIndex(string); | ||
if (startIndex !== -1) { | ||
let endIndex = string.indexOf('m', startIndex); | ||
if (endIndex === -1) { | ||
endIndex = string.length; | ||
} | ||
let stringEnd = typeof end === 'number' ? end : characters.length; | ||
let isInsideEscape = false; | ||
let ansiCode; | ||
let visible = 0; | ||
let output = ''; | ||
return string.slice(0, endIndex + 1); | ||
} | ||
} | ||
for (const [index, character] of characters.entries()) { | ||
let leftEscape = false; | ||
function tokenize(string, endCharacter = Number.POSITIVE_INFINITY) { | ||
const returnValue = []; | ||
if (ESCAPES.includes(character)) { | ||
const code = /\d[^m]*/.exec(string.slice(index, index + 18)); | ||
ansiCode = code && code.length > 0 ? code[0] : undefined; | ||
let index = 0; | ||
let visibleCount = 0; | ||
while (index < string.length) { | ||
const codePoint = string.codePointAt(index); | ||
if (visible < stringEnd) { | ||
isInsideEscape = true; | ||
if (ansiCode !== undefined) { | ||
ansiCodes.push(ansiCode); | ||
} | ||
if (ESCAPES.has(codePoint)) { | ||
const code = parseAnsiCode(string, index); | ||
if (code) { | ||
returnValue.push({ | ||
type: 'ansi', | ||
code, | ||
endCode: getEndCode(code), | ||
}); | ||
index += code.length; | ||
continue; | ||
} | ||
} else if (isInsideEscape && character === 'm') { | ||
isInsideEscape = false; | ||
leftEscape = true; | ||
} | ||
if (!isInsideEscape && !leftEscape) { | ||
visible++; | ||
const isFullWidth = isFullwidthCodePoint(codePoint); | ||
const character = String.fromCodePoint(codePoint); | ||
returnValue.push({ | ||
type: 'character', | ||
value: character, | ||
isFullWidth, | ||
}); | ||
index += character.length; | ||
visibleCount += isFullWidth ? 2 : character.length; | ||
if (visibleCount >= endCharacter) { | ||
break; | ||
} | ||
} | ||
if (!astralRegex.test(character) && isFullwidthCodePoint(character.codePointAt())) { | ||
visible++; | ||
return returnValue; | ||
} | ||
if (typeof end !== 'number') { | ||
stringEnd++; | ||
} | ||
function reduceAnsiCodes(codes) { | ||
let returnValue = []; | ||
for (const code of codes) { | ||
if (code.code === ansiStyles.reset.open) { | ||
// Reset code, disable all codes | ||
returnValue = []; | ||
} else if (endCodesSet.has(code.code)) { | ||
// This is an end code, disable all matching start codes | ||
returnValue = returnValue.filter(returnValueCode => returnValueCode.endCode !== code.code); | ||
} else { | ||
// This is a start code. Disable all styles this "overrides", then enable it | ||
returnValue = returnValue.filter(returnValueCode => returnValueCode.endCode !== code.endCode); | ||
returnValue.push(code); | ||
} | ||
} | ||
if (visible > begin && visible <= stringEnd) { | ||
output += character; | ||
} else if (visible === begin && !isInsideEscape && ansiCode !== undefined) { | ||
output = checkAnsi(ansiCodes); | ||
} else if (visible >= stringEnd) { | ||
output += checkAnsi(ansiCodes, true, ansiCode); | ||
return returnValue; | ||
} | ||
function undoAnsiCodes(codes) { | ||
const reduced = reduceAnsiCodes(codes); | ||
const endCodes = reduced.map(({endCode}) => endCode); | ||
return endCodes.reverse().join(''); | ||
} | ||
export default function sliceAnsi(string, start, end) { | ||
const tokens = tokenize(string, end); | ||
let activeCodes = []; | ||
let position = 0; | ||
let returnValue = ''; | ||
let include = false; | ||
for (const token of tokens) { | ||
if (end !== undefined && position >= end) { | ||
break; | ||
} | ||
if (token.type === 'ansi') { | ||
activeCodes.push(token); | ||
if (include) { | ||
returnValue += token.code; | ||
} | ||
} else { | ||
// Character | ||
if (!include && position >= start) { | ||
include = true; | ||
// Simplify active codes | ||
activeCodes = reduceAnsiCodes(activeCodes); | ||
returnValue = activeCodes.map(({code}) => code).join(''); | ||
} | ||
if (include) { | ||
returnValue += token.value; | ||
} | ||
position += token.isFullWidth ? 2 : token.value.length; | ||
} | ||
} | ||
return output; | ||
// Disable active codes at the end | ||
returnValue += undoAnsiCodes(activeCodes); | ||
return returnValue; | ||
} |
{ | ||
"name": "slice-ansi", | ||
"version": "5.0.0", | ||
"version": "6.0.0", | ||
"description": "Slice a string with ANSI escape codes", | ||
@@ -11,3 +11,3 @@ "license": "MIT", | ||
"engines": { | ||
"node": ">=12" | ||
"node": ">=14.16" | ||
}, | ||
@@ -44,12 +44,12 @@ "scripts": { | ||
"dependencies": { | ||
"ansi-styles": "^6.0.0", | ||
"ansi-styles": "^6.2.1", | ||
"is-fullwidth-code-point": "^4.0.0" | ||
}, | ||
"devDependencies": { | ||
"ava": "^3.15.0", | ||
"chalk": "^4.1.0", | ||
"random-item": "^4.0.0", | ||
"strip-ansi": "^7.0.0", | ||
"xo": "^0.38.2" | ||
"ava": "^5.2.0", | ||
"chalk": "^5.2.0", | ||
"random-item": "^4.0.1", | ||
"strip-ansi": "^7.0.1", | ||
"xo": "^0.53.1" | ||
} | ||
} |
@@ -7,5 +7,5 @@ # slice-ansi [![XO: Linted](https://img.shields.io/badge/xo-linted-blue.svg)](https://github.com/xojs/xo) | ||
```sh | ||
npm install slice-ansi | ||
``` | ||
$ npm install slice-ansi | ||
``` | ||
@@ -26,3 +26,3 @@ ## Usage | ||
### sliceAnsi(string, beginSlice, endSlice?) | ||
### sliceAnsi(string, startSlice, endSlice?) | ||
@@ -35,7 +35,7 @@ #### string | ||
#### beginSlice | ||
#### startSlice | ||
Type: `number` | ||
Zero-based index at which to begin the slice. | ||
Zero-based index at which to start the slice. | ||
@@ -58,13 +58,1 @@ #### endSlice | ||
- [Josh Junon](https://github.com/qix-) | ||
--- | ||
<div align="center"> | ||
<b> | ||
<a href="https://tidelift.com/subscription/pkg/npm-slice_ansi?utm_source=npm-slice-ansi&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a> | ||
</b> | ||
<br> | ||
<sub> | ||
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies. | ||
</sub> | ||
</div> |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
7163
137
55
1
Updatedansi-styles@^6.2.1