summaryrefslogtreecommitdiffstats
path: root/pkg/utils/formatting.go
blob: 657d1d2ebbf6cbd1ce25bfe35c88ee449d9afa34 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package utils

import (
	"strings"

	"github.com/jesseduffield/generics/slices"
	"github.com/mattn/go-runewidth"
	"github.com/samber/lo"
)

// WithPadding pads a string as much as you want
func WithPadding(str string, padding int) string {
	uncoloredStr := Decolorise(str)
	width := runewidth.StringWidth(uncoloredStr)
	if padding < width {
		return str
	}
	return str + strings.Repeat(" ", padding-width)
}

func RenderDisplayStrings(displayStringsArr [][]string) string {
	displayStringsArr = excludeBlankColumns(displayStringsArr)
	padWidths := getPadWidths(displayStringsArr)
	output := getPaddedDisplayStrings(displayStringsArr, padWidths)

	return output
}

// NOTE: this mutates the input slice for the sake of performance
func excludeBlankColumns(displayStringsArr [][]string) [][]string {
	if len(displayStringsArr) == 0 {
		return displayStringsArr
	}

	// if all rows share a blank column, we want to remove that column
	toRemove := []int{}
outer:
	for i := range displayStringsArr[0] {
		for _, strings := range displayStringsArr {
			if strings[i] != "" {
				continue outer
			}
		}
		toRemove = append(toRemove, i)
	}

	if len(toRemove) == 0 {
		return displayStringsArr
	}

	// remove the columns
	for i, strings := range displayStringsArr {
		for j := len(toRemove) - 1; j >= 0; j-- {
			strings = append(strings[:toRemove[j]], strings[toRemove[j]+1:]...)
		}
		displayStringsArr[i] = strings
	}

	return displayStringsArr
}

func getPaddedDisplayStrings(stringArrays [][]string, padWidths []int) string {
	builder := strings.Builder{}
	for i, stringArray := range stringArrays {
		if len(stringArray) == 0 {
			continue
		}
		for j, padWidth := range padWidths {
			if len(stringArray)-1 < j {
				continue
			}
			builder.WriteString(WithPadding(stringArray[j], padWidth))
			builder.WriteString(" ")
		}
		if len(stringArray)-1 < len(padWidths) {
			continue
		}
		builder.WriteString(stringArray[len(padWidths)])

		if i < len(stringArrays)-1 {
			builder.WriteString("\n")
		}
	}
	return builder.String()
}

func getPadWidths(stringArrays [][]string) []int {
	maxWidth := slices.MaxBy(stringArrays, func(stringArray []string) int {
		return len(stringArray)
	})

	if maxWidth-1 < 0 {
		return []int{}
	}
	return slices.Map(lo.Range(maxWidth-1), func(i int) int {
		return slices.MaxBy(stringArrays, func(stringArray []string) int {
			uncoloredStr := Decolorise(stringArray[i])

			return runewidth.StringWidth(uncoloredStr)
		})
	})
}

// TruncateWithEllipsis returns a string, truncated to a certain length, with an ellipsis
func TruncateWithEllipsis(str string, limit int) string {
	if runewidth.StringWidth(str) > limit && limit <= 3 {
		return strings.Repeat(".", limit)
	}
	return runewidth.Truncate(str, limit, "...")
}

func SafeTruncate(str string, limit int) string {
	if len(str) > limit {
		return str[0:limit]
	} else {
		return str
	}
}

func ShortSha(sha string) string {
	if len(sha) < 8 {
		return sha
	}
	return sha[:8]
}