@@ -182,6 +182,43 @@ func TestParsePartialArgs(t *testing.T) {
182182 }
183183}
184184
185+ func BenchmarkWrapLines (b * testing.B ) {
186+ shortLine := "hello world"
187+ mediumLine := "This is a medium length string that will need wrapping for testing purposes."
188+ longLine := "This is a very long line that contains many characters and will need to be wrapped multiple times when displayed in a terminal with limited width."
189+ multiLine := "Line one here\n Line two is a bit longer and might wrap\n Line three\n Line four is the longest line in this test case"
190+
191+ b .Run ("short_no_wrap" , func (b * testing.B ) {
192+ for b .Loop () {
193+ WrapLines (shortLine , 80 )
194+ }
195+ })
196+
197+ b .Run ("short_wrap" , func (b * testing.B ) {
198+ for b .Loop () {
199+ WrapLines (shortLine , 5 )
200+ }
201+ })
202+
203+ b .Run ("medium" , func (b * testing.B ) {
204+ for b .Loop () {
205+ WrapLines (mediumLine , 30 )
206+ }
207+ })
208+
209+ b .Run ("long" , func (b * testing.B ) {
210+ for b .Loop () {
211+ WrapLines (longLine , 40 )
212+ }
213+ })
214+
215+ b .Run ("multiline" , func (b * testing.B ) {
216+ for b .Loop () {
217+ WrapLines (multiLine , 25 )
218+ }
219+ })
220+ }
221+
185222func TestWrapLines (t * testing.T ) {
186223 tests := []struct {
187224 name string
@@ -340,3 +377,243 @@ func TestWrapLines(t *testing.T) {
340377 })
341378 }
342379}
380+
381+ func TestTruncateText (t * testing.T ) {
382+ tests := []struct {
383+ name string
384+ text string
385+ maxWidth int
386+ expected string
387+ }{
388+ // Basic cases
389+ {
390+ name : "text within width" ,
391+ text : "hello" ,
392+ maxWidth : 10 ,
393+ expected : "hello" ,
394+ },
395+ {
396+ name : "text exactly at width" ,
397+ text : "hello" ,
398+ maxWidth : 5 ,
399+ expected : "hello" ,
400+ },
401+ {
402+ name : "text needs truncation" ,
403+ text : "hello world" ,
404+ maxWidth : 8 ,
405+ expected : "hello w…" ,
406+ },
407+ {
408+ name : "truncate to minimum" ,
409+ text : "hello" ,
410+ maxWidth : 2 ,
411+ expected : "h…" ,
412+ },
413+
414+ // Edge cases
415+ {
416+ name : "empty string" ,
417+ text : "" ,
418+ maxWidth : 10 ,
419+ expected : "" ,
420+ },
421+ {
422+ name : "width of 1 returns ellipsis only" ,
423+ text : "hello" ,
424+ maxWidth : 1 ,
425+ expected : "…" ,
426+ },
427+ {
428+ name : "zero width" ,
429+ text : "hello" ,
430+ maxWidth : 0 ,
431+ expected : "" ,
432+ },
433+ {
434+ name : "negative width" ,
435+ text : "hello" ,
436+ maxWidth : - 5 ,
437+ expected : "" ,
438+ },
439+ {
440+ name : "single character fits" ,
441+ text : "a" ,
442+ maxWidth : 1 ,
443+ expected : "a" ,
444+ },
445+ {
446+ name : "single character with larger width" ,
447+ text : "a" ,
448+ maxWidth : 10 ,
449+ expected : "a" ,
450+ },
451+
452+ // Unicode handling
453+ {
454+ name : "unicode within width" ,
455+ text : "héllo" ,
456+ maxWidth : 10 ,
457+ expected : "héllo" ,
458+ },
459+ {
460+ name : "unicode needs truncation" ,
461+ text : "héllo wörld" ,
462+ maxWidth : 8 ,
463+ expected : "héllo w…" ,
464+ },
465+ {
466+ name : "wide characters (CJK)" ,
467+ text : "你好世界" ,
468+ maxWidth : 5 ,
469+ expected : "你好…" ,
470+ },
471+ {
472+ name : "mixed ASCII and wide chars" ,
473+ text : "hello你好" ,
474+ maxWidth : 8 ,
475+ expected : "hello你…" ,
476+ },
477+
478+ // Special characters
479+ {
480+ name : "text with newlines" ,
481+ text : "hello\n world" ,
482+ maxWidth : 8 ,
483+ expected : "hello\n world" ,
484+ },
485+ }
486+
487+ for _ , tt := range tests {
488+ t .Run (tt .name , func (t * testing.T ) {
489+ t .Parallel ()
490+
491+ result := TruncateText (tt .text , tt .maxWidth )
492+ assert .Equal (t , tt .expected , result )
493+ })
494+ }
495+ }
496+
497+ func BenchmarkTruncateText (b * testing.B ) {
498+ // Test with various string lengths to demonstrate O(n) vs O(n²) improvement
499+ shortText := "hello world"
500+ mediumText := "This is a medium length string that needs truncation for testing purposes."
501+ longText := "This is a very long line that contains many characters and will need to be truncated. " +
502+ "It continues on and on with more and more text to really stress test the truncation algorithm. " +
503+ "We want to make sure the O(n) complexity improvement is significant for longer strings."
504+
505+ b .Run ("short" , func (b * testing.B ) {
506+ for b .Loop () {
507+ TruncateText (shortText , 8 )
508+ }
509+ })
510+
511+ b .Run ("medium" , func (b * testing.B ) {
512+ for b .Loop () {
513+ TruncateText (mediumText , 30 )
514+ }
515+ })
516+
517+ b .Run ("long" , func (b * testing.B ) {
518+ for b .Loop () {
519+ TruncateText (longText , 50 )
520+ }
521+ })
522+
523+ b .Run ("no_truncation_needed" , func (b * testing.B ) {
524+ for b .Loop () {
525+ TruncateText (shortText , 100 )
526+ }
527+ })
528+ }
529+
530+ func TestRuneWidth (t * testing.T ) {
531+ tests := []struct {
532+ name string
533+ r rune
534+ expected int
535+ }{
536+ // ASCII
537+ {"space" , ' ' , 1 },
538+ {"letter" , 'a' , 1 },
539+ {"digit" , '5' , 1 },
540+ {"tilde" , '~' , 1 },
541+
542+ // Control characters
543+ {"null" , '\x00' , 0 },
544+ {"tab" , '\t' , 0 },
545+ {"newline" , '\n' , 0 },
546+ {"carriage_return" , '\r' , 0 },
547+ {"escape" , '\x1b' , 0 },
548+ {"del" , '\x7f' , 0 },
549+
550+ // C1 control characters
551+ {"c1_start" , '\x80' , 0 },
552+ {"c1_end" , '\x9f' , 0 },
553+
554+ // Latin-1 Supplement
555+ {"nbsp" , '\xa0' , 1 },
556+ {"latin_e_acute" , 'é' , 1 },
557+ {"latin_n_tilde" , 'ñ' , 1 },
558+ {"latin_u_umlaut" , 'ü' , 1 },
559+
560+ // Latin Extended
561+ {"latin_ext_a" , 'ā' , 1 },
562+ {"latin_ext_b" , 'ƀ' , 1 },
563+
564+ // CJK (double width)
565+ {"cjk_chinese" , '你' , 2 },
566+ {"cjk_japanese" , 'あ' , 2 },
567+ {"cjk_korean" , '한' , 2 },
568+
569+ // Emoji (typically double width)
570+ {"emoji_globe" , '🌍' , 2 },
571+ }
572+
573+ for _ , tt := range tests {
574+ t .Run (tt .name , func (t * testing.T ) {
575+ t .Parallel ()
576+ result := runeWidth (tt .r )
577+ assert .Equal (t , tt .expected , result , "rune %q (U+%04X)" , tt .r , tt .r )
578+ })
579+ }
580+ }
581+
582+ func BenchmarkRuneWidth (b * testing.B ) {
583+ asciiRunes := []rune ("hello world this is a test string with only ascii" )
584+ latin1Runes := []rune ("héllo wörld naïve café résumé über señor" )
585+ mixedRunes := []rune ("hello 你好 world 世界 test テスト" )
586+ cjkRunes := []rune ("你好世界这是一个测试" )
587+
588+ b .Run ("ascii" , func (b * testing.B ) {
589+ for b .Loop () {
590+ for _ , r := range asciiRunes {
591+ _ = runeWidth (r )
592+ }
593+ }
594+ })
595+
596+ b .Run ("latin1" , func (b * testing.B ) {
597+ for b .Loop () {
598+ for _ , r := range latin1Runes {
599+ _ = runeWidth (r )
600+ }
601+ }
602+ })
603+
604+ b .Run ("mixed" , func (b * testing.B ) {
605+ for b .Loop () {
606+ for _ , r := range mixedRunes {
607+ _ = runeWidth (r )
608+ }
609+ }
610+ })
611+
612+ b .Run ("cjk" , func (b * testing.B ) {
613+ for b .Loop () {
614+ for _ , r := range cjkRunes {
615+ _ = runeWidth (r )
616+ }
617+ }
618+ })
619+ }
0 commit comments