Skip to content

fix: count runes, not bytes, when wrapping help text - #2511

Merged
Juneezee merged 1 commit into
urfave:mainfrom
Rohilalala:fix/wrap-runes
Nov 7, 2026
Merged

Juneezee merged 1 commit into
urfave:mainfrom
Rohilalala:fix/wrap-runes

Conversation

@Rohilalala

@Rohilalala Rohilalala commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

What type of PR is this?

  • bug

What this PR does / why we need it:

With wrapAt set, wrapLine measured lines and words in bytes, so help text with multi-byte characters wrapped before the configured column. At wrapAt 30, app - ändert die Größe für Überprüfung broke after "Größe"; it now breaks after "für". offset and the tabwriter that aligns help output already count runes (#2493), and this makes wrapping agree with them. Wide East Asian characters now count as one column each, as they do in the tabwriter, so text in those scripts can run past wrapAt.

Which issue(s) this PR fixes:

None.

Testing

TestWrapLineCountsRunes in help_test.go.

Release Notes

Help text wrapping counts characters instead of bytes, so non-ASCII usage text no longer wraps early.

@Rohilalala
Rohilalala requested a review from a team as a code owner October 28, 2026 11:11
Comment thread help.go Resolved
@abitrolly

Copy link
Copy Markdown
Contributor

LGTM.

@paoValle paoValle left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Counting runes matches textWidth and the tabwriter, so wrapping and alignment now agree for multi-byte text. Two things before this merges: the new test only covers offset 0, while the diff also moves the spaceLeft arithmetic in the offset path, so a case with offset 4 and two multi-byte words would pin what it changed. And runes are not columns: 日本語 is three runes and six cells, so CJK help text now under-wraps where it used to over-wrap. Consistent with the tabwriter, so not wrong, but the comment could say "runes, not display columns" and save the next reader the assumption about terminal width.

wrapLine measured lines and words with len, so text with multi-byte
characters wrapped well before the wrapAt column. Count runes, as offset
and the tabwriter that aligns the help output already do.

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>

@Juneezee Juneezee left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rebased this onto current main, applied abitrolly's suggestion, and updated the CJK sentence in the description. The suggestion computes wrapAt - offset once and doesn't change behavior.

I reproduced the example from the description on main, and the fix works. Changing any of the five rune counts back to len fails the new test. Offset only sets the line width, so I don't think the test needs an offset > 0 case.

On the CJK point above: wide characters now count as one column each. Korean usage text can run past wrapAt (the first line is 47 cells wide at wrapAt 30). Counting display width would need East Asian Width data, which the standard library doesn't have. offset and the tabwriter already count runes, so I'd keep this consistent with them:

cli/help.go

Lines 577 to 581 in d854bb4

// offset counts runes, not bytes, the same way the tabwriter that aligns
// the help output does.
func offset(input string, fixed int) int {
return utf8.RuneCountInString(input) + fixed
}

@Juneezee
Juneezee merged commit 6d5074b into urfave:main Nov 7, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants