1package formatter
2
3import (
4	"unicode/utf8"
5
6	"golang.org/x/text/width"
7)
8
9// charWidth returns the number of horizontal positions a character occupies,
10// and is used to account for wide characters when displaying strings.
11//
12// In a broad sense, wide characters include East Asian Wide, East Asian Full-width,
13// (when not in East Asian context) see http://unicode.org/reports/tr11/.
14func charWidth(r rune) int {
15	switch width.LookupRune(r).Kind() {
16	case width.EastAsianWide, width.EastAsianFullwidth:
17		return 2
18	default:
19		return 1
20	}
21}
22
23// Ellipsis truncates a string to fit within maxDisplayWidth, and appends ellipsis (…).
24// For maxDisplayWidth of 1 and lower, no ellipsis is appended.
25// For maxDisplayWidth of 1, first char of string will return even if its width > 1.
26func Ellipsis(s string, maxDisplayWidth int) string {
27	if maxDisplayWidth <= 0 {
28		return ""
29	}
30	rs := []rune(s)
31	if maxDisplayWidth == 1 {
32		return string(rs[0])
33	}
34
35	byteLen := len(s)
36	if byteLen == utf8.RuneCountInString(s) {
37		if byteLen <= maxDisplayWidth {
38			return s
39		}
40		return string(rs[:maxDisplayWidth-1]) + "…"
41	}
42
43	var (
44		display      []int
45		displayWidth int
46	)
47	for _, r := range rs {
48		cw := charWidth(r)
49		displayWidth += cw
50		display = append(display, displayWidth)
51	}
52	if displayWidth <= maxDisplayWidth {
53		return s
54	}
55	for i := range display {
56		if display[i] <= maxDisplayWidth-1 && display[i+1] > maxDisplayWidth-1 {
57			return string(rs[:i+1]) + "…"
58		}
59	}
60	return s
61}
62