mirror of
https://github.com/charmbracelet/lipgloss.git
synced 2024-08-17 09:50:31 +03:00
Initial commit. Add the nuts and bolts.
This commit is contained in:
commit
5c53233775
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Charmbracelet, Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
66
align.go
Normal file
66
align.go
Normal file
@ -0,0 +1,66 @@
|
||||
package lipgloss
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/muesli/reflow/ansi"
|
||||
)
|
||||
|
||||
type Align int
|
||||
|
||||
const (
|
||||
AlignLeft Align = iota
|
||||
AlignRight
|
||||
AlignCenter
|
||||
)
|
||||
|
||||
// Perform text alignment. If the string is multi-lined, we also make all lines
|
||||
// the same width by padding them with spaces.
|
||||
func align(s string, t Align) string {
|
||||
if strings.Count(s, "\n") == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
lines, widest := getLines(s)
|
||||
var b strings.Builder
|
||||
|
||||
for i, l := range lines {
|
||||
w := ansi.PrintableRuneWidth(l)
|
||||
|
||||
if n := widest - w; n > 0 {
|
||||
switch t {
|
||||
case AlignRight:
|
||||
l = strings.Repeat(" ", n) + l
|
||||
case AlignCenter:
|
||||
left := n / 2
|
||||
right := left + n%2 // note that we put the remainder on the right
|
||||
l = strings.Repeat(" ", left) + l + strings.Repeat(" ", right)
|
||||
default:
|
||||
l += strings.Repeat(" ", n)
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteString(l)
|
||||
if i < len(lines)-1 {
|
||||
b.WriteRune('\n')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Split a string into lines, additionally returning the size of the widest
|
||||
// line.
|
||||
func getLines(s string) (lines []string, widest int) {
|
||||
lines = strings.Split(s, "\n")
|
||||
|
||||
for _, l := range lines {
|
||||
w := ansi.PrintableRuneWidth(l)
|
||||
if widest < w {
|
||||
widest = w
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
58
color.go
Normal file
58
color.go
Normal file
@ -0,0 +1,58 @@
|
||||
package lipgloss
|
||||
|
||||
import "github.com/muesli/termenv"
|
||||
|
||||
// ColorType is an interface used in color specifications.
|
||||
type ColorType interface {
|
||||
value() string
|
||||
}
|
||||
|
||||
// NoColor is used to specify the absence of color styling. When this is active
|
||||
// foreground colors will be rendered with the terminal's default text color,
|
||||
// and background colors will not be drawn at all.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// color := NoColor
|
||||
//
|
||||
var NoColor = noColor{}
|
||||
|
||||
type noColor struct{}
|
||||
|
||||
func (n noColor) value() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Color specifies a color by hex or ANSI value. For example:
|
||||
//
|
||||
// ansiColor := Color("21")
|
||||
// hexColor := Color("#0000ff")
|
||||
//
|
||||
type Color string
|
||||
|
||||
func (c Color) value() string {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
// AdaptiveColor provides color alternatives for light and dark backgrounds.
|
||||
// The appropriate color with be returned based on the darkness of the terminal
|
||||
// background color determined at runtime.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// color := AdaptiveColor{Light: "#0000ff", Dark: "#000099"}
|
||||
//
|
||||
type AdaptiveColor struct {
|
||||
Light string
|
||||
Dark string
|
||||
}
|
||||
|
||||
func (a AdaptiveColor) value() string {
|
||||
if !darkBackgroundQueried {
|
||||
hasDarkBackground = termenv.HasDarkBackground()
|
||||
}
|
||||
if hasDarkBackground {
|
||||
return a.Dark
|
||||
}
|
||||
return a.Light
|
||||
}
|
8
go.mod
Normal file
8
go.mod
Normal file
@ -0,0 +1,8 @@
|
||||
module github.com/charmbracelet/lipgloss
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68
|
||||
github.com/muesli/termenv v0.7.4
|
||||
)
|
16
go.sum
Normal file
16
go.sum
Normal file
@ -0,0 +1,16 @@
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk=
|
||||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
||||
github.com/muesli/termenv v0.7.4 h1:/pBqvU5CpkY53tU0vVn+xgs2ZTX63aH5nY+SSps5Xa8=
|
||||
github.com/muesli/termenv v0.7.4/go.mod h1:pZ7qY9l3F7e5xsAOS0zCew2tME+p7bWeBkotCEcIIcc=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
278
style.go
Normal file
278
style.go
Normal file
@ -0,0 +1,278 @@
|
||||
package lipgloss
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/muesli/reflow/indent"
|
||||
"github.com/muesli/reflow/truncate"
|
||||
"github.com/muesli/reflow/wordwrap"
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
// ANSI reset sequence.
|
||||
const resetSeq = termenv.CSI + termenv.ResetSeq + "m"
|
||||
|
||||
var (
|
||||
// Cache termenv lookups
|
||||
color func(string) termenv.Color = termenv.ColorProfile().Color
|
||||
darkBackgroundQueried bool
|
||||
hasDarkBackground bool
|
||||
)
|
||||
|
||||
// Style describes formatting instructions for a given string.
|
||||
type Style struct {
|
||||
Bold bool
|
||||
Italic bool
|
||||
Underline bool
|
||||
Strikethrough bool
|
||||
Reverse bool
|
||||
Blink bool
|
||||
Faint bool
|
||||
Foreground ColorType
|
||||
Background ColorType
|
||||
|
||||
// If the string contains multiple lines, they'll wrap at this value.
|
||||
// Lines will also be padded with spaces so they'll all be the same width.
|
||||
Width int
|
||||
|
||||
// Text alignment.
|
||||
Align Align
|
||||
|
||||
// Padding. This will be colored according to the Background value if
|
||||
// StylePadding is true.
|
||||
LeftPadding int
|
||||
RightPadding int
|
||||
TopPadding int
|
||||
BottomPadding int
|
||||
|
||||
// Whether or not to apply styling to the padding. Most notably, this
|
||||
// determines whether or not the indent background color is styled.
|
||||
StylePadding bool
|
||||
|
||||
// Margins. These will never be colored.
|
||||
LeftMargin int
|
||||
RightMargin int
|
||||
TopMargin int
|
||||
BottomMargin int
|
||||
|
||||
// If set, we truncate lines at this value after all other style has been
|
||||
// applied. That is to say, the physical width of strings will not exceed
|
||||
// this value, so this can be handy when building user interfaces.
|
||||
MaxWidth int
|
||||
|
||||
// Whether or not to remove trailing spaces with no background color. By
|
||||
// default we leave them in.
|
||||
RenderClearTrailingSpaces bool
|
||||
|
||||
// Whether to draw underlines on spaces (like padding). We don't do this by
|
||||
// default as it's likely not what people want, but you can turn it on if
|
||||
// you so desire.
|
||||
RenderUnderlinesOnSpaces bool
|
||||
}
|
||||
|
||||
// Apply applies formatting to a given string.
|
||||
func (s Style) Apply(str string) string {
|
||||
return s.apply(str, false)
|
||||
}
|
||||
|
||||
// Apply as applies formatting to the given string, removing newlines and
|
||||
// skipping over block-level rules. Use this internally if you require an
|
||||
// inline-style only in your library or component.
|
||||
func (s Style) ApplyInline(str string) string {
|
||||
return s.apply(str, true)
|
||||
}
|
||||
|
||||
// WithMaxWidth applies a max width to a given style. This is useful in
|
||||
// enforcing a certain width at render time, particularly with aribtrary
|
||||
// strings and styles.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var userInput string = "..."
|
||||
// var userStyle = text.Style{ /* ... */ }
|
||||
// fmt.Println(userStyle.WithMaxWidth(16).Apply(userInput))
|
||||
//
|
||||
func (s Style) WithMaxWidth(n int) Style {
|
||||
s.MaxWidth = n
|
||||
return s
|
||||
}
|
||||
|
||||
func (s Style) apply(str string, singleLine bool) string {
|
||||
var (
|
||||
styler = termenv.Style{}
|
||||
|
||||
// A copy of the main termenv styler, but without underlines. Used to
|
||||
// not render underlines on spaces, if applicable. It's a pointer so
|
||||
// we can treat it like a maybe monad, since it won't always be
|
||||
// applicable.
|
||||
noUnderlineStyler *termenv.Style
|
||||
)
|
||||
|
||||
if s.Bold {
|
||||
styler = styler.Bold()
|
||||
}
|
||||
if s.Italic {
|
||||
styler = styler.Italic()
|
||||
}
|
||||
if s.Strikethrough {
|
||||
styler = styler.CrossOut()
|
||||
}
|
||||
if s.Reverse {
|
||||
styler = styler.Reverse()
|
||||
}
|
||||
if s.Blink {
|
||||
styler = styler.Blink()
|
||||
}
|
||||
if s.Faint {
|
||||
styler = styler.Faint()
|
||||
}
|
||||
|
||||
switch c := s.Foreground.(type) {
|
||||
case Color, AdaptiveColor:
|
||||
styler = styler.Foreground(color(c.value()))
|
||||
}
|
||||
|
||||
switch c := s.Background.(type) {
|
||||
case Color, AdaptiveColor:
|
||||
styler = styler.Background(color(c.value()))
|
||||
}
|
||||
|
||||
if s.Underline {
|
||||
if s.Underline && !s.RenderUnderlinesOnSpaces {
|
||||
stylerCopy := styler
|
||||
noUnderlineStyler = &stylerCopy
|
||||
}
|
||||
styler = styler.Underline()
|
||||
}
|
||||
|
||||
// Strip spaces in single line mode
|
||||
if singleLine {
|
||||
str = strings.Replace(str, "\n", "", -1)
|
||||
}
|
||||
|
||||
if !s.StylePadding {
|
||||
str = styler.Styled(str)
|
||||
}
|
||||
|
||||
// Word wrap
|
||||
if !singleLine && s.Width > 0 {
|
||||
str = wordwrap.String(str, s.Width-s.LeftPadding-s.RightPadding)
|
||||
}
|
||||
|
||||
// Is a background color set?
|
||||
backgroundColorSet := true
|
||||
switch s.Background.(type) {
|
||||
case nil, noColor:
|
||||
backgroundColorSet = false
|
||||
}
|
||||
|
||||
// Left/right padding
|
||||
str = padLeft(str, s.LeftPadding)
|
||||
if !s.RenderClearTrailingSpaces || backgroundColorSet {
|
||||
str = padRight(str, s.RightPadding, s.StylePadding)
|
||||
}
|
||||
|
||||
// Top/bottom padding
|
||||
if !singleLine && s.TopPadding > 0 {
|
||||
str = strings.Repeat("\n", s.TopPadding) + str
|
||||
}
|
||||
if !singleLine && s.BottomPadding > 0 {
|
||||
str += strings.Repeat("\n", s.BottomPadding)
|
||||
}
|
||||
|
||||
numLines := strings.Count(str, "\n")
|
||||
|
||||
// Set alignment. This will also pad short lines with spaces so that all
|
||||
// lines are the same length.
|
||||
if numLines > 0 && (!s.RenderClearTrailingSpaces || backgroundColorSet || s.Align != AlignLeft) {
|
||||
str = align(str, s.Align)
|
||||
}
|
||||
|
||||
if s.StylePadding {
|
||||
// We have to do some extra work to not render underlines on spaces
|
||||
if noUnderlineStyler != nil {
|
||||
var b strings.Builder
|
||||
|
||||
for _, c := range str {
|
||||
if unicode.IsSpace(c) {
|
||||
b.WriteString(noUnderlineStyler.Styled(string(c)))
|
||||
continue
|
||||
}
|
||||
b.WriteString(styler.Styled(string(c)))
|
||||
}
|
||||
|
||||
str = b.String()
|
||||
|
||||
} else {
|
||||
str = styler.Styled(str)
|
||||
}
|
||||
}
|
||||
|
||||
// Add left margin
|
||||
str = padLeft(str, s.LeftMargin)
|
||||
|
||||
// Add right margin
|
||||
if !s.RenderClearTrailingSpaces {
|
||||
str = padRight(str, s.RightMargin, false)
|
||||
}
|
||||
|
||||
// Top/bottom margin
|
||||
if !singleLine {
|
||||
var maybeSpaces string
|
||||
|
||||
if s.RenderClearTrailingSpaces {
|
||||
_, width := getLines(str)
|
||||
maybeSpaces = strings.Repeat(" ", width)
|
||||
}
|
||||
|
||||
if s.TopMargin > 0 {
|
||||
str = strings.Repeat(maybeSpaces+"\n", s.TopMargin) + str
|
||||
}
|
||||
if s.BottomMargin > 0 {
|
||||
str += strings.Repeat("\n"+maybeSpaces, s.BottomMargin) + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate accoridng to MaxWidth
|
||||
if s.MaxWidth > 0 {
|
||||
lines := strings.Split(str, "\n")
|
||||
|
||||
for i := range lines {
|
||||
lines[i] = truncate.String(lines[i], uint(s.MaxWidth))
|
||||
}
|
||||
|
||||
str = strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// Apply left padding.
|
||||
func padLeft(str string, n int) string {
|
||||
if n == 0 || str == "" {
|
||||
return str
|
||||
}
|
||||
return indent.String(str, uint(n))
|
||||
}
|
||||
|
||||
// Apply right right padding.
|
||||
func padRight(str string, n int, stylePadding bool) string {
|
||||
if n == 0 || str == "" {
|
||||
return str
|
||||
}
|
||||
|
||||
padding := strings.Repeat(" ", n)
|
||||
|
||||
var maybeReset string
|
||||
if !stylePadding {
|
||||
maybeReset = resetSeq
|
||||
}
|
||||
|
||||
lines := strings.Split(str, "\n")
|
||||
for i := range lines {
|
||||
lines[i] += maybeReset + padding
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
Loading…
Reference in New Issue
Block a user