fast-string-width
Advanced tools
Comparing version 1.0.4 to 1.0.5
@@ -1,4 +0,4 @@ | ||
import type { Options } from './types'; | ||
declare const getStringWidth: (input: string, options?: Options) => number; | ||
export default getStringWidth; | ||
import type { WidthOptions as Options } from 'fast-string-truncated-width'; | ||
declare const fastStringWidth: (input: string, options?: Options) => number; | ||
export default fastStringWidth; | ||
export type { Options }; |
/* IMPORT */ | ||
import { isAmbiguous, isFullWidth, isWide } from './utils.js'; | ||
import fastStringTruncatedWidth from 'fast-string-truncated-width'; | ||
/* HELPERS */ | ||
const ANSI_RE = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/y; | ||
const CONTROL_RE = /[\x00-\x1F\x7F-\x9F]+/y; | ||
const EMOJI_RE = /(?:\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F)(?:\u200d(?:\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F))*/yu; | ||
const LATIN_RE = /[\x20-\x7E\xA0-\xFF]+/y; | ||
const MODIFIER_RE = /\p{M}+/gu; | ||
const NO_TRUNCATION = { limit: Infinity, ellipsis: '' }; | ||
/* MAIN */ | ||
//TODO: Optimize matching non-latin letters | ||
const getStringWidth = (input, options = {}) => { | ||
/* CONSTANTS */ | ||
const ANSI_WIDTH = options.ansiWidth ?? 0; | ||
const CONTROL_WIDTH = options.controlWidth ?? 0; | ||
const AMBIGUOUS_WIDTH = options.ambiguousWidth ?? 1; | ||
const EMOJI_WIDTH = options.emojiWidth ?? 2; | ||
const FULL_WIDTH_WIDTH = options.fullWidthWidth ?? 2; | ||
const REGULAR_WIDTH = options.regularWidth ?? 1; | ||
const WIDE_WIDTH = options.wideWidth ?? 2; | ||
/* STATE */ | ||
let indexPrev = 0; | ||
let index = 0; | ||
let length = input.length; | ||
let unmatchedStart = 0; | ||
let unmatchedEnd = 0; | ||
let width = 0; | ||
/* PARSE LOOP */ | ||
while (true) { | ||
/* UNMATCHED */ | ||
if ((unmatchedEnd > unmatchedStart) || (index >= length && index > indexPrev)) { | ||
const unmatched = input.slice(unmatchedStart, unmatchedEnd) || input.slice(indexPrev, index); | ||
for (const char of unmatched.replaceAll(MODIFIER_RE, '')) { | ||
const codePoint = char.codePointAt(0) || 0; | ||
if (isFullWidth(codePoint)) { | ||
width += FULL_WIDTH_WIDTH; | ||
} | ||
else if (isWide(codePoint)) { | ||
width += WIDE_WIDTH; | ||
} | ||
else if (AMBIGUOUS_WIDTH !== REGULAR_WIDTH && isAmbiguous(codePoint)) { | ||
width += AMBIGUOUS_WIDTH; | ||
} | ||
else { | ||
width += REGULAR_WIDTH; | ||
} | ||
} | ||
unmatchedStart = unmatchedEnd = 0; | ||
} | ||
/* EXITING */ | ||
if (index >= length) | ||
break; | ||
/* LATIN */ | ||
LATIN_RE.lastIndex = index; | ||
if (LATIN_RE.test(input)) { | ||
width += (LATIN_RE.lastIndex - index) * REGULAR_WIDTH; | ||
unmatchedStart = indexPrev; | ||
unmatchedEnd = index; | ||
index = indexPrev = LATIN_RE.lastIndex; | ||
continue; | ||
} | ||
/* ANSI */ | ||
ANSI_RE.lastIndex = index; | ||
if (ANSI_RE.test(input)) { | ||
width += ANSI_WIDTH; | ||
unmatchedStart = indexPrev; | ||
unmatchedEnd = index; | ||
index = indexPrev = ANSI_RE.lastIndex; | ||
continue; | ||
} | ||
/* CONTROL */ | ||
CONTROL_RE.lastIndex = index; | ||
if (CONTROL_RE.test(input)) { | ||
width += (CONTROL_RE.lastIndex - index) * CONTROL_WIDTH; | ||
unmatchedStart = indexPrev; | ||
unmatchedEnd = index; | ||
index = indexPrev = CONTROL_RE.lastIndex; | ||
continue; | ||
} | ||
/* EMOJI */ | ||
EMOJI_RE.lastIndex = index; | ||
if (EMOJI_RE.test(input)) { | ||
width += EMOJI_WIDTH; | ||
unmatchedStart = indexPrev; | ||
unmatchedEnd = index; | ||
index = indexPrev = EMOJI_RE.lastIndex; | ||
continue; | ||
} | ||
/* UNMATCHED INDEX */ | ||
index += 1; | ||
} | ||
/* RETURN */ | ||
return width; | ||
const fastStringWidth = (input, options = {}) => { | ||
return fastStringTruncatedWidth(input, NO_TRUNCATION, options).width; | ||
}; | ||
/* EXPORT */ | ||
export default getStringWidth; | ||
export default fastStringWidth; |
@@ -5,3 +5,3 @@ { | ||
"description": "A fast function for calculating the visual width of a string once printed to the terminal.", | ||
"version": "1.0.4", | ||
"version": "1.0.5", | ||
"type": "module", | ||
@@ -28,4 +28,6 @@ "main": "dist/index.js", | ||
], | ||
"dependencies": { | ||
"fast-string-truncated-width": "^1.0.4" | ||
}, | ||
"devDependencies": { | ||
"benchloop": "^2.1.1", | ||
"fava": "^0.3.2", | ||
@@ -32,0 +34,0 @@ "tsex": "^3.0.2", |
147
src/index.ts
/* IMPORT */ | ||
import {isAmbiguous, isFullWidth, isWide} from './utils'; | ||
import type {Options} from './types'; | ||
import type {WidthOptions as Options} from 'fast-string-truncated-width'; | ||
import fastStringTruncatedWidth from 'fast-string-truncated-width'; | ||
/* HELPERS */ | ||
const ANSI_RE = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/y; | ||
const CONTROL_RE = /[\x00-\x1F\x7F-\x9F]+/y; | ||
const EMOJI_RE = /(?:\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F)(?:\u200d(?:\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F))*/yu; | ||
const LATIN_RE = /[\x20-\x7E\xA0-\xFF]+/y; | ||
const MODIFIER_RE = /\p{M}+/gu; | ||
const NO_TRUNCATION = { limit: Infinity, ellipsis: '' }; | ||
/* MAIN */ | ||
//TODO: Optimize matching non-latin letters | ||
const fastStringWidth = ( input: string, options: Options = {} ): number => { | ||
const getStringWidth = ( input: string, options: Options = {} ): number => { | ||
return fastStringTruncatedWidth ( input, NO_TRUNCATION, options ).width; | ||
/* CONSTANTS */ | ||
const ANSI_WIDTH = options.ansiWidth ?? 0; | ||
const CONTROL_WIDTH = options.controlWidth ?? 0; | ||
const AMBIGUOUS_WIDTH = options.ambiguousWidth ?? 1; | ||
const EMOJI_WIDTH = options.emojiWidth ?? 2; | ||
const FULL_WIDTH_WIDTH = options.fullWidthWidth ?? 2; | ||
const REGULAR_WIDTH = options.regularWidth ?? 1; | ||
const WIDE_WIDTH = options.wideWidth ?? 2; | ||
/* STATE */ | ||
let indexPrev = 0; | ||
let index = 0; | ||
let length = input.length; | ||
let unmatchedStart = 0; | ||
let unmatchedEnd = 0; | ||
let width = 0; | ||
/* PARSE LOOP */ | ||
while ( true ) { | ||
/* UNMATCHED */ | ||
if ( ( unmatchedEnd > unmatchedStart ) || ( index >= length && index > indexPrev ) ) { | ||
const unmatched = input.slice ( unmatchedStart, unmatchedEnd ) || input.slice ( indexPrev, index ); | ||
for ( const char of unmatched.replaceAll ( MODIFIER_RE, '' ) ) { | ||
const codePoint = char.codePointAt ( 0 ) || 0; | ||
if ( isFullWidth ( codePoint ) ) { | ||
width += FULL_WIDTH_WIDTH; | ||
} else if ( isWide ( codePoint ) ) { | ||
width += WIDE_WIDTH; | ||
} else if ( AMBIGUOUS_WIDTH !== REGULAR_WIDTH && isAmbiguous ( codePoint ) ) { | ||
width += AMBIGUOUS_WIDTH; | ||
} else { | ||
width += REGULAR_WIDTH; | ||
} | ||
} | ||
unmatchedStart = unmatchedEnd = 0; | ||
} | ||
/* EXITING */ | ||
if ( index >= length ) break; | ||
/* LATIN */ | ||
LATIN_RE.lastIndex = index; | ||
if ( LATIN_RE.test ( input ) ) { | ||
width += ( LATIN_RE.lastIndex - index ) * REGULAR_WIDTH; | ||
unmatchedStart = indexPrev; | ||
unmatchedEnd = index; | ||
index = indexPrev = LATIN_RE.lastIndex; | ||
continue; | ||
} | ||
/* ANSI */ | ||
ANSI_RE.lastIndex = index; | ||
if ( ANSI_RE.test ( input ) ) { | ||
width += ANSI_WIDTH; | ||
unmatchedStart = indexPrev; | ||
unmatchedEnd = index; | ||
index = indexPrev = ANSI_RE.lastIndex; | ||
continue; | ||
} | ||
/* CONTROL */ | ||
CONTROL_RE.lastIndex = index; | ||
if ( CONTROL_RE.test ( input ) ) { | ||
width += ( CONTROL_RE.lastIndex - index ) * CONTROL_WIDTH; | ||
unmatchedStart = indexPrev; | ||
unmatchedEnd = index; | ||
index = indexPrev = CONTROL_RE.lastIndex; | ||
continue; | ||
} | ||
/* EMOJI */ | ||
EMOJI_RE.lastIndex = index; | ||
if ( EMOJI_RE.test ( input ) ) { | ||
width += EMOJI_WIDTH; | ||
unmatchedStart = indexPrev; | ||
unmatchedEnd = index; | ||
index = indexPrev = EMOJI_RE.lastIndex; | ||
continue; | ||
} | ||
/* UNMATCHED INDEX */ | ||
index += 1; | ||
} | ||
/* RETURN */ | ||
return width; | ||
}; | ||
@@ -156,3 +21,3 @@ | ||
export default getStringWidth; | ||
export default fastStringWidth; | ||
export type {Options}; |
@@ -11,109 +11,12 @@ | ||
it ( 'supports basic cases', t => { | ||
it ( 'works', t => { | ||
t.is ( fastStringWidth ( 'hello' ), 5 ); | ||
t.is ( fastStringWidth ( '\x1b[31mhello' ), 5 ); | ||
t.is ( fastStringWidth ( '👨👩👧👦' ), 2 ); | ||
t.is ( fastStringWidth ( 'hello👨👩👧👦' ), 7 ); | ||
t.is ( fastStringWidth ( '👶👶🏽', { emojiWidth: 1.5 } ), 3 ); | ||
t.is ( fastStringWidth ( 'abcde' ), 5 ); | ||
t.is ( fastStringWidth ( '古池や' ), 6 ); | ||
t.is ( fastStringWidth ( 'あいうabc' ), 9 ); | ||
t.is ( fastStringWidth ( 'あいう★' ), 7 ); | ||
t.is ( fastStringWidth ( '±' ), 1 ); | ||
t.is ( fastStringWidth ( 'ノード.js' ), 9 ); | ||
t.is ( fastStringWidth ( '你好' ), 4 ); | ||
t.is ( fastStringWidth ( '안녕하세요' ), 10 ); | ||
t.is ( fastStringWidth ( 'A\uD83C\uDE00BC' ), 5 ); | ||
t.is ( fastStringWidth ( '\u001B[31m\u001B[39m' ), 0 ); | ||
// t.is ( fastStringWidth ( '\u001B]8;;https://github.com\u0007Click\u001B]8;;\u0007' ), 5 ); //TODO: Maybe support these extra escapes too? | ||
t.is ( fastStringWidth ( '\u{231A}' ), 2 ); | ||
t.is ( fastStringWidth ( '\u{2194}\u{FE0F}' ), 2 ); | ||
t.is ( fastStringWidth ( '\u{1F469}' ), 2 ); | ||
t.is ( fastStringWidth ( '\u{1F469}\u{1F3FF}' ), 2 ); | ||
t.is ( fastStringWidth ( '\u{845B}\u{E0100}' ), 2 ); | ||
t.is ( fastStringWidth ( 'ปฏัก' ), 3 ); | ||
t.is ( fastStringWidth ( '_\u0E34' ), 1 ); | ||
}); | ||
it ( 'supports control characters', t => { | ||
t.is ( fastStringWidth ( String.fromCodePoint ( 0 ) ), 0 ); | ||
t.is ( fastStringWidth ( String.fromCodePoint ( 31 ) ), 0 ); | ||
t.is ( fastStringWidth ( String.fromCodePoint ( 127 ) ), 0 ); | ||
t.is ( fastStringWidth ( String.fromCodePoint ( 134 ) ), 0 ); | ||
t.is ( fastStringWidth ( String.fromCodePoint ( 159 ) ), 0 ); | ||
t.is ( fastStringWidth ( '\u001B' ), 0 ); | ||
}); | ||
it ( 'supports combining characters', t => { | ||
t.is ( fastStringWidth ( 'x\u0300' ), 1 ); | ||
}); | ||
it ( 'supports ZWJ characters', t => { | ||
t.is ( fastStringWidth ( '👶' ), 2 ); | ||
t.is ( fastStringWidth ( '👶🏽' ), 2 ); | ||
t.is ( fastStringWidth ( '👩👩👦👦' ), 2 ); | ||
t.is ( fastStringWidth ( '👨❤️💋👨' ), 2 ); | ||
t.is ( fastStringWidth ( '👶'.repeat ( 2 ) ), 4 ); | ||
t.is ( fastStringWidth ( '👶🏽'.repeat ( 2 ) ), 4 ); | ||
t.is ( fastStringWidth ( '👩👩👦👦'.repeat ( 2 ) ), 4 ); | ||
t.is ( fastStringWidth ( '👨❤️💋👨'.repeat ( 2 ) ), 4 ); | ||
}); | ||
it ( 'supports unicode characters', t => { | ||
t.is ( fastStringWidth ( '…' ), 1 ); | ||
t.is ( fastStringWidth ( '\u2770' ), 1 ); | ||
t.is ( fastStringWidth ( '\u2771' ), 1 ); | ||
// t.is ( fastStringWidth ( '\u21a9' ), 2 ); | ||
t.is ( fastStringWidth ( '\u2193' ), 1 ); | ||
t.is ( fastStringWidth ( '\u21F5' ), 1 ); | ||
t.is ( fastStringWidth ( '\u2937' ), 1 ); | ||
t.is ( fastStringWidth ( '\u27A4' ), 1 ); | ||
t.is ( fastStringWidth ( '\u2190' ), 1 ); | ||
t.is ( fastStringWidth ( '\u21d0' ), 1 ); | ||
// t.is ( fastStringWidth ( '\u2194' ), 2 ); | ||
t.is ( fastStringWidth ( '\u21d4' ), 1 ); | ||
t.is ( fastStringWidth ( '\u21ce' ), 1 ); | ||
t.is ( fastStringWidth ( '\u27f7' ), 1 ); | ||
t.is ( fastStringWidth ( '\u2192' ), 1 ); | ||
t.is ( fastStringWidth ( '\u21d2' ), 1 ); | ||
t.is ( fastStringWidth ( '\u21e8' ), 1 ); | ||
t.is ( fastStringWidth ( '\u2191' ), 1 ); | ||
t.is ( fastStringWidth ( '\u21C5' ), 1 ); | ||
// t.is ( fastStringWidth ( '\u2197' ), 2 ); | ||
t.is ( fastStringWidth ( '\u21cb' ), 1 ); | ||
t.is ( fastStringWidth ( '\u21cc' ), 1 ); | ||
t.is ( fastStringWidth ( '\u21c6' ), 1 ); | ||
t.is ( fastStringWidth ( '\u21c4' ), 1 ); | ||
t.is ( fastStringWidth ( '\u2217' ), 1 ); | ||
// t.is ( fastStringWidth ( '✔' ), 2 ); | ||
t.is ( fastStringWidth ( '\u2014' ), 1 ); | ||
t.is ( fastStringWidth ( '\u2022' ), 1 ); | ||
t.is ( fastStringWidth ( '\u2026' ), 1 ); | ||
t.is ( fastStringWidth ( '\u2013' ), 1 ); | ||
// t.is ( fastStringWidth ( '\u2709' ), 2 ); | ||
t.is ( fastStringWidth ( '\u2261' ), 1 ); | ||
t.is ( fastStringWidth ( '\u2691' ), 1 ); | ||
t.is ( fastStringWidth ( '\u2690' ), 1 ); | ||
t.is ( fastStringWidth ( '\u22EF' ), 1 ); | ||
t.is ( fastStringWidth ( '\u226A' ), 1 ); | ||
t.is ( fastStringWidth ( '\u226B' ), 1 ); | ||
t.is ( fastStringWidth ( '\u270E' ), 1 ); | ||
t.is ( fastStringWidth ( '\u00a0' ), 1 ); | ||
t.is ( fastStringWidth ( '\u2009' ), 1 ); | ||
t.is ( fastStringWidth ( '\u200A' ), 1 ); | ||
t.is ( fastStringWidth ( '\u274F' ), 1 ); | ||
t.is ( fastStringWidth ( '\u2750' ), 1 ); | ||
// t.is ( fastStringWidth ( '\u26a0' ), 2 ); | ||
t.is ( fastStringWidth ( '\u200b' ), 1 ); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
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
3
4348
1
9
42
1
+ Addedfast-string-truncated-width@1.2.1(transitive)