// Wrapping prose to a width. Code is never wrapped — see Lines for why. package acp import "strings" // Wrap breaks text into lines no wider than width runes, at spaces where it // can and mid-word when a word is longer than the line. // // Existing newlines are kept: they are the author's own breaks, and a reply // laid out in paragraphs should stay in paragraphs. func Wrap(text string, width int) []string { if width < 1 { return nil } var out []string for _, line := range strings.Split(text, "\n") { out = append(out, wrapLine(line, width)...) } return out } // wrapLine wraps one line, which always yields at least one — a blank line is // a blank line, not nothing. func wrapLine(line string, width int) []string { runes := []rune(line) if len(runes) <= width { return []string{line} } var out []string for len(runes) > width { at := breakPoint(runes, width) out = append(out, strings.TrimRight(string(runes[:at]), " ")) runes = runes[at:] for len(runes) > 0 && runes[0] == ' ' { runes = runes[1:] } } if len(runes) > 0 { out = append(out, string(runes)) } return out } // breakPoint returns where to break a run of runes, preferring the last space // that fits and falling back to the hard edge. // // The fallback is what stops a long path or a base64 blob from looping for // ever, which a space-only rule would do. func breakPoint(runes []rune, width int) int { for at := width; at > 0; at-- { if runes[at] == ' ' { return at } } return width }