| 🛟 Updated. 28d5985 k33g 5h ago | 1 | // Wrapping prose to a width. Code is never wrapped — see Lines for why. |
| 2 | |
| 3 | package acp |
| 4 | |
| 5 | import "strings" |
| 6 | |
| 7 | // Wrap breaks text into lines no wider than width runes, at spaces where it |
| 8 | // can and mid-word when a word is longer than the line. |
| 9 | // |
| 10 | // Existing newlines are kept: they are the author's own breaks, and a reply |
| 11 | // laid out in paragraphs should stay in paragraphs. |
| 12 | func Wrap(text string, width int) []string { |
| 13 | if width < 1 { |
| 14 | return nil |
| 15 | } |
| 16 | |
| 17 | var out []string |
| 18 | for _, line := range strings.Split(text, "\n") { |
| 19 | out = append(out, wrapLine(line, width)...) |
| 20 | } |
| 21 | return out |
| 22 | } |
| 23 | |
| 24 | // wrapLine wraps one line, which always yields at least one — a blank line is |
| 25 | // a blank line, not nothing. |
| 26 | func wrapLine(line string, width int) []string { |
| 27 | runes := []rune(line) |
| 28 | if len(runes) <= width { |
| 29 | return []string{line} |
| 30 | } |
| 31 | |
| 32 | var out []string |
| 33 | for len(runes) > width { |
| 34 | at := breakPoint(runes, width) |
| 35 | out = append(out, strings.TrimRight(string(runes[:at]), " ")) |
| 36 | runes = runes[at:] |
| 37 | for len(runes) > 0 && runes[0] == ' ' { |
| 38 | runes = runes[1:] |
| 39 | } |
| 40 | } |
| 41 | if len(runes) > 0 { |
| 42 | out = append(out, string(runes)) |
| 43 | } |
| 44 | return out |
| 45 | } |
| 46 | |
| 47 | // breakPoint returns where to break a run of runes, preferring the last space |
| 48 | // that fits and falling back to the hard edge. |
| 49 | // |
| 50 | // The fallback is what stops a long path or a base64 blob from looping for |
| 51 | // ever, which a space-only rule would do. |
| 52 | func breakPoint(runes []rune, width int) int { |
| 53 | for at := width; at > 0; at-- { |
| 54 | if runes[at] == ' ' { |
| 55 | return at |
| 56 | } |
| 57 | } |
| 58 | return width |
| 59 | } |