@fuzdev/fuz_util 0.43.0 โ†’ 0.44.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/lib/string.ts CHANGED
@@ -97,3 +97,69 @@ export const strip_ansi = (str: string): string => str.replaceAll(/\x1B\[[0-9;]*
97
97
  */
98
98
  export const stringify = (value: unknown): string =>
99
99
  typeof value === 'bigint' ? value + 'n' : (JSON.stringify(value) ?? String(value)); // eslint-disable-line @typescript-eslint/no-unnecessary-condition
100
+
101
+ /**
102
+ * Calculate the display width of a string in terminal columns.
103
+ * - Strips ANSI escape codes (they have 0 width)
104
+ * - Emojis and other wide characters take 2 columns
105
+ * - Tab characters take 4 columns
106
+ * - Newlines and other control characters take 0 columns
107
+ * - Uses `Intl.Segmenter` to properly handle grapheme clusters (e.g., family emoji "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ")
108
+ */
109
+ export const string_display_width = (str: string): number => {
110
+ // Strip ANSI codes first (they have 0 display width)
111
+ const clean = strip_ansi(str);
112
+
113
+ let width = 0;
114
+ const segmenter = new Intl.Segmenter();
115
+ for (const {segment} of segmenter.segment(clean)) {
116
+ const code = segment.codePointAt(0)!;
117
+
118
+ // Handle control characters
119
+ if (code === 0x09) {
120
+ // Tab = 4 columns
121
+ width += 4;
122
+ continue;
123
+ }
124
+ if (code < 0x20 || (code >= 0x7f && code < 0xa0)) {
125
+ // Other control characters (including newline) = 0 width
126
+ continue;
127
+ }
128
+
129
+ // Emoji and other wide characters (rough heuristic)
130
+ // - Most emoji are in range 0x1F300-0x1FAFF
131
+ // - Some are in 0x2600-0x27BF (misc symbols)
132
+ // - CJK characters 0x4E00-0x9FFF also double-width
133
+ // - Grapheme clusters with multiple code points (like ZWJ sequences) are typically emoji
134
+ if (
135
+ segment.length > 1 || // Multi-codepoint graphemes (ZWJ sequences, etc.)
136
+ (code >= 0x1f300 && code <= 0x1faff) ||
137
+ (code >= 0x2600 && code <= 0x27bf) ||
138
+ (code >= 0x1f600 && code <= 0x1f64f) ||
139
+ (code >= 0x1f680 && code <= 0x1f6ff) ||
140
+ (code >= 0x4e00 && code <= 0x9fff) // CJK
141
+ ) {
142
+ width += 2;
143
+ } else {
144
+ width += 1;
145
+ }
146
+ }
147
+ return width;
148
+ };
149
+
150
+ /**
151
+ * Pad a string to a target display width (accounting for wide characters).
152
+ */
153
+ export const pad_width = (
154
+ str: string,
155
+ target_width: number,
156
+ align: 'left' | 'right' = 'left',
157
+ ): string => {
158
+ const current_width = string_display_width(str);
159
+ const padding = Math.max(0, target_width - current_width);
160
+ if (align === 'left') {
161
+ return str + ' '.repeat(padding);
162
+ } else {
163
+ return ' '.repeat(padding) + str;
164
+ }
165
+ };
package/src/lib/time.ts CHANGED
@@ -110,6 +110,11 @@ export const time_ns_to_sec = (ns: number): number => ns / TIME_NS_PER_SEC;
110
110
  */
111
111
  export type TimeUnit = 'ns' | 'us' | 'ms' | 's';
112
112
 
113
+ /**
114
+ * Display labels for time units (uses proper Unicode ฮผ for microseconds).
115
+ */
116
+ export const TIME_UNIT_DISPLAY: Record<TimeUnit, string> = {ns: 'ns', us: 'ฮผs', ms: 'ms', s: 's'};
117
+
113
118
  /**
114
119
  * Detect the best time unit for a set of nanosecond values.
115
120
  * Chooses the unit where most values fall in the range 1-9999.