mirror of
https://github.com/charmbracelet/lipgloss.git
synced 2024-08-17 18:00:46 +03:00
* feat: Style.Transform for altering strings at render time * feat: add `UnsetTransform` --------- Co-authored-by: Maas Lalani <maas@lalani.dev>
671 lines
18 KiB
Go
671 lines
18 KiB
Go
package lipgloss
|
|
|
|
// This could (should) probably just be moved into NewStyle(). We've broken it
|
|
// out, so we can call it in a lazy way.
|
|
func (s *Style) init() {
|
|
if s.rules == nil {
|
|
s.rules = make(rules)
|
|
}
|
|
}
|
|
|
|
// Set a value on the underlying rules map.
|
|
func (s *Style) set(key propKey, value interface{}) {
|
|
s.init()
|
|
|
|
switch v := value.(type) {
|
|
case int:
|
|
// TabWidth is the only property that may have a negative value (and
|
|
// that negative value can be no less than -1).
|
|
if key == tabWidthKey {
|
|
s.rules[key] = v
|
|
break
|
|
}
|
|
|
|
// We don't allow negative integers on any of our other values, so just keep
|
|
// them at zero or above. We could use uints instead, but the
|
|
// conversions are a little tedious, so we're sticking with ints for
|
|
// sake of usability.
|
|
s.rules[key] = max(0, v)
|
|
default:
|
|
s.rules[key] = v
|
|
}
|
|
}
|
|
|
|
// Bold sets a bold formatting rule.
|
|
func (s Style) Bold(v bool) Style {
|
|
s.set(boldKey, v)
|
|
return s
|
|
}
|
|
|
|
// Italic sets an italic formatting rule. In some terminal emulators this will
|
|
// render with "reverse" coloring if not italic font variant is available.
|
|
func (s Style) Italic(v bool) Style {
|
|
s.set(italicKey, v)
|
|
return s
|
|
}
|
|
|
|
// Underline sets an underline rule. By default, underlines will not be drawn on
|
|
// whitespace like margins and padding. To change this behavior set
|
|
// UnderlineSpaces.
|
|
func (s Style) Underline(v bool) Style {
|
|
s.set(underlineKey, v)
|
|
return s
|
|
}
|
|
|
|
// Strikethrough sets a strikethrough rule. By default, strikes will not be
|
|
// drawn on whitespace like margins and padding. To change this behavior set
|
|
// StrikethroughSpaces.
|
|
func (s Style) Strikethrough(v bool) Style {
|
|
s.set(strikethroughKey, v)
|
|
return s
|
|
}
|
|
|
|
// Reverse sets a rule for inverting foreground and background colors.
|
|
func (s Style) Reverse(v bool) Style {
|
|
s.set(reverseKey, v)
|
|
return s
|
|
}
|
|
|
|
// Blink sets a rule for blinking foreground text.
|
|
func (s Style) Blink(v bool) Style {
|
|
s.set(blinkKey, v)
|
|
return s
|
|
}
|
|
|
|
// Faint sets a rule for rendering the foreground color in a dimmer shade.
|
|
func (s Style) Faint(v bool) Style {
|
|
s.set(faintKey, v)
|
|
return s
|
|
}
|
|
|
|
// Foreground sets a foreground color.
|
|
//
|
|
// // Sets the foreground to blue
|
|
// s := lipgloss.NewStyle().Foreground(lipgloss.Color("#0000ff"))
|
|
//
|
|
// // Removes the foreground color
|
|
// s.Foreground(lipgloss.NoColor)
|
|
func (s Style) Foreground(c TerminalColor) Style {
|
|
s.set(foregroundKey, c)
|
|
return s
|
|
}
|
|
|
|
// Background sets a background color.
|
|
func (s Style) Background(c TerminalColor) Style {
|
|
s.set(backgroundKey, c)
|
|
return s
|
|
}
|
|
|
|
// Width sets the width of the block before applying margins. The width, if
|
|
// set, also determines where text will wrap.
|
|
func (s Style) Width(i int) Style {
|
|
s.set(widthKey, i)
|
|
return s
|
|
}
|
|
|
|
// Height sets the height of the block before applying margins. If the height of
|
|
// the text block is less than this value after applying padding (or not), the
|
|
// block will be set to this height.
|
|
func (s Style) Height(i int) Style {
|
|
s.set(heightKey, i)
|
|
return s
|
|
}
|
|
|
|
// Align is a shorthand method for setting horizontal and vertical alignment.
|
|
//
|
|
// With one argument, the position value is applied to the horizontal alignment.
|
|
//
|
|
// With two arguments, the value is applied to the horizontal and vertical
|
|
// alignments, in that order.
|
|
func (s Style) Align(p ...Position) Style {
|
|
if len(p) > 0 {
|
|
s.set(alignHorizontalKey, p[0])
|
|
}
|
|
if len(p) > 1 {
|
|
s.set(alignVerticalKey, p[1])
|
|
}
|
|
return s
|
|
}
|
|
|
|
// AlignHorizontal sets a horizontal text alignment rule.
|
|
func (s Style) AlignHorizontal(p Position) Style {
|
|
s.set(alignHorizontalKey, p)
|
|
return s
|
|
}
|
|
|
|
// AlignVertical sets a vertical text alignment rule.
|
|
func (s Style) AlignVertical(p Position) Style {
|
|
s.set(alignVerticalKey, p)
|
|
return s
|
|
}
|
|
|
|
// Padding is a shorthand method for setting padding on all sides at once.
|
|
//
|
|
// With one argument, the value is applied to all sides.
|
|
//
|
|
// With two arguments, the value is applied to the vertical and horizontal
|
|
// sides, in that order.
|
|
//
|
|
// With three arguments, the value is applied to the top side, the horizontal
|
|
// sides, and the bottom side, in that order.
|
|
//
|
|
// With four arguments, the value is applied clockwise starting from the top
|
|
// side, followed by the right side, then the bottom, and finally the left.
|
|
//
|
|
// With more than four arguments no padding will be added.
|
|
func (s Style) Padding(i ...int) Style {
|
|
top, right, bottom, left, ok := whichSidesInt(i...)
|
|
if !ok {
|
|
return s
|
|
}
|
|
|
|
s.set(paddingTopKey, top)
|
|
s.set(paddingRightKey, right)
|
|
s.set(paddingBottomKey, bottom)
|
|
s.set(paddingLeftKey, left)
|
|
return s
|
|
}
|
|
|
|
// PaddingLeft adds padding on the left.
|
|
func (s Style) PaddingLeft(i int) Style {
|
|
s.set(paddingLeftKey, i)
|
|
return s
|
|
}
|
|
|
|
// PaddingRight adds padding on the right.
|
|
func (s Style) PaddingRight(i int) Style {
|
|
s.set(paddingRightKey, i)
|
|
return s
|
|
}
|
|
|
|
// PaddingTop adds padding to the top of the block.
|
|
func (s Style) PaddingTop(i int) Style {
|
|
s.set(paddingTopKey, i)
|
|
return s
|
|
}
|
|
|
|
// PaddingBottom adds padding to the bottom of the block.
|
|
func (s Style) PaddingBottom(i int) Style {
|
|
s.set(paddingBottomKey, i)
|
|
return s
|
|
}
|
|
|
|
// ColorWhitespace determines whether or not the background color should be
|
|
// applied to the padding. This is true by default as it's more than likely the
|
|
// desired and expected behavior, but it can be disabled for certain graphic
|
|
// effects.
|
|
func (s Style) ColorWhitespace(v bool) Style {
|
|
s.set(colorWhitespaceKey, v)
|
|
return s
|
|
}
|
|
|
|
// Margin is a shorthand method for setting margins on all sides at once.
|
|
//
|
|
// With one argument, the value is applied to all sides.
|
|
//
|
|
// With two arguments, the value is applied to the vertical and horizontal
|
|
// sides, in that order.
|
|
//
|
|
// With three arguments, the value is applied to the top side, the horizontal
|
|
// sides, and the bottom side, in that order.
|
|
//
|
|
// With four arguments, the value is applied clockwise starting from the top
|
|
// side, followed by the right side, then the bottom, and finally the left.
|
|
//
|
|
// With more than four arguments no margin will be added.
|
|
func (s Style) Margin(i ...int) Style {
|
|
top, right, bottom, left, ok := whichSidesInt(i...)
|
|
if !ok {
|
|
return s
|
|
}
|
|
|
|
s.set(marginTopKey, top)
|
|
s.set(marginRightKey, right)
|
|
s.set(marginBottomKey, bottom)
|
|
s.set(marginLeftKey, left)
|
|
return s
|
|
}
|
|
|
|
// MarginLeft sets the value of the left margin.
|
|
func (s Style) MarginLeft(i int) Style {
|
|
s.set(marginLeftKey, i)
|
|
return s
|
|
}
|
|
|
|
// MarginRight sets the value of the right margin.
|
|
func (s Style) MarginRight(i int) Style {
|
|
s.set(marginRightKey, i)
|
|
return s
|
|
}
|
|
|
|
// MarginTop sets the value of the top margin.
|
|
func (s Style) MarginTop(i int) Style {
|
|
s.set(marginTopKey, i)
|
|
return s
|
|
}
|
|
|
|
// MarginBottom sets the value of the bottom margin.
|
|
func (s Style) MarginBottom(i int) Style {
|
|
s.set(marginBottomKey, i)
|
|
return s
|
|
}
|
|
|
|
// MarginBackground sets the background color of the margin. Note that this is
|
|
// also set when inheriting from a style with a background color. In that case
|
|
// the background color on that style will set the margin color on this style.
|
|
func (s Style) MarginBackground(c TerminalColor) Style {
|
|
s.set(marginBackgroundKey, c)
|
|
return s
|
|
}
|
|
|
|
// Border is shorthand for setting the border style and which sides should
|
|
// have a border at once. The variadic argument sides works as follows:
|
|
//
|
|
// With one value, the value is applied to all sides.
|
|
//
|
|
// With two values, the values are applied to the vertical and horizontal
|
|
// sides, in that order.
|
|
//
|
|
// With three values, the values are applied to the top side, the horizontal
|
|
// sides, and the bottom side, in that order.
|
|
//
|
|
// With four values, the values are applied clockwise starting from the top
|
|
// side, followed by the right side, then the bottom, and finally the left.
|
|
//
|
|
// With more than four arguments the border will be applied to all sides.
|
|
//
|
|
// Examples:
|
|
//
|
|
// // Applies borders to the top and bottom only
|
|
// lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false)
|
|
//
|
|
// // Applies rounded borders to the right and bottom only
|
|
// lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), false, true, true, false)
|
|
func (s Style) Border(b Border, sides ...bool) Style {
|
|
s.set(borderStyleKey, b)
|
|
|
|
top, right, bottom, left, ok := whichSidesBool(sides...)
|
|
if !ok {
|
|
top = true
|
|
right = true
|
|
bottom = true
|
|
left = true
|
|
}
|
|
|
|
s.set(borderTopKey, top)
|
|
s.set(borderRightKey, right)
|
|
s.set(borderBottomKey, bottom)
|
|
s.set(borderLeftKey, left)
|
|
|
|
return s
|
|
}
|
|
|
|
// BorderStyle defines the Border on a style. A Border contains a series of
|
|
// definitions for the sides and corners of a border.
|
|
//
|
|
// Note that if border visibility has not been set for any sides when setting
|
|
// the border style, the border will be enabled for all sides during rendering.
|
|
//
|
|
// You can define border characters as you'd like, though several default
|
|
// styles are included: NormalBorder(), RoundedBorder(), BlockBorder(),
|
|
// OuterHalfBlockBorder(), InnerHalfBlockBorder(), ThickBorder(),
|
|
// and DoubleBorder().
|
|
//
|
|
// Example:
|
|
//
|
|
// lipgloss.NewStyle().BorderStyle(lipgloss.ThickBorder())
|
|
func (s Style) BorderStyle(b Border) Style {
|
|
s.set(borderStyleKey, b)
|
|
return s
|
|
}
|
|
|
|
// BorderTop determines whether or not to draw a top border.
|
|
func (s Style) BorderTop(v bool) Style {
|
|
s.set(borderTopKey, v)
|
|
return s
|
|
}
|
|
|
|
// BorderRight determines whether or not to draw a right border.
|
|
func (s Style) BorderRight(v bool) Style {
|
|
s.set(borderRightKey, v)
|
|
return s
|
|
}
|
|
|
|
// BorderBottom determines whether or not to draw a bottom border.
|
|
func (s Style) BorderBottom(v bool) Style {
|
|
s.set(borderBottomKey, v)
|
|
return s
|
|
}
|
|
|
|
// BorderLeft determines whether or not to draw a left border.
|
|
func (s Style) BorderLeft(v bool) Style {
|
|
s.set(borderLeftKey, v)
|
|
return s
|
|
}
|
|
|
|
// BorderForeground is a shorthand function for setting all of the
|
|
// foreground colors of the borders at once. The arguments work as follows:
|
|
//
|
|
// With one argument, the argument is applied to all sides.
|
|
//
|
|
// With two arguments, the arguments are applied to the vertical and horizontal
|
|
// sides, in that order.
|
|
//
|
|
// With three arguments, the arguments are applied to the top side, the
|
|
// horizontal sides, and the bottom side, in that order.
|
|
//
|
|
// With four arguments, the arguments are applied clockwise starting from the
|
|
// top side, followed by the right side, then the bottom, and finally the left.
|
|
//
|
|
// With more than four arguments nothing will be set.
|
|
func (s Style) BorderForeground(c ...TerminalColor) Style {
|
|
if len(c) == 0 {
|
|
return s
|
|
}
|
|
|
|
top, right, bottom, left, ok := whichSidesColor(c...)
|
|
if !ok {
|
|
return s
|
|
}
|
|
|
|
s.set(borderTopForegroundKey, top)
|
|
s.set(borderRightForegroundKey, right)
|
|
s.set(borderBottomForegroundKey, bottom)
|
|
s.set(borderLeftForegroundKey, left)
|
|
|
|
return s
|
|
}
|
|
|
|
// BorderTopForeground set the foreground color for the top of the border.
|
|
func (s Style) BorderTopForeground(c TerminalColor) Style {
|
|
s.set(borderTopForegroundKey, c)
|
|
return s
|
|
}
|
|
|
|
// BorderRightForeground sets the foreground color for the right side of the
|
|
// border.
|
|
func (s Style) BorderRightForeground(c TerminalColor) Style {
|
|
s.set(borderRightForegroundKey, c)
|
|
return s
|
|
}
|
|
|
|
// BorderBottomForeground sets the foreground color for the bottom of the
|
|
// border.
|
|
func (s Style) BorderBottomForeground(c TerminalColor) Style {
|
|
s.set(borderBottomForegroundKey, c)
|
|
return s
|
|
}
|
|
|
|
// BorderLeftForeground sets the foreground color for the left side of the
|
|
// border.
|
|
func (s Style) BorderLeftForeground(c TerminalColor) Style {
|
|
s.set(borderLeftForegroundKey, c)
|
|
return s
|
|
}
|
|
|
|
// BorderBackground is a shorthand function for setting all of the
|
|
// background colors of the borders at once. The arguments work as follows:
|
|
//
|
|
// With one argument, the argument is applied to all sides.
|
|
//
|
|
// With two arguments, the arguments are applied to the vertical and horizontal
|
|
// sides, in that order.
|
|
//
|
|
// With three arguments, the arguments are applied to the top side, the
|
|
// horizontal sides, and the bottom side, in that order.
|
|
//
|
|
// With four arguments, the arguments are applied clockwise starting from the
|
|
// top side, followed by the right side, then the bottom, and finally the left.
|
|
//
|
|
// With more than four arguments nothing will be set.
|
|
func (s Style) BorderBackground(c ...TerminalColor) Style {
|
|
if len(c) == 0 {
|
|
return s
|
|
}
|
|
|
|
top, right, bottom, left, ok := whichSidesColor(c...)
|
|
if !ok {
|
|
return s
|
|
}
|
|
|
|
s.set(borderTopBackgroundKey, top)
|
|
s.set(borderRightBackgroundKey, right)
|
|
s.set(borderBottomBackgroundKey, bottom)
|
|
s.set(borderLeftBackgroundKey, left)
|
|
|
|
return s
|
|
}
|
|
|
|
// BorderTopBackground sets the background color of the top of the border.
|
|
func (s Style) BorderTopBackground(c TerminalColor) Style {
|
|
s.set(borderTopBackgroundKey, c)
|
|
return s
|
|
}
|
|
|
|
// BorderRightBackground sets the background color of right side the border.
|
|
func (s Style) BorderRightBackground(c TerminalColor) Style {
|
|
s.set(borderRightBackgroundKey, c)
|
|
return s
|
|
}
|
|
|
|
// BorderBottomBackground sets the background color of the bottom of the
|
|
// border.
|
|
func (s Style) BorderBottomBackground(c TerminalColor) Style {
|
|
s.set(borderBottomBackgroundKey, c)
|
|
return s
|
|
}
|
|
|
|
// BorderLeftBackground set the background color of the left side of the
|
|
// border.
|
|
func (s Style) BorderLeftBackground(c TerminalColor) Style {
|
|
s.set(borderLeftBackgroundKey, c)
|
|
return s
|
|
}
|
|
|
|
// Inline makes rendering output one line and disables the rendering of
|
|
// margins, padding and borders. This is useful when you need a style to apply
|
|
// only to font rendering and don't want it to change any physical dimensions.
|
|
// It works well with Style.MaxWidth.
|
|
//
|
|
// Because this in intended to be used at the time of render, this method will
|
|
// not mutate the style and instead return a copy.
|
|
//
|
|
// Example:
|
|
//
|
|
// var userInput string = "..."
|
|
// var userStyle = text.Style{ /* ... */ }
|
|
// fmt.Println(userStyle.Inline(true).Render(userInput))
|
|
func (s Style) Inline(v bool) Style {
|
|
o := s.Copy()
|
|
o.set(inlineKey, v)
|
|
return o
|
|
}
|
|
|
|
// MaxWidth applies a max width to a given style. This is useful in enforcing
|
|
// a certain width at render time, particularly with arbitrary strings and
|
|
// styles.
|
|
//
|
|
// Because this in intended to be used at the time of render, this method will
|
|
// not mutate the style and instead return a copy.
|
|
//
|
|
// Example:
|
|
//
|
|
// var userInput string = "..."
|
|
// var userStyle = text.Style{ /* ... */ }
|
|
// fmt.Println(userStyle.MaxWidth(16).Render(userInput))
|
|
func (s Style) MaxWidth(n int) Style {
|
|
o := s.Copy()
|
|
o.set(maxWidthKey, n)
|
|
return o
|
|
}
|
|
|
|
// MaxHeight applies a max height to a given style. This is useful in enforcing
|
|
// a certain height at render time, particularly with arbitrary strings and
|
|
// styles.
|
|
//
|
|
// Because this in intended to be used at the time of render, this method will
|
|
// not mutate the style and instead returns a copy.
|
|
func (s Style) MaxHeight(n int) Style {
|
|
o := s.Copy()
|
|
o.set(maxHeightKey, n)
|
|
return o
|
|
}
|
|
|
|
// NoTabConversion can be passed to [Style.TabWidth] to disable the replacement
|
|
// of tabs with spaces at render time.
|
|
const NoTabConversion = -1
|
|
|
|
// TabWidth sets the number of spaces that a tab (/t) should be rendered as.
|
|
// When set to 0, tabs will be removed. To disable the replacement of tabs with
|
|
// spaces entirely, set this to [NoTabConversion].
|
|
//
|
|
// By default, tabs will be replaced with 4 spaces.
|
|
func (s Style) TabWidth(n int) Style {
|
|
if n <= -1 {
|
|
n = -1
|
|
}
|
|
s.set(tabWidthKey, n)
|
|
return s
|
|
}
|
|
|
|
// UnderlineSpaces determines whether to underline spaces between words. By
|
|
// default, this is true. Spaces can also be underlined without underlining the
|
|
// text itself.
|
|
func (s Style) UnderlineSpaces(v bool) Style {
|
|
s.set(underlineSpacesKey, v)
|
|
return s
|
|
}
|
|
|
|
// StrikethroughSpaces determines whether to apply strikethroughs to spaces
|
|
// between words. By default, this is true. Spaces can also be struck without
|
|
// underlining the text itself.
|
|
func (s Style) StrikethroughSpaces(v bool) Style {
|
|
s.set(strikethroughSpacesKey, v)
|
|
return s
|
|
}
|
|
|
|
// Transform applies a given function to a string at render time, allowing for
|
|
// the string being rendered to be manipuated.
|
|
//
|
|
// Example:
|
|
//
|
|
// s := NewStyle().Transform(strings.ToUpper)
|
|
// fmt.Println(s.Render("raow!") // "RAOW!"
|
|
func (s Style) Transform(fn func(string) string) Style {
|
|
s.set(transformKey, fn)
|
|
return s
|
|
}
|
|
|
|
// Renderer sets the renderer for the style. This is useful for changing the
|
|
// renderer for a style that is being used in a different context.
|
|
func (s Style) Renderer(r *Renderer) Style {
|
|
s.r = r
|
|
return s
|
|
}
|
|
|
|
// whichSidesInt is a helper method for setting values on sides of a block based
|
|
// on the number of arguments. It follows the CSS shorthand rules for blocks
|
|
// like margin, padding. and borders. Here are how the rules work:
|
|
//
|
|
// 0 args: do nothing
|
|
// 1 arg: all sides
|
|
// 2 args: top -> bottom
|
|
// 3 args: top -> horizontal -> bottom
|
|
// 4 args: top -> right -> bottom -> left
|
|
// 5+ args: do nothing.
|
|
func whichSidesInt(i ...int) (top, right, bottom, left int, ok bool) {
|
|
switch len(i) {
|
|
case 1:
|
|
top = i[0]
|
|
bottom = i[0]
|
|
left = i[0]
|
|
right = i[0]
|
|
ok = true
|
|
case 2: //nolint:gomnd
|
|
top = i[0]
|
|
bottom = i[0]
|
|
left = i[1]
|
|
right = i[1]
|
|
ok = true
|
|
case 3: //nolint:gomnd
|
|
top = i[0]
|
|
left = i[1]
|
|
right = i[1]
|
|
bottom = i[2]
|
|
ok = true
|
|
case 4: //nolint:gomnd
|
|
top = i[0]
|
|
right = i[1]
|
|
bottom = i[2]
|
|
left = i[3]
|
|
ok = true
|
|
}
|
|
return top, right, bottom, left, ok
|
|
}
|
|
|
|
// whichSidesBool is like whichSidesInt, except it operates on a series of
|
|
// boolean values. See the comment on whichSidesInt for details on how this
|
|
// works.
|
|
func whichSidesBool(i ...bool) (top, right, bottom, left bool, ok bool) {
|
|
switch len(i) {
|
|
case 1:
|
|
top = i[0]
|
|
bottom = i[0]
|
|
left = i[0]
|
|
right = i[0]
|
|
ok = true
|
|
case 2: //nolint:gomnd
|
|
top = i[0]
|
|
bottom = i[0]
|
|
left = i[1]
|
|
right = i[1]
|
|
ok = true
|
|
case 3: //nolint:gomnd
|
|
top = i[0]
|
|
left = i[1]
|
|
right = i[1]
|
|
bottom = i[2]
|
|
ok = true
|
|
case 4: //nolint:gomnd
|
|
top = i[0]
|
|
right = i[1]
|
|
bottom = i[2]
|
|
left = i[3]
|
|
ok = true
|
|
}
|
|
return top, right, bottom, left, ok
|
|
}
|
|
|
|
// whichSidesColor is like whichSides, except it operates on a series of
|
|
// boolean values. See the comment on whichSidesInt for details on how this
|
|
// works.
|
|
func whichSidesColor(i ...TerminalColor) (top, right, bottom, left TerminalColor, ok bool) {
|
|
switch len(i) {
|
|
case 1:
|
|
top = i[0]
|
|
bottom = i[0]
|
|
left = i[0]
|
|
right = i[0]
|
|
ok = true
|
|
case 2: //nolint:gomnd
|
|
top = i[0]
|
|
bottom = i[0]
|
|
left = i[1]
|
|
right = i[1]
|
|
ok = true
|
|
case 3: //nolint:gomnd
|
|
top = i[0]
|
|
left = i[1]
|
|
right = i[1]
|
|
bottom = i[2]
|
|
ok = true
|
|
case 4: //nolint:gomnd
|
|
top = i[0]
|
|
right = i[1]
|
|
bottom = i[2]
|
|
left = i[3]
|
|
ok = true
|
|
}
|
|
return top, right, bottom, left, ok
|
|
}
|