pixl-cli 1.0.21 → 1.1.0

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.
Files changed (40) hide show
  1. package/agents/string-width/.editorconfig +12 -0
  2. package/agents/string-width/.gitattributes +1 -0
  3. package/agents/string-width/.github/security.md +3 -0
  4. package/agents/string-width/.github/workflows/main.yml +22 -0
  5. package/agents/string-width/index.d.ts +39 -0
  6. package/agents/string-width/index.js +207 -0
  7. package/agents/string-width/index.test-d.ts +7 -0
  8. package/agents/string-width/license +9 -0
  9. package/agents/string-width/package.json +65 -0
  10. package/agents/string-width/readme.md +66 -0
  11. package/agents/string-width/test.js +339 -0
  12. package/agents/widest-line/.editorconfig +12 -0
  13. package/agents/widest-line/.gitattributes +1 -0
  14. package/agents/widest-line/.github/security.md +3 -0
  15. package/agents/widest-line/.github/workflows/main.yml +21 -0
  16. package/agents/widest-line/index.d.ts +12 -0
  17. package/agents/widest-line/index.js +11 -0
  18. package/agents/widest-line/license +9 -0
  19. package/agents/widest-line/package.json +60 -0
  20. package/agents/widest-line/readme.md +26 -0
  21. package/agents/widest-line/test.js +8 -0
  22. package/agents/word-wrap/.editorconfig +13 -0
  23. package/agents/word-wrap/.eslintrc.json +122 -0
  24. package/agents/word-wrap/.gitattributes +10 -0
  25. package/agents/word-wrap/.github/workflows/publish.yml +19 -0
  26. package/agents/word-wrap/.travis.yml +13 -0
  27. package/agents/word-wrap/.verb.md +114 -0
  28. package/agents/word-wrap/LICENSE +21 -0
  29. package/agents/word-wrap/README.md +201 -0
  30. package/agents/word-wrap/bower.json +60 -0
  31. package/agents/word-wrap/index.d.ts +50 -0
  32. package/agents/word-wrap/index.js +61 -0
  33. package/agents/word-wrap/package.json +77 -0
  34. package/agents/word-wrap/test.js +69 -0
  35. package/cli.js +17 -10
  36. package/package.json +3 -8
  37. package/test.js +20 -0
  38. package/util.js +15 -0
  39. package/width.js +75 -0
  40. package/wrap.js +141 -0
@@ -0,0 +1,339 @@
1
+ import test from 'ava';
2
+ import stringWidth from './index.js';
3
+
4
+ const macro = test.macro((t, input, expected, options = {}) => {
5
+ t.is(stringWidth(input, options), expected);
6
+ });
7
+
8
+ // Basic functionality
9
+ test('empty string', macro, '', 0);
10
+ test('single ASCII character', macro, 'a', 1);
11
+ test('ASCII string', macro, 'hello world', 11);
12
+
13
+ // Edge cases for input validation
14
+ test('non-string input (number)', t => {
15
+ t.is(stringWidth(123), 0);
16
+ });
17
+ test('non-string input (null)', t => {
18
+ t.is(stringWidth(null), 0);
19
+ });
20
+ test('non-string input (undefined)', t => {
21
+ t.is(stringWidth(undefined), 0);
22
+ });
23
+
24
+ // East Asian width categories
25
+ test('full-width characters', macro, '你好', 4);
26
+ test('half-width characters', macro, 'hello', 5);
27
+ test('mixed width', macro, 'hello世界', 9);
28
+ test('repeated leading Hangul jamo in one grapheme cluster stay additive', macro, 'ᄀᄀ', 4);
29
+ test('long leading Hangul jamo cluster stays additive', macro, 'ᄀᄀᄀᄀᄀᄀ', 12);
30
+ test('decomposed Hangul syllable cluster', macro, '가', 2);
31
+ test('decomposed Hangul syllable cluster with trailing jamo', macro, '각', 2);
32
+ test('decomposed Hangul syllable cluster with variation selector', macro, '가\uFE0F', 2);
33
+ test('repeated leading jamo before vowel composes last pair', macro, 'ᄀ가', 4);
34
+ test('repeated vowel jamo after leading composes first pair', macro, '가ᅡ', 3);
35
+ test('repeated trailing jamo after syllable composes first three', macro, '각ᆨ', 3);
36
+ test('repeated leading Hangul jamo with variation selector stay additive', macro, 'ᄀᄀ\uFE0F', 4);
37
+ test('repeated leading Hangul jamo with trailing ZWJ stay additive', macro, 'ᄀᄀ\u200D', 4);
38
+ test('vowel-only Hangul jamo cluster stays additive', macro, 'ᅡᅡᅡ', 3);
39
+ test('trailing-only Hangul jamo cluster stays additive', macro, 'ᆨᆨ', 2);
40
+ test('single leading jamo alone is width 2', macro, 'ᄀ', 2);
41
+ test('single vowel jamo alone is width 1', macro, 'ᅡ', 1);
42
+ test('single trailing jamo alone is width 1', macro, 'ᆨ', 1);
43
+ test('vowel jamo before leading jamo does not compose', macro, 'ᅡᄀ', 3);
44
+ test('leading jamo followed by trailing jamo without vowel does not compose', macro, 'ᄀᆨ', 3);
45
+ test('extended leading jamo U+A960 + standard vowel composes to width 2', macro, 'ꥠᅡ', 2);
46
+ test('standard leading jamo + extended vowel U+D7B0 composes to width 2', macro, 'ᄀힰ', 2);
47
+ test('extended leading + standard vowel + standard trailing composes to width 2', macro, 'ꥠᅡᆨ', 2);
48
+ test('Hangul Compatibility Jamo is full-width via EAW', macro, 'ㄱ', 2);
49
+ test('two Hangul Compatibility Jamo do not compose', macro, 'ㄱㄱ', 4);
50
+ test('precomposed syllable 가 U+AC00', macro, '가', 2);
51
+ test('precomposed syllable 한 U+D55C', macro, '한', 2);
52
+ test('three precomposed syllables', macro, '한국어', 6);
53
+ test('leading jamo + precomposed syllable in one cluster', macro, 'ᄀ가', 4);
54
+
55
+ // Halfwidth Katakana with dakuten/handakuten (issue #55)
56
+ test('halfwidth kana voiced sound mark (ba)', macro, 'バ', 2);
57
+ test('halfwidth kana semi-voiced sound mark (pa)', macro, 'パ', 2);
58
+ test('halfwidth vu', macro, 'ヴ', 2);
59
+ test('halfwidth vu with prolonged sound', macro, 'ヴー', 3);
60
+ test('voiced sound mark alone', macro, '゙', 1);
61
+ test('semi-voiced sound mark alone', macro, '゚', 1);
62
+ test('halfwidth prolonged sound with kana', macro, 'カー', 2);
63
+ test('halfwidth kana with dakuten and prolonged mark', macro, 'ガー', 3);
64
+
65
+ // Ambiguous characters
66
+ test('ambiguous narrow (default)', macro, '±', 1);
67
+ test('ambiguous wide', macro, '±', 2, {ambiguousIsNarrow: false});
68
+
69
+ // Control characters (should be ignored)
70
+ test('null character', macro, '\u0000', 0);
71
+ test('tab character', macro, '\t', 0);
72
+ // Tabs are ignored by design (issue #45)
73
+ test('tab sandwich ASCII', macro, 'a\tb', 2);
74
+ test('multiple tabs between ASCII', macro, 'a\t\tb', 2);
75
+ test('leading tab before ASCII', macro, '\ta', 1);
76
+ test('trailing tab after ASCII', macro, 'a\t', 1);
77
+ test('only tabs', macro, '\t\t', 0);
78
+ test('newline character', macro, '\n', 0);
79
+ test('escape character', macro, '\u001B', 0);
80
+ test('control in text', macro, 'a\u0001b', 2);
81
+
82
+ // ANSI escape sequences
83
+ test('ANSI color codes', macro, '\u001B[31mred\u001B[0m', 3);
84
+ test('ANSI codes counted', macro, '\u001B[31m', 4, {countAnsiEscapeCodes: true});
85
+ test('complex ANSI sequence', macro, '\u001B]8;;https://example.com\u0007link\u001B]8;;\u0007', 4);
86
+
87
+ // Zero-width characters
88
+ test('zero-width space', macro, 'a\u200Bb', 2);
89
+ test('zero-width non-joiner', macro, 'a\u200Cb', 2);
90
+ test('zero-width joiner', macro, 'a\u200Db', 2);
91
+ test('zero-width no-break space', macro, 'a\uFEFFb', 2);
92
+
93
+ // Explicit ZWNJ/ZWJ cases (issue #4)
94
+ test('ZWNJ alone', macro, '\u200C', 0);
95
+ test('ZWJ alone', macro, '\u200D', 0);
96
+ test('Arabic with ZWNJ', macro, 'ب\u200Cه', 2);
97
+ test('multiple ZWNJ between ASCII', macro, 'a\u200C\u200Cb', 2);
98
+ test('Indic conjunct via ZWJ', macro, 'क्\u200Dष', 1);
99
+
100
+ // Combining characters
101
+ test('combining diacritical mark', macro, 'e\u0301', 1);
102
+ test('multiple combining marks', macro, 'e\u0301\u0302', 1);
103
+ test('combining marks only', macro, '\u0301\u0302', 0);
104
+ test('Tibetan combining mark', macro, 'ཟླ', 1);
105
+ test('enclosing mark', macro, 'a\u20DD', 1);
106
+ test('spacing mark alone', macro, '\u093E', 1);
107
+ test('spacing mark after base character', macro, '\u0915\u093E', 2);
108
+ test('pre-base spacing mark after base character', macro, '\u0915\u093F', 2);
109
+
110
+ // Surrogate pairs and high code points
111
+ test('emoji surrogate pair', macro, '😀', 2);
112
+ test('text with emoji', macro, 'a😀b', 4);
113
+
114
+ // Variation selectors
115
+ test('variation selector ignored', macro, 'a\uFE0F', 1);
116
+ test('emoji with variation selector', macro, '⚡\uFE0F', 2);
117
+
118
+ // Symbols that should remain narrow (issue #56)
119
+ test('black medium square', macro, '◼', 1);
120
+ test('warning sign', macro, '⚠', 1);
121
+ test('check mark', macro, '✔', 1);
122
+ test('heart suit', macro, '♥', 1);
123
+ test('female sign', macro, '♀', 1);
124
+ test('male sign', macro, '♂', 1);
125
+ test('warning sign emoji style', macro, '⚠\uFE0F', 2);
126
+ test('warning sign text style', macro, '⚠\uFE0E', 1);
127
+ test('check mark text style', macro, '✔\uFE0E', 1);
128
+
129
+ // Variation selector sequences (issue #42)
130
+ test('arrow with text variation', macro, '\u21A9\uFE0E', 1);
131
+ test('arrow with emoji variation', macro, '\u21A9\uFE0F', 2);
132
+ test('information source text variation', macro, '\u2139\uFE0E', 1);
133
+ test('information source emoji variation', macro, '\u2139\uFE0F', 2);
134
+ test('registered sign text variation', macro, '\u00AE\uFE0E', 1);
135
+ test('registered sign emoji variation', macro, '\u00AE\uFE0F', 2);
136
+ test('copyright sign text variation', macro, '\u00A9\uFE0E', 1);
137
+ test('copyright sign emoji variation', macro, '\u00A9\uFE0F', 2);
138
+
139
+ // Emoji and emoji-like sequences
140
+ test('basic emoji', macro, '😀', 2);
141
+ test('emoji with skin tone', macro, '👋🏽', 2);
142
+ test('ZWJ sequence', macro, '👨‍👩‍👧‍👦', 2);
143
+ test('keycap sequence', macro, '1️⃣', 2);
144
+ test('flag sequence', macro, '🇺🇸', 2);
145
+ test('tag sequence', macro, '🏴', 2);
146
+
147
+ // Regional indicators
148
+ test('single regional indicator', macro, '🇦', 1);
149
+ test('flag from two regional indicators', macro, '🇺🇸', 2);
150
+ test('three regional indicators', macro, '🇺🇸🇦', 3);
151
+
152
+ // Default ignorable code points
153
+ test('word joiner', macro, 'a\u2060b', 2);
154
+ test('function application', macro, 'a\u2061b', 2);
155
+
156
+ // Complex real-world cases
157
+ test('mixed script with emoji', macro, 'Hello 👋 世界', 13);
158
+ test('ANSI with emoji', macro, '\u001B[32m✅ Pass\u001B[0m', 7);
159
+ test('combining with full-width', macro, '東京\u0301', 4);
160
+
161
+ // Edge cases that could break
162
+ test('only combining marks', macro, '\u0300\u0301\u0302', 0);
163
+ test('only control characters', macro, '\u0000\u0001\u001B', 0);
164
+ test('only zero-width characters', macro, '\u200B\u200C\u200D', 0);
165
+ test('malformed surrogate', macro, '\uD800', 0);
166
+ test('mixed control and text', macro, 'a\u0000b\u0001c', 3);
167
+ test('double-width with combining', macro, '你\u0301好', 4);
168
+
169
+ // Performance edge cases
170
+ test('long ASCII string', macro, 'a'.repeat(1000), 1000);
171
+ test('long full-width string', macro, '你'.repeat(500), 1000);
172
+ test('mixed long string', macro, 'a你'.repeat(500), 1500);
173
+
174
+ // Variation selectors alone: zero width
175
+ test('VS16 alone', macro, '\uFE0F', 0);
176
+ test('VS15 alone', macro, '\uFE0E', 0);
177
+
178
+ // ZWJ appended to ASCII should not change width
179
+ test('ASCII with trailing ZWJ', macro, 'A\u200D\u200D', 1);
180
+
181
+ // Leading ZWJ should not steal the base width
182
+ test('leading ZWJ + ASCII', macro, '\u200DA', 1);
183
+ test('leading ZWJ + CJK', macro, '\u200D你', 2);
184
+
185
+ // Prepended Concatenation Mark (GB9b) should not affect width of following cluster
186
+ test('prepend + ASCII', macro, '\u0600A', 1);
187
+ test('prepend + CJK', macro, '\u0600你', 2);
188
+ test('prepend + emoji', macro, '\u0600😀', 2);
189
+
190
+ // Sequence consisting only of default ignorables should be zero
191
+ test('only default ignorables (tags)', macro, '\u{E0020}\u{E007F}', 0);
192
+
193
+ // Emoji coverage for RGI and related cases
194
+ // Case 1: Emoji with VS16 (should be double-width)
195
+ test('digit with VS16 (keycap base)', macro, '0\uFE0F', 1); // Digits/asterisk/pound are not RGI emoji
196
+ test('asterisk with VS16', macro, '*\uFE0F', 1);
197
+ test('pound with VS16', macro, '#\uFE0F', 1);
198
+ test('trademark with VS16', macro, '™\uFE0F', 2);
199
+ test('watch with VS16', macro, '⌚\uFE0F', 2);
200
+ test('phone with VS16', macro, '☎\uFE0F', 2);
201
+ test('keyboard with VS16', macro, '⌨\uFE0F', 2);
202
+ test('envelope with VS16', macro, '✉\uFE0F', 2);
203
+
204
+ // Case 2: Emoji_Presentation characters without VS15 (should be double-width)
205
+ test('face emoji (has Emoji_Presentation)', macro, '😀', 2);
206
+ test('heart emoji (has Emoji_Presentation)', macro, '❤️', 2);
207
+ test('fire emoji (has Emoji_Presentation)', macro, '🔥', 2);
208
+ test('rocket emoji (has Emoji_Presentation)', macro, '🚀', 2);
209
+ test('star emoji (has Emoji_Presentation)', macro, '⭐', 2);
210
+
211
+ // Case 3: Emoji_Presentation with VS15 (should be single-width)
212
+ test('heart with VS15 (text style)', macro, '❤\uFE0E', 1);
213
+ test('star with VS15 (text style)', macro, '⭐\uFE0E', 2); // Star with VS15 still renders as 2 in terminals
214
+
215
+ // Case 4: Multi-scalar meaningful sequences (should be double-width)
216
+ test('keycap sequence 0️⃣', macro, '0️⃣', 2);
217
+ test('keycap sequence 1️⃣', macro, '1️⃣', 2);
218
+ test('keycap sequence 9️⃣', macro, '9️⃣', 2);
219
+ test('keycap sequence *️⃣', macro, '*️⃣', 2);
220
+ test('keycap sequence #️⃣', macro, '#️⃣', 2);
221
+ test('flag emoji GB', macro, '🇬🇧', 2);
222
+ test('flag emoji JP', macro, '🇯🇵', 2);
223
+ test('skin tone modifier', macro, '👋🏻', 2);
224
+ test('skin tone modifier dark', macro, '👋🏿', 2);
225
+ test('couple with heart', macro, '👨‍❤️‍👨', 2);
226
+ test('woman technologist', macro, '👩‍💻', 2);
227
+ test('man health worker', macro, '👨‍⚕️', 2);
228
+
229
+ // Case 5: Non-emoji presentation without VS16 (should be single-width via EAW)
230
+ test('watch without VS', macro, '⌚', 2); // Watch is actually RGI emoji with Emoji_Presentation
231
+ test('phone without VS (not Emoji_Presentation)', macro, '☎', 1);
232
+ test('keyboard without VS (not Emoji_Presentation)', macro, '⌨', 1);
233
+ test('envelope without VS (not Emoji_Presentation)', macro, '✉', 1);
234
+
235
+ // Case 6: Edge cases for multi-scalar logic
236
+ test('single code point with VS15 (2 scalars but not meaningful)', macro, 'A\uFE0E', 1);
237
+ test('emoji with VS15 only (2 scalars, VS15 present)', macro, '⌚\uFE0E', 2); // Watch with VS15 still renders as 2
238
+ test('three code points with VS15 at end', macro, 'A👋\uFE0E', 3);
239
+
240
+ // Case 7: RGI emoji coverage
241
+ test('RGI emoji face', macro, '😊', 2);
242
+ test('RGI emoji hand', macro, '✋', 2);
243
+ test('RGI emoji animal', macro, '🐶', 2);
244
+ test('RGI emoji food', macro, '🍕', 2);
245
+ test('RGI emoji flag', macro, '🏁', 2);
246
+
247
+ // Case 8: Complex emoji sequences that test all branches
248
+ test('emoji with combining mark', macro, '😀\u0301', 2);
249
+ test('emoji with ZWJ and another emoji', macro, '😀\u200D😀', 2);
250
+ test('text character made emoji with VS16', macro, '↔\uFE0F', 2);
251
+ test('text character kept text with VS15', macro, '↔\uFE0E', 1);
252
+
253
+ // Case 9: Non-RGI sequences
254
+ test('non-RGI with VS16', macro, '〰\uFE0F', 2);
255
+ test('non-RGI multi-scalar', macro, '☎\uFE0F\u20E3', 1); // Not RGI emoji, counts as 1
256
+
257
+ // Case 10: VS15 on Emoji_Presentation remain width 2 in most terminals via EAW
258
+ test('hourglass with VS15', macro, '⌛\uFE0E', 2);
259
+ test('fast-forward with VS15', macro, '⏩\uFE0E', 2);
260
+ test('rewind with VS15', macro, '⏪\uFE0E', 2);
261
+ test('arrow double up with VS15', macro, '⏫\uFE0E', 2);
262
+ test('arrow double down with VS15', macro, '⏬\uFE0E', 2);
263
+ test('alarm clock with VS15', macro, '⏰\uFE0E', 2);
264
+ test('hourglass flowing sand with VS15', macro, '⏳\uFE0E', 2);
265
+ test('umbrella with rain with VS15', macro, '☔\uFE0E', 2);
266
+ test('hot beverage with VS15', macro, '☕\uFE0E', 2);
267
+ // Removed redundant duplicate of '⭐\uFE0E' test
268
+
269
+ // Other emoji that may not have Emoji_Presentation but are affected
270
+ test('sun with VS15', macro, '☀\uFE0E', 1);
271
+ test('cloud with VS15', macro, '☁\uFE0E', 1);
272
+ test('umbrella with VS15', macro, '☂\uFE0E', 1);
273
+ test('snowman with VS15', macro, '☃\uFE0E', 1);
274
+ test('comet with VS15', macro, '☄\uFE0E', 1);
275
+ test('black nib with VS15', macro, '✒\uFE0E', 1);
276
+ test('heavy check mark with VS15', macro, '✔\uFE0E', 1);
277
+
278
+ // More edge cases for single-scalar text characters (not emoji)
279
+ test('digit zero as plain text (not emoji)', macro, '0', 1);
280
+ test('digit one as plain text', macro, '1', 1);
281
+ test('asterisk as plain text', macro, '*', 1);
282
+ test('hash as plain text', macro, '#', 1);
283
+
284
+ // Unicode Format characters (non-default-ignorable)
285
+ test('Arabic number sign U+0600', macro, '\u0600', 0);
286
+ test('Arabic end of ayah U+06DD', macro, '\u06DD', 0);
287
+ test('Syriac abbreviation mark U+070F', macro, '\u070F', 0);
288
+
289
+ // Minimally-qualified/unqualified emoji sequences
290
+ // These are emoji sequences missing VS16 but should still be width 2
291
+ test('heart on fire (MQ)', macro, '\u2764\u200D\u{1F525}', 2); // ❤‍🔥
292
+ test('rainbow flag (MQ)', macro, '\u{1F3F3}\u200D\u{1F308}', 2); // 🏳‍🌈
293
+ test('transgender flag (MQ)', macro, '\u{1F3F3}\u200D\u26A7', 2); // 🏳‍⚧
294
+ test('broken chain (MQ)', macro, '\u26D3\u200D\u{1F4A5}', 2); // ⛓‍💥
295
+ test('eye in speech bubble (MQ)', macro, '\u{1F441}\u200D\u{1F5E8}', 2); // 👁‍🗨
296
+ test('man bouncing ball (MQ)', macro, '\u26F9\u200D\u2642', 2); // ⛹‍♂
297
+ test('woman bouncing ball (MQ)', macro, '\u26F9\u200D\u2640', 2); // ⛹‍♀
298
+ test('man detective (MQ)', macro, '\u{1F575}\u200D\u2642', 2); // 🕵‍♂
299
+ test('woman detective (MQ)', macro, '\u{1F575}\u200D\u2640', 2); // 🕵‍♀
300
+
301
+ // Unqualified keycap sequences (missing VS16)
302
+ test('keycap # (UQ)', macro, '#\u20E3', 2); // #⃣
303
+ test('keycap 0 (UQ)', macro, '0\u20E3', 2); // 0⃣
304
+ test('keycap * (UQ)', macro, '*\u20E3', 2); // *⃣
305
+
306
+ // Ensure invalid keycap sequences don't match
307
+ test('phone + keycap (invalid)', macro, '\u260E\uFE0F\u20E3', 1); // Not a valid keycap base
308
+
309
+ // Latin1 range (0xA0–0x2FF) — width 1, no segmenter or EAW lookup needed
310
+ test('non-breaking space U+00A0', macro, '\u00A0', 1);
311
+ test('Latin ñ U+00F1', macro, 'ñ', 1);
312
+ test('Latin ü U+00FC', macro, 'ü', 1);
313
+ test('Latin ÿ U+00FF', macro, 'ÿ', 1);
314
+ test('Latin1 in text', macro, 'café', 4);
315
+ test('Spacing Modifier U+02FF', macro, '\u02FF', 1);
316
+
317
+ // Soft hyphen (0xAD) — zero-width
318
+ test('soft hyphen U+00AD', macro, '\u00AD', 0);
319
+ test('soft hyphen in text', macro, 'a\u00ADb', 2);
320
+
321
+ // Combining diacritical boundary (0x300) — zero-width combining mark
322
+ test('combining grave U+0300 alone', macro, '\u0300', 0);
323
+ test('char + combining at 0x300 boundary', macro, 'a\u0300', 1);
324
+
325
+ // ASCII boundary characters
326
+ test('space U+0020 (lowest printable ASCII)', macro, ' ', 1);
327
+ test('tilde U+007E (highest printable ASCII)', macro, '~', 1);
328
+ test('DEL U+007F (just above printable ASCII)', macro, '\u007F', 0);
329
+ test('unit separator U+001F (just below printable ASCII)', macro, '\u001F', 0);
330
+
331
+ // Ambiguous characters
332
+ test('ambiguous in text (narrow default)', macro, '±×÷', 3);
333
+ test('ambiguous in text (wide)', macro, '±×÷', 6, {ambiguousIsNarrow: false});
334
+ test('ambiguous mixed with CJK', macro, '±你', 3);
335
+ test('ambiguous mixed with CJK (wide)', macro, '±你', 4, {ambiguousIsNarrow: false});
336
+
337
+ // `stripAnsi` guard: non-ANSI strings should not call `stripAnsi`
338
+ test('non-ASCII without ANSI escapes', macro, '你好世界', 8);
339
+ test('Latin1 without ANSI escapes', macro, 'résumé', 6);
@@ -0,0 +1,12 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = tab
5
+ end_of_line = lf
6
+ charset = utf-8
7
+ trim_trailing_whitespace = true
8
+ insert_final_newline = true
9
+
10
+ [*.yml]
11
+ indent_style = space
12
+ indent_size = 2
@@ -0,0 +1 @@
1
+ * text=auto eol=lf
@@ -0,0 +1,3 @@
1
+ # Security Policy
2
+
3
+ To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
@@ -0,0 +1,21 @@
1
+ name: CI
2
+ on:
3
+ - push
4
+ - pull_request
5
+ jobs:
6
+ test:
7
+ name: Node.js ${{ matrix.node-version }}
8
+ runs-on: ubuntu-latest
9
+ strategy:
10
+ fail-fast: false
11
+ matrix:
12
+ node-version:
13
+ - 24
14
+ - 20
15
+ steps:
16
+ - uses: actions/checkout@v6
17
+ - uses: actions/setup-node@v6
18
+ with:
19
+ node-version: ${{ matrix.node-version }}
20
+ - run: npm install
21
+ - run: npm test
@@ -0,0 +1,12 @@
1
+ /**
2
+ Get the visual width of the widest line in a string - the number of columns required to display it.
3
+
4
+ @example
5
+ ```
6
+ import widestLine from 'widest-line';
7
+
8
+ widestLine('古\n\u001B[1m@\u001B[22m');
9
+ //=> 2
10
+ ```
11
+ */
12
+ export default function widestLine(string: string): number;
@@ -0,0 +1,11 @@
1
+ import stringWidth from 'string-width';
2
+
3
+ export default function widestLine(string) {
4
+ let lineWidth = 0;
5
+
6
+ for (const line of string.split('\n')) {
7
+ lineWidth = Math.max(lineWidth, stringWidth(line));
8
+ }
9
+
10
+ return lineWidth;
11
+ }
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
4
+
5
+ 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:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ 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.
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "widest-line",
3
+ "version": "6.0.0",
4
+ "description": "Get the visual width of the widest line in a string - the number of columns required to display it",
5
+ "license": "MIT",
6
+ "repository": "sindresorhus/widest-line",
7
+ "funding": "https://github.com/sponsors/sindresorhus",
8
+ "author": {
9
+ "name": "Sindre Sorhus",
10
+ "email": "sindresorhus@gmail.com",
11
+ "url": "https://sindresorhus.com"
12
+ },
13
+ "type": "module",
14
+ "exports": {
15
+ "types": "./index.d.ts",
16
+ "default": "./index.js"
17
+ },
18
+ "sideEffects": false,
19
+ "engines": {
20
+ "node": ">=20"
21
+ },
22
+ "scripts": {
23
+ "test": "xo && ava"
24
+ },
25
+ "files": [
26
+ "index.js",
27
+ "index.d.ts"
28
+ ],
29
+ "keywords": [
30
+ "string",
31
+ "character",
32
+ "unicode",
33
+ "width",
34
+ "visual",
35
+ "column",
36
+ "columns",
37
+ "fullwidth",
38
+ "full-width",
39
+ "full",
40
+ "ansi",
41
+ "escape",
42
+ "codes",
43
+ "cli",
44
+ "command-line",
45
+ "terminal",
46
+ "console",
47
+ "cjk",
48
+ "chinese",
49
+ "japanese",
50
+ "korean",
51
+ "fixed-width"
52
+ ],
53
+ "dependencies": {
54
+ "string-width": "^8.1.0"
55
+ },
56
+ "devDependencies": {
57
+ "ava": "^6.4.1",
58
+ "xo": "^1.2.3"
59
+ }
60
+ }
@@ -0,0 +1,26 @@
1
+ # widest-line
2
+
3
+ > Get the visual width of the widest line in a string - the number of columns required to display it
4
+
5
+ Some Unicode characters are [fullwidth](https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms) and use double the normal width. [ANSI escape codes](http://en.wikipedia.org/wiki/ANSI_escape_code) are stripped and doesn't affect the width.
6
+
7
+ Useful to be able to know the maximum width a string will take up in the terminal.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ npm install widest-line
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```js
18
+ import widestLine from 'widest-line';
19
+
20
+ widestLine('古\n\u001B[1m@\u001B[22m');
21
+ //=> 2
22
+ ```
23
+
24
+ ## Related
25
+
26
+ - [string-width](https://github.com/sindresorhus/string-width) - Get the visual width of a string
@@ -0,0 +1,8 @@
1
+ import test from 'ava';
2
+ import widestLine from './index.js';
3
+
4
+ test('main', t => {
5
+ t.is(widestLine('a'), 1);
6
+ t.is(widestLine('a\nbe'), 2);
7
+ t.is(widestLine('古\n\u001B[1m@\u001B[22m'), 2);
8
+ });
@@ -0,0 +1,13 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = space
5
+ end_of_line = lf
6
+ charset = utf-8
7
+ indent_size = 2
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+
11
+ [{**/{actual,fixtures,expected,templates}/**,*.md}]
12
+ trim_trailing_whitespace = false
13
+ insert_final_newline = false
@@ -0,0 +1,122 @@
1
+ {
2
+ "ecmaFeatures": {
3
+ "modules": true,
4
+ "experimentalObjectRestSpread": true
5
+ },
6
+
7
+ "env": {
8
+ "browser": false,
9
+ "es6": true,
10
+ "node": true,
11
+ "mocha": true
12
+ },
13
+
14
+ "globals": {
15
+ "document": false,
16
+ "navigator": false,
17
+ "window": false
18
+ },
19
+
20
+ "rules": {
21
+ "accessor-pairs": 2,
22
+ "arrow-spacing": [2, { "before": true, "after": true }],
23
+ "block-spacing": [2, "always"],
24
+ "brace-style": [2, "1tbs", { "allowSingleLine": true }],
25
+ "comma-dangle": [2, "never"],
26
+ "comma-spacing": [2, { "before": false, "after": true }],
27
+ "comma-style": [2, "last"],
28
+ "constructor-super": 2,
29
+ "curly": [2, "multi-line"],
30
+ "dot-location": [2, "property"],
31
+ "eol-last": 2,
32
+ "eqeqeq": [2, "allow-null"],
33
+ "generator-star-spacing": [2, { "before": true, "after": true }],
34
+ "handle-callback-err": [2, "^(err|error)$" ],
35
+ "indent": [2, 2, { "SwitchCase": 1 }],
36
+ "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
37
+ "keyword-spacing": [2, { "before": true, "after": true }],
38
+ "new-cap": [2, { "newIsCap": true, "capIsNew": false }],
39
+ "new-parens": 2,
40
+ "no-array-constructor": 2,
41
+ "no-caller": 2,
42
+ "no-class-assign": 2,
43
+ "no-cond-assign": 2,
44
+ "no-const-assign": 2,
45
+ "no-control-regex": 2,
46
+ "no-debugger": 2,
47
+ "no-delete-var": 2,
48
+ "no-dupe-args": 2,
49
+ "no-dupe-class-members": 2,
50
+ "no-dupe-keys": 2,
51
+ "no-duplicate-case": 2,
52
+ "no-empty-character-class": 2,
53
+ "no-eval": 2,
54
+ "no-ex-assign": 2,
55
+ "no-extend-native": 2,
56
+ "no-extra-bind": 2,
57
+ "no-extra-boolean-cast": 2,
58
+ "no-extra-parens": [2, "functions"],
59
+ "no-fallthrough": 2,
60
+ "no-floating-decimal": 2,
61
+ "no-func-assign": 2,
62
+ "no-implied-eval": 2,
63
+ "no-inner-declarations": [2, "functions"],
64
+ "no-invalid-regexp": 2,
65
+ "no-irregular-whitespace": 2,
66
+ "no-iterator": 2,
67
+ "no-label-var": 2,
68
+ "no-labels": 2,
69
+ "no-lone-blocks": 2,
70
+ "no-mixed-spaces-and-tabs": 2,
71
+ "no-multi-spaces": 2,
72
+ "no-multi-str": 2,
73
+ "no-multiple-empty-lines": [2, { "max": 1 }],
74
+ "no-native-reassign": 0,
75
+ "no-negated-in-lhs": 2,
76
+ "no-new": 2,
77
+ "no-new-func": 2,
78
+ "no-new-object": 2,
79
+ "no-new-require": 2,
80
+ "no-new-wrappers": 2,
81
+ "no-obj-calls": 2,
82
+ "no-octal": 2,
83
+ "no-octal-escape": 2,
84
+ "no-proto": 0,
85
+ "no-redeclare": 2,
86
+ "no-regex-spaces": 2,
87
+ "no-return-assign": 2,
88
+ "no-self-compare": 2,
89
+ "no-sequences": 2,
90
+ "no-shadow-restricted-names": 2,
91
+ "no-spaced-func": 2,
92
+ "no-sparse-arrays": 2,
93
+ "no-this-before-super": 2,
94
+ "no-throw-literal": 2,
95
+ "no-trailing-spaces": 0,
96
+ "no-undef": 2,
97
+ "no-undef-init": 2,
98
+ "no-unexpected-multiline": 2,
99
+ "no-unneeded-ternary": [2, { "defaultAssignment": false }],
100
+ "no-unreachable": 2,
101
+ "no-unused-vars": [2, { "vars": "all", "args": "none" }],
102
+ "no-useless-call": 0,
103
+ "no-with": 2,
104
+ "one-var": [0, { "initialized": "never" }],
105
+ "operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }],
106
+ "padded-blocks": [0, "never"],
107
+ "quotes": [2, "single", "avoid-escape"],
108
+ "radix": 2,
109
+ "semi": [2, "always"],
110
+ "semi-spacing": [2, { "before": false, "after": true }],
111
+ "space-before-blocks": [2, "always"],
112
+ "space-before-function-paren": [2, "never"],
113
+ "space-in-parens": [2, "never"],
114
+ "space-infix-ops": 2,
115
+ "space-unary-ops": [2, { "words": true, "nonwords": false }],
116
+ "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }],
117
+ "use-isnan": 2,
118
+ "valid-typeof": 2,
119
+ "wrap-iife": [2, "any"],
120
+ "yoda": [2, "never"]
121
+ }
122
+ }
@@ -0,0 +1,10 @@
1
+ # Enforce Unix newlines
2
+ * text eol=lf
3
+
4
+ # binaries
5
+ *.ai binary
6
+ *.psd binary
7
+ *.jpg binary
8
+ *.gif binary
9
+ *.png binary
10
+ *.jpeg binary