mirror of
https://github.com/charmbracelet/lipgloss.git
synced 2024-08-16 17:30:40 +03:00
Generally run-of-the-mill directives to disable magic number and exhaustive switches. Reasonings should be self explanatory while reading the code and comments, where applicable.
176 lines
4.0 KiB
Go
176 lines
4.0 KiB
Go
package lipgloss
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
|
|
"github.com/muesli/reflow/ansi"
|
|
)
|
|
|
|
// JoinHorizontal is a utility function for horizontally joining two
|
|
// potentially multi-lined strings along a vertical axis. The first argument is
|
|
// the position, with 0 being all the way at the top and 1 being all the way
|
|
// at the bottom.
|
|
//
|
|
// If you just want to align to the left, right or center you may as well just
|
|
// use the helper constants Top, Center, and Bottom.
|
|
//
|
|
// Example:
|
|
//
|
|
// blockB := "...\n...\n..."
|
|
// blockA := "...\n...\n...\n...\n..."
|
|
//
|
|
// // Join 20% from the top
|
|
// str := lipgloss.JoinHorizontal(0.2, blockA, blockB)
|
|
//
|
|
// // Join on the top edge
|
|
// str := lipgloss.JoinHorizontal(lipgloss.Top, blockA, blockB)
|
|
func JoinHorizontal(pos Position, strs ...string) string {
|
|
if len(strs) == 0 {
|
|
return ""
|
|
}
|
|
if len(strs) == 1 {
|
|
return strs[0]
|
|
}
|
|
|
|
var (
|
|
// Groups of strings broken into multiple lines
|
|
blocks = make([][]string, len(strs))
|
|
|
|
// Max line widths for the above text blocks
|
|
maxWidths = make([]int, len(strs))
|
|
|
|
// Height of the tallest block
|
|
maxHeight int
|
|
)
|
|
|
|
// Break text blocks into lines and get max widths for each text block
|
|
for i, str := range strs {
|
|
blocks[i], maxWidths[i] = getLines(str)
|
|
if len(blocks[i]) > maxHeight {
|
|
maxHeight = len(blocks[i])
|
|
}
|
|
}
|
|
|
|
// Add extra lines to make each side the same height
|
|
for i := range blocks {
|
|
if len(blocks[i]) >= maxHeight {
|
|
continue
|
|
}
|
|
|
|
extraLines := make([]string, maxHeight-len(blocks[i]))
|
|
|
|
switch pos { //nolint:exhaustive
|
|
case Top:
|
|
blocks[i] = append(blocks[i], extraLines...)
|
|
|
|
case Bottom:
|
|
blocks[i] = append(extraLines, blocks[i]...)
|
|
|
|
default: // Somewhere in the middle
|
|
n := len(extraLines)
|
|
split := int(math.Round(float64(n) * pos.value()))
|
|
top := n - split
|
|
bottom := n - top
|
|
|
|
blocks[i] = append(extraLines[top:], blocks[i]...)
|
|
blocks[i] = append(blocks[i], extraLines[bottom:]...)
|
|
}
|
|
}
|
|
|
|
// Merge lines
|
|
var b strings.Builder
|
|
for i := range blocks[0] { // remember, all blocks have the same number of members now
|
|
for j, block := range blocks {
|
|
b.WriteString(block[i])
|
|
|
|
// Also make lines the same length
|
|
b.WriteString(strings.Repeat(" ", maxWidths[j]-ansi.PrintableRuneWidth(block[i])))
|
|
}
|
|
if i < len(blocks[0])-1 {
|
|
b.WriteRune('\n')
|
|
}
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// JoinVertical is a utility function for vertically joining two potentially
|
|
// multi-lined strings along a horizontal axis. The first argument is the
|
|
// position, with 0 being all the way to the left and 1 being all the way to
|
|
// the right.
|
|
//
|
|
// If you just want to align to the left, right or center you may as well just
|
|
// use the helper constants Left, Center, and Right.
|
|
//
|
|
// Example:
|
|
//
|
|
// blockB := "...\n...\n..."
|
|
// blockA := "...\n...\n...\n...\n..."
|
|
//
|
|
// // Join 20% from the top
|
|
// str := lipgloss.JoinVertical(0.2, blockA, blockB)
|
|
//
|
|
// // Join on the right edge
|
|
// str := lipgloss.JoinVertical(lipgloss.Right, blockA, blockB)
|
|
func JoinVertical(pos Position, strs ...string) string {
|
|
if len(strs) == 0 {
|
|
return ""
|
|
}
|
|
if len(strs) == 1 {
|
|
return strs[0]
|
|
}
|
|
|
|
var (
|
|
blocks = make([][]string, len(strs))
|
|
maxWidth int
|
|
)
|
|
|
|
for i := range strs {
|
|
var w int
|
|
blocks[i], w = getLines(strs[i])
|
|
if w > maxWidth {
|
|
maxWidth = w
|
|
}
|
|
}
|
|
|
|
var b strings.Builder
|
|
for i, block := range blocks {
|
|
for j, line := range block {
|
|
w := maxWidth - ansi.PrintableRuneWidth(line)
|
|
|
|
switch pos { //nolint:exhaustive
|
|
case Left:
|
|
b.WriteString(line)
|
|
b.WriteString(strings.Repeat(" ", w))
|
|
|
|
case Right:
|
|
b.WriteString(strings.Repeat(" ", w))
|
|
b.WriteString(line)
|
|
|
|
default: // Somewhere in the middle
|
|
if w < 1 {
|
|
b.WriteString(line)
|
|
break
|
|
}
|
|
|
|
split := int(math.Round(float64(w) * pos.value()))
|
|
right := w - split
|
|
left := w - right
|
|
|
|
b.WriteString(strings.Repeat(" ", left))
|
|
b.WriteString(line)
|
|
b.WriteString(strings.Repeat(" ", right))
|
|
}
|
|
|
|
// Write a newline as long as we're not on the last line of the
|
|
// last block.
|
|
if !(i == len(blocks)-1 && j == len(block)-1) {
|
|
b.WriteRune('\n')
|
|
}
|
|
}
|
|
}
|
|
|
|
return b.String()
|
|
}
|