Primeiro commit do projeto Angular
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
Slice a string with [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors_and_Styles)
|
||||
|
||||
@param string - A string with ANSI escape codes. Like one styled by [`chalk`](https://github.com/chalk/chalk).
|
||||
@param startSlice - Zero-based visible-column index at which to start the slice. Grapheme clusters are kept intact.
|
||||
@param endSlice - Zero-based visible-column index at which to end the slice.
|
||||
|
||||
@example
|
||||
```
|
||||
import chalk from 'chalk';
|
||||
import sliceAnsi from 'slice-ansi';
|
||||
|
||||
const string = 'The quick brown ' + chalk.red('fox jumped over ') +
|
||||
'the lazy ' + chalk.green('dog and then ran away with the unicorn.');
|
||||
|
||||
console.log(sliceAnsi(string, 20, 30));
|
||||
```
|
||||
*/
|
||||
export default function sliceAnsi(string: string, startSlice: number, endSlice?: number): string;
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
import tokenizeAnsi from './tokenize-ansi.js';
|
||||
|
||||
function applySgrFragments(activeStyles, fragments) {
|
||||
for (const fragment of fragments) {
|
||||
switch (fragment.type) {
|
||||
case 'reset': {
|
||||
activeStyles.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'end': {
|
||||
activeStyles.delete(fragment.endCode);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'start': {
|
||||
activeStyles.delete(fragment.endCode);
|
||||
activeStyles.set(fragment.endCode, fragment.code);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return activeStyles;
|
||||
}
|
||||
|
||||
function undoAnsiCodes(activeStyles) {
|
||||
return [...activeStyles.keys()].reverse().join('');
|
||||
}
|
||||
|
||||
function closeHyperlink(hyperlinkToken) {
|
||||
return `${hyperlinkToken.closePrefix}${hyperlinkToken.terminator}`;
|
||||
}
|
||||
|
||||
function shouldIncludeSgrAfterEnd(token, activeStyles) {
|
||||
let hasStartFragment = false;
|
||||
let hasClosingEffect = false;
|
||||
|
||||
for (const fragment of token.fragments) {
|
||||
if (fragment.type === 'start') {
|
||||
hasStartFragment = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fragment.type === 'reset' && activeStyles.size > 0) {
|
||||
hasClosingEffect = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fragment.type === 'end' && activeStyles.has(fragment.endCode)) {
|
||||
hasClosingEffect = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasClosingEffect && !hasStartFragment;
|
||||
}
|
||||
|
||||
function applySgrToken({token, isPastEnd, activeStyles, returnValue, include, activeHyperlink, position}) {
|
||||
if (isPastEnd && !shouldIncludeSgrAfterEnd(token, activeStyles)) {
|
||||
return {
|
||||
activeStyles,
|
||||
activeHyperlink,
|
||||
position,
|
||||
returnValue,
|
||||
include,
|
||||
};
|
||||
}
|
||||
|
||||
activeStyles = applySgrFragments(activeStyles, token.fragments);
|
||||
if (include) {
|
||||
returnValue += token.code;
|
||||
}
|
||||
|
||||
return {
|
||||
activeStyles,
|
||||
activeHyperlink,
|
||||
position,
|
||||
returnValue,
|
||||
include,
|
||||
};
|
||||
}
|
||||
|
||||
function applyHyperlinkToken({token, isPastEnd, activeStyles, activeHyperlink, position, returnValue, include}) {
|
||||
if (
|
||||
isPastEnd
|
||||
&& (
|
||||
token.action !== 'close'
|
||||
|| !activeHyperlink
|
||||
)
|
||||
) {
|
||||
return {
|
||||
activeStyles,
|
||||
activeHyperlink,
|
||||
position,
|
||||
returnValue,
|
||||
include,
|
||||
};
|
||||
}
|
||||
|
||||
if (token.action === 'open') {
|
||||
activeHyperlink = token;
|
||||
} else if (token.action === 'close') {
|
||||
activeHyperlink = undefined;
|
||||
}
|
||||
|
||||
if (include) {
|
||||
returnValue += token.code;
|
||||
}
|
||||
|
||||
return {
|
||||
activeStyles,
|
||||
activeHyperlink,
|
||||
position,
|
||||
returnValue,
|
||||
include,
|
||||
};
|
||||
}
|
||||
|
||||
function applyControlToken({token, isPastEnd, activeStyles, activeHyperlink, position, returnValue, include}) {
|
||||
if (!isPastEnd && include) {
|
||||
returnValue += token.code;
|
||||
}
|
||||
|
||||
return {
|
||||
activeStyles,
|
||||
activeHyperlink,
|
||||
position,
|
||||
returnValue,
|
||||
include,
|
||||
};
|
||||
}
|
||||
|
||||
function applyCharacterToken({token, start, activeStyles, activeHyperlink, position, returnValue, include}) {
|
||||
if (
|
||||
!include
|
||||
&& position >= start
|
||||
&& !token.isGraphemeContinuation
|
||||
) {
|
||||
include = true;
|
||||
returnValue = [...activeStyles.values()].join('');
|
||||
if (activeHyperlink) {
|
||||
returnValue += activeHyperlink.code;
|
||||
}
|
||||
}
|
||||
|
||||
if (include) {
|
||||
returnValue += token.value;
|
||||
}
|
||||
|
||||
position += token.visibleWidth;
|
||||
return {
|
||||
activeStyles,
|
||||
activeHyperlink,
|
||||
position,
|
||||
returnValue,
|
||||
include,
|
||||
};
|
||||
}
|
||||
|
||||
const tokenHandlers = {
|
||||
sgr: applySgrToken,
|
||||
hyperlink: applyHyperlinkToken,
|
||||
control: applyControlToken,
|
||||
character: applyCharacterToken,
|
||||
};
|
||||
|
||||
function applyToken(parameters) {
|
||||
const tokenHandler = tokenHandlers[parameters.token.type];
|
||||
if (!tokenHandler) {
|
||||
const {
|
||||
activeStyles,
|
||||
activeHyperlink,
|
||||
position,
|
||||
returnValue,
|
||||
include,
|
||||
} = parameters;
|
||||
|
||||
return {
|
||||
activeStyles,
|
||||
activeHyperlink,
|
||||
position,
|
||||
returnValue,
|
||||
include,
|
||||
};
|
||||
}
|
||||
|
||||
return tokenHandler(parameters);
|
||||
}
|
||||
|
||||
function createHasContinuationAheadMap(tokens) {
|
||||
const hasContinuationAhead = Array.from({length: tokens.length}, () => false);
|
||||
let nextCharacterIsContinuation = false;
|
||||
|
||||
for (let tokenIndex = tokens.length - 1; tokenIndex >= 0; tokenIndex--) {
|
||||
const token = tokens[tokenIndex];
|
||||
hasContinuationAhead[tokenIndex] = nextCharacterIsContinuation;
|
||||
if (token.type === 'character') {
|
||||
nextCharacterIsContinuation = Boolean(token.isGraphemeContinuation);
|
||||
}
|
||||
}
|
||||
|
||||
return hasContinuationAhead;
|
||||
}
|
||||
|
||||
export default function sliceAnsi(string, start, end) {
|
||||
const tokens = tokenizeAnsi(string, {endCharacter: end});
|
||||
const hasContinuationAhead = createHasContinuationAheadMap(tokens);
|
||||
let activeStyles = new Map();
|
||||
let activeHyperlink;
|
||||
let position = 0;
|
||||
let returnValue = '';
|
||||
let include = false;
|
||||
|
||||
for (const [tokenIndex, token] of tokens.entries()) {
|
||||
let isPastEnd = end !== undefined && position >= end;
|
||||
if (
|
||||
isPastEnd
|
||||
&& token.type !== 'character'
|
||||
&& hasContinuationAhead[tokenIndex]
|
||||
) {
|
||||
isPastEnd = false;
|
||||
}
|
||||
|
||||
if (
|
||||
isPastEnd
|
||||
&& token.type === 'character'
|
||||
&& !token.isGraphemeContinuation
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
({activeStyles, activeHyperlink, position, returnValue, include} = applyToken({
|
||||
token,
|
||||
isPastEnd,
|
||||
start,
|
||||
activeStyles,
|
||||
activeHyperlink,
|
||||
position,
|
||||
returnValue,
|
||||
include,
|
||||
}));
|
||||
}
|
||||
|
||||
if (!include) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (activeHyperlink) {
|
||||
returnValue += closeHyperlink(activeHyperlink);
|
||||
}
|
||||
|
||||
// Disable active codes at the end
|
||||
returnValue += undoAnsiCodes(activeStyles);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) DC <threedeecee@gmail.com>
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "slice-ansi",
|
||||
"version": "8.0.0",
|
||||
"description": "Slice a string with ANSI escape codes",
|
||||
"license": "MIT",
|
||||
"repository": "chalk/slice-ansi",
|
||||
"funding": "https://github.com/chalk/slice-ansi?sponsor=1",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"types": "./index.d.ts",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "xo && ava && tsc --lib esnext index.d.ts"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"tokenize-ansi.js",
|
||||
"index.d.ts"
|
||||
],
|
||||
"keywords": [
|
||||
"slice",
|
||||
"string",
|
||||
"ansi",
|
||||
"styles",
|
||||
"color",
|
||||
"colour",
|
||||
"colors",
|
||||
"terminal",
|
||||
"console",
|
||||
"cli",
|
||||
"tty",
|
||||
"escape",
|
||||
"formatting",
|
||||
"rgb",
|
||||
"256",
|
||||
"shell",
|
||||
"xterm",
|
||||
"log",
|
||||
"logging",
|
||||
"command-line",
|
||||
"text"
|
||||
],
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.2.3",
|
||||
"is-fullwidth-code-point": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ava": "^6.4.1",
|
||||
"chalk": "^5.6.2",
|
||||
"random-item": "^4.0.1",
|
||||
"strip-ansi": "^7.1.2",
|
||||
"xo": "^1.2.3"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
# slice-ansi [](https://github.com/xojs/xo)
|
||||
|
||||
> Slice a string with [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors_and_Styles)
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
npm install slice-ansi
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import chalk from 'chalk';
|
||||
import sliceAnsi from 'slice-ansi';
|
||||
|
||||
const string = 'The quick brown ' + chalk.red('fox jumped over ') +
|
||||
'the lazy ' + chalk.green('dog and then ran away with the unicorn.');
|
||||
|
||||
console.log(sliceAnsi(string, 20, 30));
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### sliceAnsi(string, startSlice, endSlice?)
|
||||
|
||||
#### string
|
||||
|
||||
Type: `string`
|
||||
|
||||
String with ANSI escape codes. Like one styled by [`chalk`](https://github.com/chalk/chalk).
|
||||
|
||||
#### startSlice
|
||||
|
||||
Type: `number`
|
||||
|
||||
Zero-based visible-column index at which to start the slice. Grapheme clusters (for example, emoji sequences and combining marks) are kept intact.
|
||||
|
||||
#### endSlice
|
||||
|
||||
Type: `number`
|
||||
|
||||
Zero-based visible-column index at which to end the slice.
|
||||
|
||||
## Related
|
||||
|
||||
- [wrap-ansi](https://github.com/chalk/wrap-ansi) - Wordwrap a string with ANSI escape codes
|
||||
- [cli-truncate](https://github.com/sindresorhus/cli-truncate) - Truncate a string to a specific width in the terminal
|
||||
- [chalk](https://github.com/chalk/chalk) - Terminal string styling done right
|
||||
|
||||
## Maintainers
|
||||
|
||||
- [Sindre Sorhus](https://github.com/sindresorhus)
|
||||
- [Josh Junon](https://github.com/qix-)
|
||||
+752
@@ -0,0 +1,752 @@
|
||||
import ansiStyles from 'ansi-styles';
|
||||
import isFullwidthCodePoint from 'is-fullwidth-code-point';
|
||||
|
||||
const ESCAPE_CODE_POINT = 27;
|
||||
const C1_DCS_CODE_POINT = 144;
|
||||
const C1_SOS_CODE_POINT = 152;
|
||||
const C1_CSI_CODE_POINT = 155;
|
||||
const C1_ST_CODE_POINT = 156;
|
||||
const C1_OSC_CODE_POINT = 157;
|
||||
const C1_PM_CODE_POINT = 158;
|
||||
const C1_APC_CODE_POINT = 159;
|
||||
const ESCAPES = new Set([
|
||||
ESCAPE_CODE_POINT,
|
||||
C1_DCS_CODE_POINT,
|
||||
C1_SOS_CODE_POINT,
|
||||
C1_CSI_CODE_POINT,
|
||||
C1_ST_CODE_POINT,
|
||||
C1_OSC_CODE_POINT,
|
||||
C1_PM_CODE_POINT,
|
||||
C1_APC_CODE_POINT,
|
||||
]);
|
||||
|
||||
const ESCAPE = '\u001B';
|
||||
const ANSI_BELL = '\u0007';
|
||||
const ANSI_CSI = '[';
|
||||
const ANSI_OSC = ']';
|
||||
const ANSI_DCS = 'P';
|
||||
const ANSI_SOS = 'X';
|
||||
const ANSI_PM = '^';
|
||||
const ANSI_APC = '_';
|
||||
const ANSI_SGR_TERMINATOR = 'm';
|
||||
const ANSI_OSC_TERMINATOR = '\\';
|
||||
const ANSI_STRING_TERMINATOR = `${ESCAPE}${ANSI_OSC_TERMINATOR}`;
|
||||
const C1_OSC = '\u009D';
|
||||
const C1_STRING_TERMINATOR = '\u009C';
|
||||
const ANSI_HYPERLINK_ESC_PREFIX = `${ESCAPE}${ANSI_OSC}8;`;
|
||||
const ANSI_HYPERLINK_C1_PREFIX = `${C1_OSC}8;`;
|
||||
const ANSI_HYPERLINK_ESC_CLOSE = `${ANSI_HYPERLINK_ESC_PREFIX};`;
|
||||
const ANSI_HYPERLINK_C1_CLOSE = `${ANSI_HYPERLINK_C1_PREFIX};`;
|
||||
|
||||
const CODE_POINT_0 = '0'.codePointAt(0);
|
||||
const CODE_POINT_9 = '9'.codePointAt(0);
|
||||
const CODE_POINT_SEMICOLON = ';'.codePointAt(0);
|
||||
const CODE_POINT_COLON = ':'.codePointAt(0);
|
||||
// ECMA-48 CSI format: parameter bytes 0x30-0x3F, intermediates 0x20-0x2F, final 0x40-0x7E.
|
||||
const CODE_POINT_CSI_PARAMETER_START = '0'.codePointAt(0);
|
||||
const CODE_POINT_CSI_PARAMETER_END = '?'.codePointAt(0);
|
||||
const CODE_POINT_CSI_INTERMEDIATE_START = ' '.codePointAt(0);
|
||||
const CODE_POINT_CSI_INTERMEDIATE_END = '/'.codePointAt(0);
|
||||
const CODE_POINT_CSI_FINAL_START = '@'.codePointAt(0);
|
||||
const CODE_POINT_CSI_FINAL_END = '~'.codePointAt(0);
|
||||
const REGIONAL_INDICATOR_SYMBOL_LETTER_A = 127_462;
|
||||
const REGIONAL_INDICATOR_SYMBOL_LETTER_Z = 127_487;
|
||||
const SGR_RESET_CODE = 0;
|
||||
const SGR_EXTENDED_FOREGROUND_CODE = 38;
|
||||
const SGR_DEFAULT_FOREGROUND_CODE = 39;
|
||||
const SGR_EXTENDED_BACKGROUND_CODE = 48;
|
||||
const SGR_DEFAULT_BACKGROUND_CODE = 49;
|
||||
const SGR_COLOR_TYPE_ANSI_256 = 5;
|
||||
const SGR_COLOR_TYPE_TRUECOLOR = 2;
|
||||
const SGR_ANSI_256_FRAGMENT_LENGTH = 3;
|
||||
const SGR_TRUECOLOR_FRAGMENT_LENGTH = 5;
|
||||
const SGR_ANSI_256_LAST_PARAMETER_OFFSET = 2;
|
||||
const SGR_TRUECOLOR_LAST_PARAMETER_OFFSET = 4;
|
||||
const VARIATION_SELECTOR_16_CODE_POINT = 65_039;
|
||||
const COMBINING_ENCLOSING_KEYCAP_CODE_POINT = 8419;
|
||||
const EMOJI_PRESENTATION_GRAPHEME_REGEX = /\p{Emoji_Presentation}/u;
|
||||
const GRAPHEME_SEGMENTER = new Intl.Segmenter(undefined, {granularity: 'grapheme'});
|
||||
|
||||
const endCodeNumbers = new Set();
|
||||
for (const [, end] of ansiStyles.codes) {
|
||||
endCodeNumbers.add(end);
|
||||
}
|
||||
|
||||
function isSgrParameterCharacter(codePoint) {
|
||||
return (
|
||||
(codePoint >= CODE_POINT_0 && codePoint <= CODE_POINT_9)
|
||||
|| codePoint === CODE_POINT_SEMICOLON
|
||||
|| codePoint === CODE_POINT_COLON
|
||||
);
|
||||
}
|
||||
|
||||
function isCsiParameterCharacter(codePoint) {
|
||||
return codePoint >= CODE_POINT_CSI_PARAMETER_START && codePoint <= CODE_POINT_CSI_PARAMETER_END;
|
||||
}
|
||||
|
||||
function isCsiIntermediateCharacter(codePoint) {
|
||||
return codePoint >= CODE_POINT_CSI_INTERMEDIATE_START && codePoint <= CODE_POINT_CSI_INTERMEDIATE_END;
|
||||
}
|
||||
|
||||
function isCsiFinalCharacter(codePoint) {
|
||||
return codePoint >= CODE_POINT_CSI_FINAL_START && codePoint <= CODE_POINT_CSI_FINAL_END;
|
||||
}
|
||||
|
||||
function isRegionalIndicatorCodePoint(codePoint) {
|
||||
return codePoint >= REGIONAL_INDICATOR_SYMBOL_LETTER_A && codePoint <= REGIONAL_INDICATOR_SYMBOL_LETTER_Z;
|
||||
}
|
||||
|
||||
function createControlParseResult(code, endIndex) {
|
||||
return {
|
||||
token: {
|
||||
type: 'control',
|
||||
code,
|
||||
},
|
||||
endIndex,
|
||||
};
|
||||
}
|
||||
|
||||
function isEmojiStyleGrapheme(grapheme) {
|
||||
if (EMOJI_PRESENTATION_GRAPHEME_REGEX.test(grapheme)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const character of grapheme) {
|
||||
const codePoint = character.codePointAt(0);
|
||||
if (
|
||||
codePoint === VARIATION_SELECTOR_16_CODE_POINT
|
||||
|| codePoint === COMBINING_ENCLOSING_KEYCAP_CODE_POINT
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getGraphemeWidth(grapheme) {
|
||||
let regionalIndicatorCount = 0;
|
||||
for (const character of grapheme) {
|
||||
const codePoint = character.codePointAt(0);
|
||||
if (isFullwidthCodePoint(codePoint)) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (isRegionalIndicatorCodePoint(codePoint)) {
|
||||
regionalIndicatorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (regionalIndicatorCount >= 1) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (isEmojiStyleGrapheme(grapheme)) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
function getSgrPrefix(code) {
|
||||
if (code.startsWith('\u009B')) {
|
||||
return '\u009B';
|
||||
}
|
||||
|
||||
return `${ESCAPE}${ANSI_CSI}`;
|
||||
}
|
||||
|
||||
function createSgrCode(prefix, values) {
|
||||
return `${prefix}${values.join(';')}${ANSI_SGR_TERMINATOR}`;
|
||||
}
|
||||
|
||||
function getSgrFragments(code) {
|
||||
const fragments = [];
|
||||
const sgrPrefix = getSgrPrefix(code);
|
||||
let parameterString;
|
||||
|
||||
if (code.startsWith(`${ESCAPE}${ANSI_CSI}`)) {
|
||||
parameterString = code.slice(2, -1);
|
||||
} else if (code.startsWith('\u009B')) {
|
||||
parameterString = code.slice(1, -1);
|
||||
} else {
|
||||
return fragments;
|
||||
}
|
||||
|
||||
const rawCodes = parameterString.length === 0 ? [String(SGR_RESET_CODE)] : parameterString.split(';');
|
||||
let index = 0;
|
||||
while (index < rawCodes.length) {
|
||||
const codeNumber = Number.parseInt(rawCodes[index], 10);
|
||||
if (Number.isNaN(codeNumber)) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (codeNumber === SGR_RESET_CODE) {
|
||||
fragments.push({type: 'reset'});
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (codeNumber === SGR_EXTENDED_FOREGROUND_CODE || codeNumber === SGR_EXTENDED_BACKGROUND_CODE) {
|
||||
const colorType = Number.parseInt(rawCodes[index + 1], 10);
|
||||
if (colorType === SGR_COLOR_TYPE_ANSI_256 && index + SGR_ANSI_256_LAST_PARAMETER_OFFSET < rawCodes.length) {
|
||||
const openCode = createSgrCode(sgrPrefix, rawCodes.slice(index, index + SGR_ANSI_256_FRAGMENT_LENGTH));
|
||||
fragments.push({
|
||||
type: 'start',
|
||||
code: openCode,
|
||||
endCode: ansiStyles.color.ansi(codeNumber === SGR_EXTENDED_FOREGROUND_CODE ? SGR_DEFAULT_FOREGROUND_CODE : SGR_DEFAULT_BACKGROUND_CODE),
|
||||
});
|
||||
index += SGR_ANSI_256_FRAGMENT_LENGTH;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (colorType === SGR_COLOR_TYPE_TRUECOLOR && index + SGR_TRUECOLOR_LAST_PARAMETER_OFFSET < rawCodes.length) {
|
||||
const openCode = createSgrCode(sgrPrefix, rawCodes.slice(index, index + SGR_TRUECOLOR_FRAGMENT_LENGTH));
|
||||
fragments.push({
|
||||
type: 'start',
|
||||
code: openCode,
|
||||
endCode: ansiStyles.color.ansi(codeNumber === SGR_EXTENDED_FOREGROUND_CODE ? SGR_DEFAULT_FOREGROUND_CODE : SGR_DEFAULT_BACKGROUND_CODE),
|
||||
});
|
||||
index += SGR_TRUECOLOR_FRAGMENT_LENGTH;
|
||||
continue;
|
||||
}
|
||||
|
||||
const openCode = createSgrCode(sgrPrefix, [rawCodes[index]]);
|
||||
fragments.push({
|
||||
type: 'start',
|
||||
code: openCode,
|
||||
endCode: ansiStyles.color.ansi(codeNumber === SGR_EXTENDED_FOREGROUND_CODE ? SGR_DEFAULT_FOREGROUND_CODE : SGR_DEFAULT_BACKGROUND_CODE),
|
||||
});
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (endCodeNumbers.has(codeNumber)) {
|
||||
fragments.push({
|
||||
type: 'end',
|
||||
endCode: ansiStyles.color.ansi(codeNumber),
|
||||
});
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const mappedEndCode = ansiStyles.codes.get(codeNumber);
|
||||
if (mappedEndCode !== undefined) {
|
||||
const openCode = createSgrCode(sgrPrefix, [rawCodes[index]]);
|
||||
fragments.push({
|
||||
type: 'start',
|
||||
code: openCode,
|
||||
endCode: ansiStyles.color.ansi(mappedEndCode),
|
||||
});
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const openCode = createSgrCode(sgrPrefix, [rawCodes[index]]);
|
||||
fragments.push({
|
||||
type: 'start',
|
||||
code: openCode,
|
||||
endCode: ansiStyles.reset.open,
|
||||
});
|
||||
index++;
|
||||
}
|
||||
|
||||
if (fragments.length === 0) {
|
||||
fragments.push({type: 'reset'});
|
||||
}
|
||||
|
||||
return fragments;
|
||||
}
|
||||
|
||||
function parseCsiCode(string, index) {
|
||||
const escapeCodePoint = string.codePointAt(index);
|
||||
let sequenceStartIndex;
|
||||
|
||||
if (escapeCodePoint === ESCAPE_CODE_POINT) {
|
||||
if (string[index + 1] !== ANSI_CSI) {
|
||||
return;
|
||||
}
|
||||
|
||||
sequenceStartIndex = index + 2;
|
||||
} else if (escapeCodePoint === C1_CSI_CODE_POINT) {
|
||||
sequenceStartIndex = index + 1;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
let hasCanonicalSgrParameters = true;
|
||||
for (let sequenceIndex = sequenceStartIndex; sequenceIndex < string.length; sequenceIndex++) {
|
||||
const codePoint = string.codePointAt(sequenceIndex);
|
||||
|
||||
if (isCsiFinalCharacter(codePoint)) {
|
||||
const code = string.slice(index, sequenceIndex + 1);
|
||||
if (string[sequenceIndex] !== ANSI_SGR_TERMINATOR || !hasCanonicalSgrParameters) {
|
||||
return createControlParseResult(code, sequenceIndex + 1);
|
||||
}
|
||||
|
||||
return {
|
||||
token: {
|
||||
type: 'sgr',
|
||||
code,
|
||||
fragments: getSgrFragments(code),
|
||||
},
|
||||
endIndex: sequenceIndex + 1,
|
||||
};
|
||||
}
|
||||
|
||||
if (isCsiParameterCharacter(codePoint)) {
|
||||
if (!isSgrParameterCharacter(codePoint)) {
|
||||
hasCanonicalSgrParameters = false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isCsiIntermediateCharacter(codePoint)) {
|
||||
hasCanonicalSgrParameters = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
const endIndex = sequenceIndex;
|
||||
return createControlParseResult(string.slice(index, endIndex), endIndex);
|
||||
}
|
||||
|
||||
return createControlParseResult(string.slice(index), string.length);
|
||||
}
|
||||
|
||||
function parseHyperlinkCode(string, index) {
|
||||
let hyperlinkPrefix;
|
||||
let hyperlinkClose;
|
||||
const codePoint = string.codePointAt(index);
|
||||
|
||||
if (
|
||||
codePoint === ESCAPE_CODE_POINT
|
||||
&& string.startsWith(ANSI_HYPERLINK_ESC_PREFIX, index)
|
||||
) {
|
||||
hyperlinkPrefix = ANSI_HYPERLINK_ESC_PREFIX;
|
||||
hyperlinkClose = ANSI_HYPERLINK_ESC_CLOSE;
|
||||
} else if (
|
||||
codePoint === C1_OSC_CODE_POINT
|
||||
&& string.startsWith(ANSI_HYPERLINK_C1_PREFIX, index)
|
||||
) {
|
||||
hyperlinkPrefix = ANSI_HYPERLINK_C1_PREFIX;
|
||||
hyperlinkClose = ANSI_HYPERLINK_C1_CLOSE;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
const uriStart = string.indexOf(';', index + hyperlinkPrefix.length);
|
||||
if (uriStart === -1) {
|
||||
return createControlParseResult(string.slice(index), string.length);
|
||||
}
|
||||
|
||||
for (let sequenceIndex = uriStart + 1; sequenceIndex < string.length; sequenceIndex++) {
|
||||
const character = string[sequenceIndex];
|
||||
|
||||
if (character === ANSI_BELL) {
|
||||
const code = string.slice(index, sequenceIndex + 1);
|
||||
const action = sequenceIndex === uriStart + 1 ? 'close' : 'open';
|
||||
return {
|
||||
token: {
|
||||
type: 'hyperlink',
|
||||
code,
|
||||
action,
|
||||
closePrefix: hyperlinkClose,
|
||||
terminator: ANSI_BELL,
|
||||
},
|
||||
endIndex: sequenceIndex + 1,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
character === ESCAPE
|
||||
&& string[sequenceIndex + 1] === ANSI_OSC_TERMINATOR
|
||||
) {
|
||||
const code = string.slice(index, sequenceIndex + 2);
|
||||
const action = sequenceIndex === uriStart + 1 ? 'close' : 'open';
|
||||
return {
|
||||
token: {
|
||||
type: 'hyperlink',
|
||||
code,
|
||||
action,
|
||||
closePrefix: hyperlinkClose,
|
||||
terminator: ANSI_STRING_TERMINATOR,
|
||||
},
|
||||
endIndex: sequenceIndex + 2,
|
||||
};
|
||||
}
|
||||
|
||||
if (character === C1_STRING_TERMINATOR) {
|
||||
const code = string.slice(index, sequenceIndex + 1);
|
||||
const action = sequenceIndex === uriStart + 1 ? 'close' : 'open';
|
||||
return {
|
||||
token: {
|
||||
type: 'hyperlink',
|
||||
code,
|
||||
action,
|
||||
closePrefix: hyperlinkClose,
|
||||
terminator: C1_STRING_TERMINATOR,
|
||||
},
|
||||
endIndex: sequenceIndex + 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return createControlParseResult(string.slice(index), string.length);
|
||||
}
|
||||
|
||||
function parseControlStringCode(string, index) {
|
||||
const codePoint = string.codePointAt(index);
|
||||
let sequenceStartIndex;
|
||||
let supportsBellTerminator = false;
|
||||
|
||||
switch (codePoint) {
|
||||
case ESCAPE_CODE_POINT: {
|
||||
const command = string[index + 1];
|
||||
switch (command) {
|
||||
case ANSI_OSC: {
|
||||
// OSC accepts ST (ECMA-48) and BEL (xterm compatibility extension).
|
||||
sequenceStartIndex = index + 2;
|
||||
supportsBellTerminator = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case ANSI_DCS:
|
||||
case ANSI_SOS:
|
||||
case ANSI_PM:
|
||||
case ANSI_APC: {
|
||||
sequenceStartIndex = index + 2;
|
||||
break;
|
||||
}
|
||||
|
||||
case ANSI_OSC_TERMINATOR: {
|
||||
return createControlParseResult(ANSI_STRING_TERMINATOR, index + 2);
|
||||
}
|
||||
|
||||
default: {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case C1_OSC_CODE_POINT: {
|
||||
sequenceStartIndex = index + 1;
|
||||
supportsBellTerminator = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case C1_DCS_CODE_POINT:
|
||||
case C1_SOS_CODE_POINT:
|
||||
case C1_PM_CODE_POINT:
|
||||
case C1_APC_CODE_POINT: {
|
||||
sequenceStartIndex = index + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
case C1_ST_CODE_POINT: {
|
||||
return createControlParseResult(C1_STRING_TERMINATOR, index + 1);
|
||||
}
|
||||
|
||||
default: {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (let sequenceIndex = sequenceStartIndex; sequenceIndex < string.length; sequenceIndex++) {
|
||||
if (supportsBellTerminator && string[sequenceIndex] === ANSI_BELL) {
|
||||
return createControlParseResult(string.slice(index, sequenceIndex + 1), sequenceIndex + 1);
|
||||
}
|
||||
|
||||
if (
|
||||
string[sequenceIndex] === ESCAPE
|
||||
&& string[sequenceIndex + 1] === ANSI_OSC_TERMINATOR
|
||||
) {
|
||||
return createControlParseResult(string.slice(index, sequenceIndex + 2), sequenceIndex + 2);
|
||||
}
|
||||
|
||||
if (string[sequenceIndex] === C1_STRING_TERMINATOR) {
|
||||
return createControlParseResult(string.slice(index, sequenceIndex + 1), sequenceIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return createControlParseResult(string.slice(index), string.length);
|
||||
}
|
||||
|
||||
function parseAnsiCode(string, index) {
|
||||
const codePoint = string.codePointAt(index);
|
||||
if (codePoint === ESCAPE_CODE_POINT || codePoint === C1_OSC_CODE_POINT) {
|
||||
const hyperlinkCode = parseHyperlinkCode(string, index);
|
||||
if (hyperlinkCode) {
|
||||
return hyperlinkCode;
|
||||
}
|
||||
}
|
||||
|
||||
const controlStringCode = parseControlStringCode(string, index);
|
||||
if (controlStringCode) {
|
||||
return controlStringCode;
|
||||
}
|
||||
|
||||
return parseCsiCode(string, index);
|
||||
}
|
||||
|
||||
function appendTrailingAnsiTokens(string, index, tokens) {
|
||||
while (index < string.length) {
|
||||
const nextCodePoint = string.codePointAt(index);
|
||||
if (!ESCAPES.has(nextCodePoint)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const escapeCode = parseAnsiCode(string, index);
|
||||
if (!escapeCode) {
|
||||
break;
|
||||
}
|
||||
|
||||
tokens.push(escapeCode.token);
|
||||
index = escapeCode.endIndex;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
function parseCharacterTokenWithRawSegmentation(string, index, graphemeSegments) {
|
||||
const segment = graphemeSegments.containing(index);
|
||||
if (!segment || segment.index !== index) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
token: {
|
||||
type: 'character',
|
||||
// Intentionally preserve UAX29 behavior (GB3): CRLF is one grapheme cluster.
|
||||
value: segment.segment,
|
||||
visibleWidth: getGraphemeWidth(segment.segment),
|
||||
isGraphemeContinuation: false,
|
||||
},
|
||||
endIndex: index + segment.segment.length,
|
||||
};
|
||||
}
|
||||
|
||||
function collectVisibleCharacters(string) {
|
||||
const visibleCharacters = [];
|
||||
let index = 0;
|
||||
|
||||
while (index < string.length) {
|
||||
const codePoint = string.codePointAt(index);
|
||||
if (ESCAPES.has(codePoint)) {
|
||||
const code = parseAnsiCode(string, index);
|
||||
if (code) {
|
||||
index = code.endIndex;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const value = String.fromCodePoint(codePoint);
|
||||
visibleCharacters.push({
|
||||
value,
|
||||
visibleWidth: 1,
|
||||
isGraphemeContinuation: false,
|
||||
});
|
||||
index += value.length;
|
||||
}
|
||||
|
||||
return visibleCharacters;
|
||||
}
|
||||
|
||||
function applyGraphemeMetadata(visibleCharacters) {
|
||||
if (visibleCharacters.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const visibleString = visibleCharacters.map(({value}) => value).join('');
|
||||
const scalarOffsets = [];
|
||||
let scalarOffset = 0;
|
||||
|
||||
for (const visibleCharacter of visibleCharacters) {
|
||||
scalarOffsets.push(scalarOffset);
|
||||
scalarOffset += visibleCharacter.value.length;
|
||||
}
|
||||
|
||||
let scalarIndex = 0;
|
||||
for (const segment of GRAPHEME_SEGMENTER.segment(visibleString)) {
|
||||
while (
|
||||
scalarIndex < visibleCharacters.length
|
||||
&& scalarOffsets[scalarIndex] < segment.index
|
||||
) {
|
||||
scalarIndex++;
|
||||
}
|
||||
|
||||
let graphemeIndex = scalarIndex;
|
||||
let isFirstInGrapheme = true;
|
||||
while (
|
||||
graphemeIndex < visibleCharacters.length
|
||||
&& scalarOffsets[graphemeIndex] < segment.index + segment.segment.length
|
||||
) {
|
||||
visibleCharacters[graphemeIndex].visibleWidth = isFirstInGrapheme ? getGraphemeWidth(segment.segment) : 0;
|
||||
visibleCharacters[graphemeIndex].isGraphemeContinuation = !isFirstInGrapheme;
|
||||
isFirstInGrapheme = false;
|
||||
graphemeIndex++;
|
||||
}
|
||||
|
||||
scalarIndex = graphemeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
function tokenizeAnsiWithVisibleSegmentation(string, {endCharacter = Number.POSITIVE_INFINITY} = {}) {
|
||||
const tokens = [];
|
||||
const visibleCharacters = collectVisibleCharacters(string);
|
||||
applyGraphemeMetadata(visibleCharacters);
|
||||
|
||||
let index = 0;
|
||||
let visibleCharacterIndex = 0;
|
||||
let visibleCount = 0;
|
||||
while (index < string.length) {
|
||||
const codePoint = string.codePointAt(index);
|
||||
|
||||
if (ESCAPES.has(codePoint)) {
|
||||
const code = parseAnsiCode(string, index);
|
||||
if (code) {
|
||||
tokens.push(code.token);
|
||||
index = code.endIndex;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const value = String.fromCodePoint(codePoint);
|
||||
const visibleCharacter = visibleCharacters[visibleCharacterIndex];
|
||||
let visibleWidth = isFullwidthCodePoint(codePoint) ? 2 : value.length;
|
||||
if (visibleCharacter) {
|
||||
visibleWidth = visibleCharacter.visibleWidth;
|
||||
}
|
||||
|
||||
const token = {
|
||||
type: 'character',
|
||||
value,
|
||||
visibleWidth,
|
||||
isGraphemeContinuation: visibleCharacter ? visibleCharacter.isGraphemeContinuation : false,
|
||||
};
|
||||
|
||||
tokens.push(token);
|
||||
index += value.length;
|
||||
visibleCharacterIndex++;
|
||||
visibleCount += token.visibleWidth;
|
||||
|
||||
if (visibleCount >= endCharacter) {
|
||||
const nextVisibleCharacter = visibleCharacters[visibleCharacterIndex];
|
||||
if (
|
||||
!nextVisibleCharacter
|
||||
|| !nextVisibleCharacter.isGraphemeContinuation
|
||||
) {
|
||||
index = appendTrailingAnsiTokens(string, index, tokens);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function areValuesInSameGrapheme(leftValue, rightValue) {
|
||||
const pair = `${leftValue}${rightValue}`;
|
||||
const splitIndex = leftValue.length;
|
||||
|
||||
for (const segment of GRAPHEME_SEGMENTER.segment(pair)) {
|
||||
if (segment.index === splitIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (segment.index > splitIndex) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function hasAnsiSplitContinuationAhead(string, startIndex, previousVisibleValue, graphemeSegments) {
|
||||
if (!previousVisibleValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let index = startIndex;
|
||||
let hasAnsiCode = false;
|
||||
while (index < string.length) {
|
||||
const codePoint = string.codePointAt(index);
|
||||
if (ESCAPES.has(codePoint)) {
|
||||
const code = parseAnsiCode(string, index);
|
||||
if (code) {
|
||||
hasAnsiCode = true;
|
||||
index = code.endIndex;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasAnsiCode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const characterToken = parseCharacterTokenWithRawSegmentation(string, index, graphemeSegments);
|
||||
if (!characterToken) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return areValuesInSameGrapheme(previousVisibleValue, characterToken.token.value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export default function tokenizeAnsi(string, {endCharacter = Number.POSITIVE_INFINITY} = {}) {
|
||||
const tokens = [];
|
||||
const graphemeSegments = GRAPHEME_SEGMENTER.segment(string);
|
||||
|
||||
let index = 0;
|
||||
let visibleCount = 0;
|
||||
let previousVisibleValue;
|
||||
let hasAnsiSinceLastVisible = false;
|
||||
while (index < string.length) {
|
||||
const codePoint = string.codePointAt(index);
|
||||
|
||||
if (ESCAPES.has(codePoint)) {
|
||||
const code = parseAnsiCode(string, index);
|
||||
if (code) {
|
||||
tokens.push(code.token);
|
||||
index = code.endIndex;
|
||||
hasAnsiSinceLastVisible = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const characterToken = parseCharacterTokenWithRawSegmentation(string, index, graphemeSegments);
|
||||
if (!characterToken) {
|
||||
return tokenizeAnsiWithVisibleSegmentation(string, {endCharacter});
|
||||
}
|
||||
|
||||
if (
|
||||
hasAnsiSinceLastVisible
|
||||
&& previousVisibleValue
|
||||
&& areValuesInSameGrapheme(previousVisibleValue, characterToken.token.value)
|
||||
) {
|
||||
return tokenizeAnsiWithVisibleSegmentation(string, {endCharacter});
|
||||
}
|
||||
|
||||
tokens.push(characterToken.token);
|
||||
index = characterToken.endIndex;
|
||||
visibleCount += characterToken.token.visibleWidth;
|
||||
hasAnsiSinceLastVisible = false;
|
||||
previousVisibleValue = characterToken.token.value;
|
||||
|
||||
if (visibleCount >= endCharacter) {
|
||||
if (hasAnsiSplitContinuationAhead(string, index, previousVisibleValue, graphemeSegments)) {
|
||||
return tokenizeAnsiWithVisibleSegmentation(string, {endCharacter});
|
||||
}
|
||||
|
||||
index = appendTrailingAnsiTokens(string, index, tokens);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
Reference in New Issue
Block a user