Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

fast-string-width

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fast-string-width - npm Package Compare versions

Comparing version 1.0.4 to 1.0.5

6

dist/index.d.ts

@@ -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",

/* 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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc