pax_global_header 0000666 0000000 0000000 00000000064 14114427461 0014516 g ustar 00root root 0000000 0000000 52 comment=4f0d448967dbff9047863235ef3a4bf01a8174e2
lipgloss-0.4.0/ 0000775 0000000 0000000 00000000000 14114427461 0013353 5 ustar 00root root 0000000 0000000 lipgloss-0.4.0/.github/ 0000775 0000000 0000000 00000000000 14114427461 0014713 5 ustar 00root root 0000000 0000000 lipgloss-0.4.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14114427461 0016750 5 ustar 00root root 0000000 0000000 lipgloss-0.4.0/.github/workflows/build.yml 0000664 0000000 0000000 00000001115 14114427461 0020570 0 ustar 00root root 0000000 0000000 name: build
on: [push, pull_request]
jobs:
test:
strategy:
matrix:
go-version: [~1.13, ^1]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
env:
GO111MODULE: "on"
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Download Go modules
run: go mod download
- name: Build
run: go build -v ./...
- name: Test
run: go test ./...
lipgloss-0.4.0/.github/workflows/lint.yml 0000664 0000000 0000000 00000001056 14114427461 0020443 0 ustar 00root root 0000000 0000000 name: lint
on: [push, pull_request]
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
# Optional: golangci-lint command line arguments.
args: --issues-exit-code=0
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: show only new issues if it's a pull request. The default value is `false`.
only-new-issues: true
lipgloss-0.4.0/.golangci.yml 0000664 0000000 0000000 00000000712 14114427461 0015737 0 ustar 00root root 0000000 0000000 run:
tests: false
issues:
include:
- EXC0001
- EXC0005
- EXC0011
- EXC0012
- EXC0013
max-issues-per-linter: 0
max-same-issues: 0
linters:
enable:
- bodyclose
- dupl
- exportloopref
- goconst
- godot
- godox
- goimports
- goprintffuncname
- gosec
- ifshort
- misspell
- prealloc
- revive
- rowserrcheck
- sqlclosecheck
- unconvert
- unparam
- whitespace
lipgloss-0.4.0/LICENSE 0000664 0000000 0000000 00000002063 14114427461 0014361 0 ustar 00root root 0000000 0000000 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.
lipgloss-0.4.0/README.md 0000664 0000000 0000000 00000024276 14114427461 0014645 0 ustar 00root root 0000000 0000000 Lip Gloss
=========

Style definitions for nice terminal layouts. Built with TUIs in mind.

Lip Gloss takes an expressive, declarative approach to terminal rendering.
Users familiar with CSS will feel at home with Lip Gloss.
```go
import "github.com/charmbracelet/lipgloss"
var style = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#FAFAFA")).
Background(lipgloss.Color("#7D56F4")).
PaddingTop(2).
PaddingLeft(4).
Width(22)
fmt.Println(style.Render("Hello, kitty."))
```
## Colors
Lip Gloss supports the following color profiles:
### ANSI 16 colors (4-bit)
```go
lipgloss.Color("5") // magenta
lipgloss.Color("9") // red
lipgloss.Color("12") // light blue
```
### ANSI 256 Colors (8-bit)
```go
lipgloss.Color("86") // aqua
lipgloss.Color("201") // hot pink
lipgloss.Color("202") // orange
```
### True Color (16,777,216 colors; 24-bit)
```go
lipgloss.Color("#0000FF") // good ol' 100% blue
lipgloss.Color("#04B575") // a green
lipgloss.Color("#3C3C3C") // a dark gray
```
...as well as a 1-bit Ascii profile, which is black and white only.
The terminal's color profile will be automatically detected, and colors outside
the gamut of the current palette will be automatically coerced to their closest
available value.
### Adaptive Colors
You can also specify color options for light and dark backgrounds:
```go
lipgloss.AdaptiveColor{Light: "236", Dark: "248"}
```
The terminal's background color will automatically be detected and the
appropriate color will be chosen at runtime.
## Inline Formatting
Lip Gloss supports the usual ANSI text formatting options:
```go
var style = lipgloss.NewStyle().
Bold(true).
Italic(true).
Faint(true).
Blink(true).
Strikethrough(true).
Underline(true).
Reverse(true)
```
## Block-Level Formatting
Lip Gloss also supports rules for block-level formatting:
```go
// Padding
var style = lipgloss.NewStyle().
PaddingTop(2).
PaddingRight(4).
PaddingBottom(2).
PaddingLeft(4)
// Margins
var style = lipgloss.NewStyle().
MarginTop(2).
RightMarginRight(4).
MarginBottom(2).
MarginLeft(4)
```
There is also shorthand syntax for margins and padding, which follows the same
format as CSS:
```go
// 2 cells on all sides
lipgloss.NewStyle().Padding(2)
// 2 cells on the top and bottom, 4 cells on the left and right
lipgloss.NewStyle().Margin(2, 4)
// 1 cell on the top, 4 cells on the sides, 2 cells on the bottom
lipgloss.NewStyle().Padding(1, 4, 2)
// Clockwise, starting from the top: 2 cells on the top, 4 on the right, 3 on
// the bottom, and 1 on the left
lipgloss.NewStyle().Margin(2, 4, 3, 1)
```
## Aligning Text
You can align paragraphs of text to the left, right, or center.
```go
var style = lipgloss.NewStyle().
Width(24).
Align(lipgloss.Left). // align it left
Align(lipgloss.Right). // no wait, align it right
Align(lipgloss.Center) // just kidding, align it in the center
```
## Width and Height
Setting a minimim width and height is simple and straightforward.
```go
var str = lipgloss.NewStyle().
Width(24).
Height(32).
Foreground(lipgloss.Color("63")).
Render("What’s for lunch?")
```
## Borders
Adding borders is easy:
```go
// Add a purple, regtangular border
var style = lipgloss.NewStyle().
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("63"))
// Set a rounded, yellow-on-purple border to the top and left
var anotherStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("228")).
BorderBackground(lipgloss.Color("63")).
BorderTop(true).
BorderLeft(true)
// Make your own border
var myCuteBorder = lipgloss.Border{
Top: "._.:*:",
Bottom: "._.:*:",
Left: "|*",
Right: "|*",
TopLeft: "*",
TopRight: "*",
BottomLeft: "*",
BottomRight: "*",
}
```
There are also shorthand functions for defining borders, which follow a similar
pattern to the margin and padding shorthand functions.
```go
// Add a thick border to the top and bottom
lipgloss.NewStyle().
Border(lipgloss.ThickBorder(), true, false)
// Add a thick border to the right and bottom sides. Rules are set clockwise
// from top.
lipgloss.NewStyle().
Border(lipgloss.DoubleBorder(), true, false, false, true)
```
For more on borders see [the docs][docs].
## Copying Styles
Just use `Copy()`:
```go
var style = lipgloss.NewStyle().Foreground(lipgloss.Color("219"))
var wildStyle = style.Copy().Blink(true)
```
`Copy()` performs a copy on the underlying data structure ensuring that you get
a true, dereferenced copy of a style. Without copying it's possible to mutate
styles.
## Inheritance
Styles can inherit rules from other styles. When inheriting, only unset rules
on the receiver are inherited.
```go
var styleA = lipgloss.NewStyle().
Foreground(lipgloss.Color("229")).
Background(lipgloss.Color("63"))
// Only the background color will be inherited here, because the foreground
// color will have been already set:
var styleB = lipgloss.NewStyle().
Foreground(lipgloss.Color("201")).
Inherit(styleA)
```
## Unsetting Rules
All rules can be unset:
```go
var style = lipgloss.NewStyle().
Bold(true). // make it bold
UnsetBold(). // jk don't make it bold
Background(lipgloss.Color("227")). // yellow background
UnsetBackground() // never mind
```
When a rule is unset, it won't be inherited or copied.
## Enforcing Rules
Sometimes, such as when developing a component, you want to make sure style
definitions respect their intended purpose in the UI. This is where `Inline`
and `MaxWidth`, and `MaxHeight` come in:
```go
// Force rendering onto a single line, ignoring margins, padding, and borders.
someStyle.Inline(true).Render("yadda yadda")
// Also limit rendering to five cells
someStyle.Inline(true).MaxWidth(5).Render("yadda yadda")
// Limit rendering to a 5x5 cell block
someStyle.MaxWidth(5).MaxHeight(5).Render("yadda yadda")
```
## Rendering
Generally, you just call the `Render(string)` method on a `lipgloss.Style`:
```go
fmt.Println(lipgloss.NewStyle().Bold(true).Render("Hello, kitty."))
```
But you could also use the Stringer interface:
```go
var style = lipgloss.NewStyle().SetString("你好,猫咪。").Bold(true)
fmt.Printf("%s\n", style)
```
## Utilities
In addition to pure styling, Lip Gloss also ships with some utilties to help
assemble your layouts.
### Joining Paragraphs
Horizontally and vertically joining paragraphs is a cinch.
```go
// Horizontally join three paragraphs along their bottom edges
lipgloss.JoinHorizontal(lipgloss.Bottom, paragraphA, paragraphB, paragraphC)
// Vertically join two paragraphs along their center axes
lipgloss.JoinVertical(lipgloss.Center, paragraphA, paragraphB)
// Horizontally join three paragraphs, with the shorter ones aligning 20%
// from the top of the tallest
lipgloss.JoinHorizontal(0.2, paragraphA, paragraphB, paragraphC)
```
### Measuring Width and Height
Sometimes you’ll want to know the width and height of text blocks when building
your layouts.
```go
var block string = lipgloss.NewStyle().
Width(40).
Padding(2).
Render(someLongString)
// Get the actual, phsical dimensions of the text block.
width := lipgloss.Width(block)
height := lipgloss.Height(block)
// Here's a shorthand function.
w, h := lipgloss.Size(block)
```
### Placing Text in Whitespace
Sometimes you’ll simply want to place a block of text in whitespace.
```go
// Center a paragraph horizontally in a space 80 cells wide. The height of
// the block returned will be as tall as the input paragraph.
block := lipgloss.PlaceHorizontal(80, lipgloss.Center, fancyStyledParagraph)
// Place a paragraph at the bottom of a space 30 cells tall. The width of
// the text block returned will be as wide as the input paragraph.
block := lipgloss.PlaceVertical(30, lipgloss.Bottom, fancyStyledParagraph)
// Place a paragraph in the bottom right corner of a 30x80 cell space.
block := lipgloss.Place(30, 80, lipgloss.Right, lipgloss.Bottom, fancyStyledParagraph)
```
You can also style the whitespace. For details, see [the docs][docs].
***
## What about [Bubble Tea][tea]?
Lip Gloss doesn’t replace Bubble Tea. Rather, it is an excellent Bubble Tea
companion. It was designed to make assembling terminal user interface views as
simple and fun as possible so that you can focus on building your application
instead of concerning yourself with low-level layout details.
In simple terms, you can use Lip Gloss to help build your Bubble Tea views.
[tea]: https://github.com/charmbracelet/tea
## Under the Hood
Lip Gloss is built on the excellent [Termenv][termenv] and [Reflow][reflow]
libraries which deal with color and ANSI-aware text operations, respectively.
For many use cases Termenv and Reflow will be sufficient for your needs.
[termenv]: https://github.com/muesli/termenv
[reflow]: https://github.com/muesli/reflow
## Rendering Markdown
For a more document-centric rendering solution with support for things like
lists, tables, and syntax-highlighted code have a look at [Glamour][glamour],
the stylesheet-based Markdown renderer.
[glamour]: https://github.com/charmbracelet/glamour
## License
[MIT](https://github.com/charmbracelet/lipgloss/raw/master/LICENSE)
***
Part of [Charm](https://charm.sh).
Charm热爱开源 • Charm loves open source
[docs]: https://pkg.go.dev/github.com/charmbracelet/lipgloss?tab=doc
lipgloss-0.4.0/align.go 0000664 0000000 0000000 00000002670 14114427461 0015001 0 ustar 00root root 0000000 0000000 package lipgloss
import (
"strings"
"github.com/muesli/reflow/ansi"
"github.com/muesli/termenv"
)
// Perform text alignment. If the string is multi-lined, we also make all lines
// the same width by padding them with spaces. If a termenv style is passed,
// use that to style the spaces added.
func alignText(str string, pos Position, width int, style *termenv.Style) string {
lines, widestLine := getLines(str)
var b strings.Builder
for i, l := range lines {
lineWidth := ansi.PrintableRuneWidth(l)
shortAmount := widestLine - lineWidth // difference from the widest line
shortAmount += max(0, width-(shortAmount+lineWidth)) // difference from the total width, if set
if shortAmount > 0 {
switch pos {
case Right:
s := strings.Repeat(" ", shortAmount)
if style != nil {
s = style.Styled(s)
}
l = s + l
case Center:
left := shortAmount / 2
right := left + shortAmount%2 // note that we put the remainder on the right
leftSpaces := strings.Repeat(" ", left)
rightSpaces := strings.Repeat(" ", right)
if style != nil {
leftSpaces = style.Styled(leftSpaces)
rightSpaces = style.Styled(rightSpaces)
}
l = leftSpaces + l + rightSpaces
default: // Left
s := strings.Repeat(" ", shortAmount)
if style != nil {
s = style.Styled(s)
}
l += s
}
}
b.WriteString(l)
if i < len(lines)-1 {
b.WriteRune('\n')
}
}
return b.String()
}
lipgloss-0.4.0/ansi_unix.go 0000664 0000000 0000000 00000000176 14114427461 0015703 0 ustar 00root root 0000000 0000000 // +build !windows
package lipgloss
// enableLegacyWindowsANSI is only needed on Windows.
func enableLegacyWindowsANSI() {}
lipgloss-0.4.0/ansi_windows.go 0000664 0000000 0000000 00000001173 14114427461 0016410 0 ustar 00root root 0000000 0000000 // +build windows
package lipgloss
import (
"os"
"sync"
"golang.org/x/sys/windows"
)
var enableANSI sync.Once
// enableANSIColors enables support for ANSI color sequences in the Windows
// default console (cmd.exe and the PowerShell application). Note that this
// only works with Windows 10. Also note that Windows Terminal supports colors
// by default.
func enableLegacyWindowsANSI() {
enableANSI.Do(func() {
stdout := windows.Handle(os.Stdout.Fd())
var originalMode uint32
windows.GetConsoleMode(stdout, &originalMode)
windows.SetConsoleMode(stdout, originalMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
})
}
lipgloss-0.4.0/borders.go 0000664 0000000 0000000 00000020664 14114427461 0015352 0 ustar 00root root 0000000 0000000 package lipgloss
import (
"strings"
"github.com/mattn/go-runewidth"
"github.com/muesli/reflow/ansi"
"github.com/muesli/termenv"
)
// Border contains a series of values which comprise the various parts of a
// border.
type Border struct {
Top string
Bottom string
Left string
Right string
TopLeft string
TopRight string
BottomRight string
BottomLeft string
}
// GetTopSize returns the width of the top border. If borders contain runes of
// varying widths, the widest rune is returned. If no border exists on the top
// edge, 0 is returned.
func (b Border) GetTopSize() int {
return getBorderEdgeWidth(b.TopLeft, b.Top, b.TopRight)
}
// GetRightSize returns the width of the right border. If borders contain
// runes of varying widths, the widest rune is returned. If no border exists on
// the right edge, 0 is returned.
func (b Border) GetRightSize() int {
return getBorderEdgeWidth(b.TopRight, b.Top, b.BottomRight)
}
// GetBottomSize returns the width of the bottom border. If borders contain
// runes of varying widths, the widest rune is returned. If no border exists on
// the bottom edge, 0 is returned.
func (b Border) GetBottomSize() int {
return getBorderEdgeWidth(b.BottomLeft, b.Bottom, b.BottomRight)
}
// GetLeftSize returns the width of the left border. If borders contain runes
// of varying widths, the widest rune is returned. If no border exists on the
// left edge, 0 is returned.
func (b Border) GetLeftSize() int {
return getBorderEdgeWidth(b.TopLeft, b.Left, b.TopRight)
}
func getBorderEdgeWidth(borderParts ...string) (maxWidth int) {
for _, piece := range borderParts {
w := maxRuneWidth(piece)
if w > maxWidth {
maxWidth = w
}
}
return maxWidth
}
var (
noBorder = Border{}
normalBorder = Border{
Top: "─",
Bottom: "─",
Left: "│",
Right: "│",
TopLeft: "┌",
TopRight: "┐",
BottomLeft: "└",
BottomRight: "┘",
}
roundedBorder = Border{
Top: "─",
Bottom: "─",
Left: "│",
Right: "│",
TopLeft: "╭",
TopRight: "╮",
BottomLeft: "╰",
BottomRight: "╯",
}
thickBorder = Border{
Top: "━",
Bottom: "━",
Left: "┃",
Right: "┃",
TopLeft: "┏",
TopRight: "┓",
BottomLeft: "┗",
BottomRight: "┛",
}
doubleBorder = Border{
Top: "═",
Bottom: "═",
Left: "║",
Right: "║",
TopLeft: "╔",
TopRight: "╗",
BottomLeft: "╚",
BottomRight: "╝",
}
hiddenBorder = Border{
Top: " ",
Bottom: " ",
Left: " ",
Right: " ",
TopLeft: " ",
TopRight: " ",
BottomLeft: " ",
BottomRight: " ",
}
)
// NormalBorder returns a standard-type border with a normal weight and 90
// degree corners.
func NormalBorder() Border {
return normalBorder
}
// RoundedBorder returns a border with rounded corners.
func RoundedBorder() Border {
return roundedBorder
}
// Thick border returns a border that's thicker than the one returned by
// NormalBorder.
func ThickBorder() Border {
return thickBorder
}
// DoubleBorder returns a border comprised of two thin strokes.
func DoubleBorder() Border {
return doubleBorder
}
// HiddenBorder returns a border that renders as a series of single-cell
// spaces. It's useful for cases when you want to remove a standard border but
// maintain layout positioning. This said, you can still apply a background
// color to a hidden border.
func HiddenBorder() Border {
return hiddenBorder
}
func (s Style) applyBorder(str string) string {
var (
topSet = s.isSet(borderTopKey)
rightSet = s.isSet(borderRightKey)
bottomSet = s.isSet(borderBottomKey)
leftSet = s.isSet(borderLeftKey)
border = s.getBorderStyle()
hasTop = s.getAsBool(borderTopKey, false)
hasRight = s.getAsBool(borderRightKey, false)
hasBottom = s.getAsBool(borderBottomKey, false)
hasLeft = s.getAsBool(borderLeftKey, false)
topFG = s.getAsColor(borderTopForegroundKey)
rightFG = s.getAsColor(borderRightForegroundKey)
bottomFG = s.getAsColor(borderBottomForegroundKey)
leftFG = s.getAsColor(borderLeftForegroundKey)
topBG = s.getAsColor(borderTopBackgroundKey)
rightBG = s.getAsColor(borderRightBackgroundKey)
bottomBG = s.getAsColor(borderBottomBackgroundKey)
leftBG = s.getAsColor(borderLeftBackgroundKey)
)
// If a border is set and no sides have been specifically turned on or off
// render borders on all sides.
if border != noBorder && !(topSet || rightSet || bottomSet || leftSet) {
hasTop = true
hasRight = true
hasBottom = true
hasLeft = true
}
// If no border is set or all borders are been disabled, abort.
if border == noBorder || (!hasTop && !hasRight && !hasBottom && !hasLeft) {
return str
}
lines, width := getLines(str)
if hasLeft {
if border.Left == "" {
border.Left = " "
}
width += maxRuneWidth(border.Left)
}
if hasRight && border.Right == "" {
border.Right = " "
}
// If corners should be render but are set with the empty string, fill them
// with a single space.
if hasTop && hasLeft && border.TopLeft == "" {
border.TopLeft = " "
}
if hasTop && hasRight && border.TopRight == "" {
border.TopRight = " "
}
if hasBottom && hasLeft && border.BottomLeft == "" {
border.BottomLeft = " "
}
if hasBottom && hasRight && border.BottomRight == "" {
border.BottomRight = " "
}
// Figure out which corners we should actually be using based on which
// sides are set to show.
if hasTop {
switch {
case !hasLeft && !hasRight:
border.TopLeft = ""
border.TopRight = ""
case !hasLeft:
border.TopLeft = ""
case !hasRight:
border.TopRight = ""
}
}
if hasBottom {
switch {
case !hasLeft && !hasRight:
border.BottomLeft = ""
border.BottomRight = ""
case !hasLeft:
border.BottomLeft = ""
case !hasRight:
border.BottomRight = ""
}
}
// For now, limit corners to one rune.
border.TopLeft = getFirstRuneAsString(border.TopLeft)
border.TopRight = getFirstRuneAsString(border.TopRight)
border.BottomRight = getFirstRuneAsString(border.BottomRight)
border.BottomLeft = getFirstRuneAsString(border.BottomLeft)
var out strings.Builder
// Render top
if hasTop {
top := renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width)
top = styleBorder(top, topFG, topBG)
out.WriteString(top)
out.WriteRune('\n')
}
leftRunes := []rune(border.Left)
leftIndex := 0
rightRunes := []rune(border.Right)
rightIndex := 0
// Render sides
for i, l := range lines {
if hasLeft {
r := string(leftRunes[leftIndex])
leftIndex++
if leftIndex >= len(leftRunes) {
leftIndex = 0
}
out.WriteString(styleBorder(r, leftFG, leftBG))
}
out.WriteString(l)
if hasRight {
r := string(rightRunes[rightIndex])
rightIndex++
if rightIndex >= len(rightRunes) {
rightIndex = 0
}
out.WriteString(styleBorder(r, rightFG, rightBG))
}
if i < len(lines)-1 {
out.WriteRune('\n')
}
}
// Render bottom
if hasBottom {
bottom := renderHorizontalEdge(border.BottomLeft, border.Bottom, border.BottomRight, width)
bottom = styleBorder(bottom, bottomFG, bottomBG)
out.WriteRune('\n')
out.WriteString(bottom)
}
return out.String()
}
// Render the horizontal (top or bottom) portion of a border.
func renderHorizontalEdge(left, middle, right string, width int) string {
if width < 1 {
return ""
}
if middle == "" {
middle = " "
}
leftWidth := ansi.PrintableRuneWidth(left)
rightWidth := ansi.PrintableRuneWidth(right)
runes := []rune(middle)
j := 0
out := strings.Builder{}
out.WriteString(left)
for i := leftWidth + rightWidth; i < width+rightWidth; {
out.WriteRune(runes[j])
j++
if j >= len(runes) {
j = 0
}
i += ansi.PrintableRuneWidth(string(runes[j]))
}
out.WriteString(right)
return out.String()
}
// Apply foreground and background styling to a border.
func styleBorder(border string, fg, bg TerminalColor) string {
if fg == noColor && bg == noColor {
return border
}
var style = termenv.Style{}
if fg != noColor {
style = style.Foreground(ColorProfile().Color(fg.value()))
}
if bg != noColor {
style = style.Background(ColorProfile().Color(bg.value()))
}
return style.Styled(border)
}
func maxRuneWidth(str string) (width int) {
for _, r := range str {
w := runewidth.RuneWidth(r)
if w > width {
width = w
}
}
return width
}
func getFirstRuneAsString(str string) string {
if str == "" {
return str
}
r := []rune(str)
return string(r[0])
}
lipgloss-0.4.0/color.go 0000664 0000000 0000000 00000010703 14114427461 0015021 0 ustar 00root root 0000000 0000000 package lipgloss
import (
"sync"
"github.com/lucasb-eyer/go-colorful"
"github.com/muesli/termenv"
)
var (
colorProfile termenv.Profile
getColorProfile sync.Once
explicitColorProfile bool
colorProfileMtx sync.Mutex
// Because it's a potentially long operation (relatively speaking), we
// check the background color on initialization rather than at the last
// possible second.
hasDarkBackground = termenv.HasDarkBackground()
)
// ColorProfile returns the detected termenv color profile. It will perform the
// actual check only once.
func ColorProfile() termenv.Profile {
if !explicitColorProfile {
getColorProfile.Do(func() {
colorProfile = termenv.ColorProfile()
})
}
return colorProfile
}
// SetColorProfile sets the color profile on a package-wide context. This
// function exists mostly for testing purposes so that you can assure you're
// testing against a specific profile.
//
// Outside of testing you likely won't want to use this function as
// ColorProfile() will detect and cache the terminal's color capabilities
// and choose the best available profile.
//
// Available color profiles are:
//
// termenv.Ascii (no color, 1-bit)
// termenv.ANSI (16 colors, 4-bit)
// termenv.ANSI256 (256 colors, 8-bit)
// termenv.TrueColor (16,777,216 colors, 24-bit)
//
// This function is thread-safe.
func SetColorProfile(p termenv.Profile) {
colorProfileMtx.Lock()
defer colorProfileMtx.Unlock()
colorProfile = p
explicitColorProfile = true
}
// HadDarkBackground returns whether or not the terminal has a dark background.
func HasDarkBackground() bool {
return hasDarkBackground
}
// TerminalColor is a color intended to be rendered in the terminal. It
// satisfies the Go color.Color interface.
type TerminalColor interface {
value() string
color() termenv.Color
RGBA() (r, g, b, a uint32)
}
// 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:
//
// var style = someStyle.Copy().Background(lipgloss.NoColor{})
//
type NoColor struct{}
func (n NoColor) value() string {
return ""
}
func (n NoColor) color() termenv.Color {
return ColorProfile().Color("")
}
// RGBA returns the RGBA value of this color. Because we have to return
// something, despite this color being the absence of color, we're returning
// the same value that go-colorful returns on error:
//
// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF.
func (n NoColor) RGBA() (r, g, b, a uint32) {
return 0x0, 0x0, 0x0, 0xFFFF
}
var noColor = NoColor{}
// Color specifies a color by hex or ANSI value. For example:
//
// ansiColor := lipgloss.Color("21")
// hexColor := lipgloss.Color("#0000ff")
//
type Color string
func (c Color) value() string {
return string(c)
}
func (c Color) color() termenv.Color {
return ColorProfile().Color(string(c))
}
// RGBA returns the RGBA value of this color. This satisfies the Go Color
// interface. Note that on error we return black with 100% opacity, or:
//
// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF
//
// This is inline with go-colorful's default behavior.
func (c Color) RGBA() (r, g, b, a uint32) {
cf, err := colorful.Hex(c.value())
if err != nil {
// If we ignore the return behavior and simply return what go-colorful
// give us for the color value we'd be returning exactly this, however
// we're being explicit here for the sake of clarity.
return colorful.Color{}.RGBA()
}
return cf.RGBA()
}
// AdaptiveColor provides color options for light and dark backgrounds. The
// appropriate color with be returned at runtime based on the darkness of the
// terminal background color.
//
// Example usage:
//
// color := lipgloss.AdaptiveColor{Light: "#0000ff", Dark: "#000099"}
//
type AdaptiveColor struct {
Light string
Dark string
}
func (ac AdaptiveColor) value() string {
if HasDarkBackground() {
return ac.Dark
}
return ac.Light
}
func (ac AdaptiveColor) color() termenv.Color {
return ColorProfile().Color(ac.value())
}
// RGBA returns the RGBA value of this color. This satisfies the Go Color
// interface. Note that on error we return black with 100% opacity, or:
//
// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF
//
// This is inline with go-colorful's default behavior.
func (ac AdaptiveColor) RGBA() (r, g, b, a uint32) {
cf, err := colorful.Hex(ac.value())
if err != nil {
return colorful.Color{}.RGBA()
}
return cf.RGBA()
}
lipgloss-0.4.0/color_test.go 0000664 0000000 0000000 00000001706 14114427461 0016063 0 ustar 00root root 0000000 0000000 package lipgloss
import (
"testing"
"github.com/muesli/termenv"
)
func TestSetColorProfile(t *testing.T) {
t.Parallel()
tt := []struct {
profile termenv.Profile
input string
style Style
expected string
}{
{
termenv.Ascii,
"hello",
NewStyle().Foreground(Color("#5A56E0")),
"hello",
},
{
termenv.ANSI,
"hello",
NewStyle().Foreground(Color("#5A56E0")),
"\x1b[94mhello\x1b[0m",
},
{
termenv.ANSI256,
"hello",
NewStyle().Foreground(Color("#5A56E0")),
"\x1b[38;5;62mhello\x1b[0m",
},
{
termenv.TrueColor,
"hello",
NewStyle().Foreground(Color("#5A56E0")),
"\x1b[38;2;89;86;224mhello\x1b[0m",
},
}
for i, tc := range tt {
SetColorProfile(tc.profile)
res := tc.style.Render(tc.input)
if res != tc.expected {
t.Errorf("Test %d, expected:\n\n`%s`\n`%s`\n\nActual output:\n\n`%s`\n`%s`\n\n",
i, tc.expected, formatEscapes(tc.expected),
res, formatEscapes(res))
}
}
}
lipgloss-0.4.0/example/ 0000775 0000000 0000000 00000000000 14114427461 0015006 5 ustar 00root root 0000000 0000000 lipgloss-0.4.0/example/go.mod 0000664 0000000 0000000 00000000342 14114427461 0016113 0 ustar 00root root 0000000 0000000 module example
go 1.16
require (
github.com/charmbracelet/lipgloss v0.1.0
github.com/lucasb-eyer/go-colorful v1.2.0
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6
)
replace github.com/charmbracelet/lipgloss => ../
lipgloss-0.4.0/example/go.sum 0000664 0000000 0000000 00000003562 14114427461 0016147 0 ustar 00root root 0000000 0000000 github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/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.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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.8.1 h1:9q230czSP3DHVpkaPDXGp0TOfAwyjyYwXlUCQxQSaBk=
github.com/muesli/termenv v0.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0=
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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs=
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
lipgloss-0.4.0/example/main.go 0000664 0000000 0000000 00000022407 14114427461 0016266 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"os"
"strings"
"github.com/charmbracelet/lipgloss"
"github.com/lucasb-eyer/go-colorful"
"golang.org/x/term"
)
const (
// In real life situations we'd adjust the document to fit the width we've
// detected. In the case of this example we're hardcoding the width, and
// later using the detected width only to truncate in order to avoid jaggy
// wrapping.
width = 96
columnWidth = 30
)
// Style definitions.
var (
// General.
subtle = lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#383838"}
highlight = lipgloss.AdaptiveColor{Light: "#874BFD", Dark: "#7D56F4"}
special = lipgloss.AdaptiveColor{Light: "#43BF6D", Dark: "#73F59F"}
divider = lipgloss.NewStyle().
SetString("•").
Padding(0, 1).
Foreground(subtle).
String()
url = lipgloss.NewStyle().Foreground(special).Render
// Tabs.
activeTabBorder = lipgloss.Border{
Top: "─",
Bottom: " ",
Left: "│",
Right: "│",
TopLeft: "╭",
TopRight: "╮",
BottomLeft: "┘",
BottomRight: "└",
}
tabBorder = lipgloss.Border{
Top: "─",
Bottom: "─",
Left: "│",
Right: "│",
TopLeft: "╭",
TopRight: "╮",
BottomLeft: "┴",
BottomRight: "┴",
}
tab = lipgloss.NewStyle().
Border(tabBorder, true).
BorderForeground(highlight).
Padding(0, 1)
activeTab = tab.Copy().Border(activeTabBorder, true)
tabGap = tab.Copy().
BorderTop(false).
BorderLeft(false).
BorderRight(false)
// Title.
titleStyle = lipgloss.NewStyle().
MarginLeft(1).
MarginRight(5).
Padding(0, 1).
Italic(true).
Foreground(lipgloss.Color("#FFF7DB")).
SetString("Lip Gloss")
descStyle = lipgloss.NewStyle().MarginTop(1)
infoStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.NormalBorder()).
BorderTop(true).
BorderForeground(subtle)
// Dialog.
dialogBoxStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("#874BFD")).
Padding(1, 0).
BorderTop(true).
BorderLeft(true).
BorderRight(true).
BorderBottom(true)
buttonStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFF7DB")).
Background(lipgloss.Color("#888B7E")).
Padding(0, 3).
MarginTop(1)
activeButtonStyle = buttonStyle.Copy().
Foreground(lipgloss.Color("#FFF7DB")).
Background(lipgloss.Color("#F25D94")).
MarginRight(2).
Underline(true)
// List.
list = lipgloss.NewStyle().
Border(lipgloss.NormalBorder(), false, true, false, false).
BorderForeground(subtle).
MarginRight(2).
Height(8).
Width(columnWidth + 1)
listHeader = lipgloss.NewStyle().
BorderStyle(lipgloss.NormalBorder()).
BorderBottom(true).
BorderForeground(subtle).
MarginRight(2).
Render
listItem = lipgloss.NewStyle().PaddingLeft(2).Render
checkMark = lipgloss.NewStyle().SetString("✓").
Foreground(special).
PaddingRight(1).
String()
listDone = func(s string) string {
return checkMark + lipgloss.NewStyle().
Strikethrough(true).
Foreground(lipgloss.AdaptiveColor{Light: "#969B86", Dark: "#696969"}).
Render(s)
}
// Paragraphs/History.
historyStyle = lipgloss.NewStyle().
Align(lipgloss.Left).
Foreground(lipgloss.Color("#FAFAFA")).
Background(highlight).
Margin(1, 3, 0, 0).
Padding(1, 2).
Height(19).
Width(columnWidth)
// Status Bar.
statusNugget = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFFDF5")).
Padding(0, 1)
statusBarStyle = lipgloss.NewStyle().
Foreground(lipgloss.AdaptiveColor{Light: "#343433", Dark: "#C1C6B2"}).
Background(lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#353533"})
statusStyle = lipgloss.NewStyle().
Inherit(statusBarStyle).
Foreground(lipgloss.Color("#FFFDF5")).
Background(lipgloss.Color("#FF5F87")).
Padding(0, 1).
MarginRight(1)
encodingStyle = statusNugget.Copy().
Background(lipgloss.Color("#A550DF")).
Align(lipgloss.Right)
statusText = lipgloss.NewStyle().Inherit(statusBarStyle)
fishCakeStyle = statusNugget.Copy().Background(lipgloss.Color("#6124DF"))
// Page.
docStyle = lipgloss.NewStyle().Padding(1, 2, 1, 2)
)
func main() {
physicalWidth, _, _ := term.GetSize(int(os.Stdout.Fd()))
doc := strings.Builder{}
// Tabs
{
row := lipgloss.JoinHorizontal(
lipgloss.Top,
activeTab.Render("Lip Gloss"),
tab.Render("Blush"),
tab.Render("Eye Shadow"),
tab.Render("Mascara"),
tab.Render("Foundation"),
)
gap := tabGap.Render(strings.Repeat(" ", max(0, width-lipgloss.Width(row)-2)))
row = lipgloss.JoinHorizontal(lipgloss.Bottom, row, gap)
doc.WriteString(row + "\n\n")
}
// Title
{
var (
colors = colorGrid(1, 5)
title strings.Builder
)
for i, v := range colors {
const offset = 2
c := lipgloss.Color(v[0])
fmt.Fprint(&title, titleStyle.Copy().MarginLeft(i*offset).Background(c))
if i < len(colors)-1 {
title.WriteRune('\n')
}
}
desc := lipgloss.JoinVertical(lipgloss.Left,
descStyle.Render("Style Definitions for Nice Terminal Layouts"),
infoStyle.Render("From Charm"+divider+url("https://github.com/charmbracelet/lipgloss")),
)
row := lipgloss.JoinHorizontal(lipgloss.Top, title.String(), desc)
doc.WriteString(row + "\n\n")
}
// Dialog
{
okButton := activeButtonStyle.Render("Yes")
cancelButton := buttonStyle.Render("Maybe")
question := lipgloss.NewStyle().Width(50).Align(lipgloss.Center).Render("Are you sure you want to eat marmalade?")
buttons := lipgloss.JoinHorizontal(lipgloss.Top, okButton, cancelButton)
ui := lipgloss.JoinVertical(lipgloss.Center, question, buttons)
dialog := lipgloss.Place(width, 9,
lipgloss.Center, lipgloss.Center,
dialogBoxStyle.Render(ui),
lipgloss.WithWhitespaceChars("猫咪"),
lipgloss.WithWhitespaceForeground(subtle),
)
doc.WriteString(dialog + "\n\n")
}
// Color grid
colors := func() string {
colors := colorGrid(14, 8)
b := strings.Builder{}
for _, x := range colors {
for _, y := range x {
s := lipgloss.NewStyle().SetString(" ").Background(lipgloss.Color(y))
b.WriteString(s.String())
}
b.WriteRune('\n')
}
return b.String()
}()
lists := lipgloss.JoinHorizontal(lipgloss.Top,
list.Render(
lipgloss.JoinVertical(lipgloss.Left,
listHeader("Citrus Fruits to Try"),
listDone("Grapefruit"),
listDone("Yuzu"),
listItem("Citron"),
listItem("Kumquat"),
listItem("Pomelo"),
),
),
list.Copy().Width(columnWidth).Render(
lipgloss.JoinVertical(lipgloss.Left,
listHeader("Actual Lip Gloss Vendors"),
listItem("Glossier"),
listItem("Claire‘s Boutique"),
listDone("Nyx"),
listItem("Mac"),
listDone("Milk"),
),
),
)
doc.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, lists, colors))
// Marmalade history
{
const (
historyA = "The Romans learned from the Greeks that quinces slowly cooked with honey would “set” when cool. The Apicius gives a recipe for preserving whole quinces, stems and leaves attached, in a bath of honey diluted with defrutum: Roman marmalade. Preserves of quince and lemon appear (along with rose, apple, plum and pear) in the Book of ceremonies of the Byzantine Emperor Constantine VII Porphyrogennetos."
historyB = "Medieval quince preserves, which went by the French name cotignac, produced in a clear version and a fruit pulp version, began to lose their medieval seasoning of spices in the 16th century. In the 17th century, La Varenne provided recipes for both thick and clear cotignac."
historyC = "In 1524, Henry VIII, King of England, received a “box of marmalade” from Mr. Hull of Exeter. This was probably marmelada, a solid quince paste from Portugal, still made and sold in southern Europe today. It became a favourite treat of Anne Boleyn and her ladies in waiting."
)
doc.WriteString(lipgloss.JoinHorizontal(
lipgloss.Top,
historyStyle.Copy().Align(lipgloss.Right).Render(historyA),
historyStyle.Copy().Align(lipgloss.Center).Render(historyB),
historyStyle.Copy().MarginRight(0).Render(historyC),
))
doc.WriteString("\n\n")
}
// Status bar
{
w := lipgloss.Width
statusKey := statusStyle.Render("STATUS")
encoding := encodingStyle.Render("UTF-8")
fishCake := fishCakeStyle.Render("🍥 Fish Cake")
statusVal := statusText.Copy().
Width(width - w(statusKey) - w(encoding) - w(fishCake)).
Render("Ravishing")
bar := lipgloss.JoinHorizontal(lipgloss.Top,
statusKey,
statusVal,
encoding,
fishCake,
)
doc.WriteString(statusBarStyle.Width(width).Render(bar))
}
if physicalWidth > 0 {
docStyle = docStyle.MaxWidth(physicalWidth)
}
// Okay, let's print it
fmt.Println(docStyle.Render(doc.String()))
}
func colorGrid(xSteps, ySteps int) [][]string {
x0y0, _ := colorful.Hex("#F25D94")
x1y0, _ := colorful.Hex("#EDFF82")
x0y1, _ := colorful.Hex("#643AFF")
x1y1, _ := colorful.Hex("#14F9D5")
x0 := make([]colorful.Color, ySteps)
for i := range x0 {
x0[i] = x0y0.BlendLuv(x0y1, float64(i)/float64(ySteps))
}
x1 := make([]colorful.Color, ySteps)
for i := range x1 {
x1[i] = x1y0.BlendLuv(x1y1, float64(i)/float64(ySteps))
}
grid := make([][]string, ySteps)
for x := 0; x < ySteps; x++ {
y0 := x0[x]
grid[x] = make([]string, xSteps)
for y := 0; y < xSteps; y++ {
grid[x][y] = y0.BlendLuv(x1[x], float64(y)/float64(xSteps)).Hex()
}
}
return grid
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
lipgloss-0.4.0/get.go 0000664 0000000 0000000 00000032142 14114427461 0014463 0 ustar 00root root 0000000 0000000 package lipgloss
import (
"strings"
"github.com/muesli/reflow/ansi"
)
// GetBold returns the style's bold value It no value is set false is returned.
func (s Style) GetBold() bool {
return s.getAsBool(boldKey, false)
}
// GetItalic returns the style's italic value. It no value is set false is
// returned.
func (s Style) GetItalic() bool {
return s.getAsBool(italicKey, false)
}
// GetUnderline returns the style's underline value. It no value is set false is
// returned.
func (s Style) GetUnderline() bool {
return s.getAsBool(underlineKey, false)
}
// GetStrikethrough returns the style's strikethrough value. It no value is set false
// is returned.
func (s Style) GetStrikethrough() bool {
return s.getAsBool(strikethroughKey, false)
}
// GetReverse returns the style's reverse value. It no value is set false is
// returned.
func (s Style) GetReverse() bool {
return s.getAsBool(reverseKey, false)
}
// GetBlink returns the style's blink value. It no value is set false is
// returned.
func (s Style) GetBlink() bool {
return s.getAsBool(blinkKey, false)
}
// GetFaint returns the style's faint value. It no value is set false is
// returned.
func (s Style) GetFaint() bool {
return s.getAsBool(faintKey, false)
}
// GetForeground returns the style's foreground color. If no value is set
// NoColor{} is returned.
func (s Style) GetForeground() TerminalColor {
return s.getAsColor(foregroundKey)
}
// GetBackground returns the style's back color. If no value is set
// NoColor{} is returned.
func (s Style) GetBackground() TerminalColor {
return s.getAsColor(backgroundKey)
}
// GetWidth returns the style's width setting. If no width is set 0 is
// returned.
func (s Style) GetWidth() int {
return s.getAsInt(widthKey)
}
// GetHeight returns the style's height setting. If no height is set 0 is
// returned.
func (s Style) GetHeight() int {
return s.getAsInt(heightKey)
}
// GetAlign returns the style's implicit alignment setting. If no alignment is
// set Position.AlignLeft is returned.
func (s Style) GetAlign() Position {
v := s.getAsPosition(alignKey)
if v == Position(0) {
return Left
}
return v
}
// GetPadding returns the style's top, right, bottom, and left padding values,
// in that order. 0 is returned for unset values.
func (s Style) GetPadding() (top, right, bottom, left int) {
return s.getAsInt(paddingTopKey),
s.getAsInt(paddingRightKey),
s.getAsInt(paddingBottomKey),
s.getAsInt(paddingLeftKey)
}
// GetPaddingTop returns the style's top padding. If no value is set 0 is
// returned.
func (s Style) GetPaddingTop() int {
return s.getAsInt(paddingTopKey)
}
// GetPaddingRight returns the style's right padding. If no value is set 0 is
// returned.
func (s Style) GetPaddingRight() int {
return s.getAsInt(paddingRightKey)
}
// GetPaddingBottom returns the style's bottom padding. If no value is set 0 is
// returned.
func (s Style) GetPaddingBottom() int {
return s.getAsInt(paddingBottomKey)
}
// GetPaddingLeft returns the style's left padding. If no value is set 0 is
// returned.
func (s Style) GetPaddingLeft() int {
return s.getAsInt(paddingLeftKey)
}
// GetHorizontalPadding returns the style's left and right padding. Unset
// values are measured as 0.
func (s Style) GetHorizontalPadding() int {
return s.getAsInt(paddingLeftKey) + s.getAsInt(paddingRightKey)
}
// GetVerticalPadding returns the style's top and bottom padding. Unset values
// are measured as 0.
func (s Style) GetVerticalPadding() int {
return s.getAsInt(paddingTopKey) + s.getAsInt(paddingBottomKey)
}
// GetColorWhitespace returns the style's whitespace coloring setting. If no
// value is set false is returned.
func (s Style) GetColorWhitespace() bool {
return s.getAsBool(colorWhitespaceKey, false)
}
// GetMargin returns the style's top, right, bottom, and left margins, in that
// order. 0 is returned for unset values.
func (s Style) GetMargin() (top, right, bottom, left int) {
return s.getAsInt(marginTopKey),
s.getAsInt(marginRightKey),
s.getAsInt(marginBottomKey),
s.getAsInt(marginLeftKey)
}
// GetMarginTop returns the style's top margin. If no value is set 0 is
// returned.
func (s Style) GetMarginTop() int {
return s.getAsInt(marginTopKey)
}
// GetMarginRight returns the style's right margin. If no value is set 0 is
// returned.
func (s Style) GetMarginRight() int {
return s.getAsInt(marginRightKey)
}
// GetMarginBottom returns the style's bottom margin. If no value is set 0 is
// returned.
func (s Style) GetMarginBottom() int {
return s.getAsInt(marginBottomKey)
}
// GetMarginLeft returns the style's left margin. If no value is set 0 is
// returned.
func (s Style) GetMarginLeft() int {
return s.getAsInt(marginLeftKey)
}
// GetHorizontalMargins returns the style's left and right margins. Unset
// values are measured as 0.
func (s Style) GetHorizontalMargins() int {
return s.getAsInt(marginLeftKey) + s.getAsInt(marginRightKey)
}
// GetVerticalMargins returns the style's top and bottom padding. Unset values
// are measured as 0.
func (s Style) GetVerticalMargins() int {
return s.getAsInt(marginTopKey) + s.getAsInt(marginBottomKey)
}
// GetBorder returns the style's border style (type Border) and value for the
// top, right, bottom, and left in that order. If no value is set for the
// border style, Border{} is returned. For all other unset values false is
// returend.
func (s Style) GetBorder() (b Border, top, right, bottom, left bool) {
return s.getBorderStyle(),
s.getAsBool(borderTopKey, false),
s.getAsBool(borderRightKey, false),
s.getAsBool(borderBottomKey, false),
s.getAsBool(borderLeftKey, false)
}
// GetBorderStyle returns the style's border style (type Border). If no value
// is set Border{} is returned.
func (s Style) GetBorderStyle() Border {
return s.getBorderStyle()
}
// GetBorderTop returns the style's top border setting. If no value is set
// false is returned.
func (s Style) GetBorderTop() bool {
return s.getAsBool(borderTopKey, false)
}
// GetBorderRight returns the style's right border setting. If no value is set
// false is returned.
func (s Style) GetBorderRight() bool {
return s.getAsBool(borderRightKey, false)
}
// GetBorderBottom returns the style's bottom border setting. If no value is
// set false is returned.
func (s Style) GetBorderBottom() bool {
return s.getAsBool(borderBottomKey, false)
}
// GetBorderLeft returns the style's left border setting. If no value is
// set false is returned.
func (s Style) GetBorderLeft() bool {
return s.getAsBool(borderLeftKey, false)
}
// GetBorderTopForeground returns the style's border top foreground color. If
// no value is set NoColor{} is returned.
func (s Style) GetBorderTopForeground() TerminalColor {
return s.getAsColor(borderTopForegroundKey)
}
// GetBorderRightForeground returns the style's border right foreground color.
// If no value is set NoColor{} is returned.
func (s Style) GetBorderRightForeground() TerminalColor {
return s.getAsColor(borderRightForegroundKey)
}
// GetBorderBottomForeground returns the style's border bottom foreground
// color. If no value is set NoColor{} is returned.
func (s Style) GetBorderBottomForeground() TerminalColor {
return s.getAsColor(borderBottomForegroundKey)
}
// GetBorderLeftForeground returns the style's border bottom foreground
// color. If no value is set NoColor{} is returned.
func (s Style) GetBorderLeftForeground() TerminalColor {
return s.getAsColor(borderLeftForegroundKey)
}
// GetBorderTopBackground returns the style's border top background color. If
// no value is set NoColor{} is returned.
func (s Style) GetBorderTopBackground() TerminalColor {
return s.getAsColor(borderTopBackgroundKey)
}
// GetBorderRightBackground returns the style's border right background color.
// If no value is set NoColor{} is returned.
func (s Style) GetBorderRightBackground() TerminalColor {
return s.getAsColor(borderRightBackgroundKey)
}
// GetBorderBottomBackground returns the style's border bottom background
// color. If no value is set NoColor{} is returned.
func (s Style) GetBorderBottomBackground() TerminalColor {
return s.getAsColor(borderBottomBackgroundKey)
}
// GetBorderLeftBackground returns the style's border bottom background
// color. If no value is set NoColor{} is returned.
func (s Style) GetBorderLeftBackground() TerminalColor {
return s.getAsColor(borderLeftBackgroundKey)
}
// GetBorderTopWidth returns the width of the top border. If borders contain
// runes of varying widths, the widest rune is returned. If no border exists on
// the top edge, 0 is returned.
func (s Style) GetBorderTopWidth() int {
if !s.getAsBool(borderTopKey, false) {
return 0
}
return s.getBorderStyle().GetTopSize()
}
// GetBorderLeftSize returns the width of the left border. If borders contain
// runes of varying widths, the widest rune is returned. If no border exists on
// the left edge, 0 is returned.
func (s Style) GetBorderLeftSize() int {
if !s.getAsBool(borderLeftKey, false) {
return 0
}
return s.getBorderStyle().GetLeftSize()
}
// GetBorderLeftWidth returns the width of the bottom border. If borders
// contain runes of varying widths, the widest rune is returned. If no border
// exists on the left edge, 0 is returned.
func (s Style) GetBorderBottomSize() int {
if !s.getAsBool(borderBottomKey, false) {
return 0
}
return s.getBorderStyle().GetBottomSize()
}
// GetBorderRightSize returns the width of the right border. If borders
// contain runes of varying widths, the widest rune is returned. If no border
// exists on the right edge, 0 is returned.
func (s Style) GetBorderRightSize() int {
if !s.getAsBool(borderRightKey, false) {
return 0
}
return s.getBorderStyle().GetBottomSize()
}
// GetHorizontalBorderSize returns the width of the horizontal borders. If
// borders contain runes of varying widths, the widest rune is returned. If no
// border exists on the horizontal edges, 0 is returned.
func (s Style) GetHorizontalBorderSize() int {
b := s.getBorderStyle()
return b.GetLeftSize() + b.GetRightSize()
}
// GetVerticalBorderSize returns the width of the horizontal borders. If
// borders contain runes of varying widths, the widest rune is returned. If no
// border exists on the horizontal edges, 0 is returned.
func (s Style) GetVerticalBorderSize() int {
b := s.getBorderStyle()
return b.GetTopSize() + b.GetBottomSize()
}
// GetInline returns the style's inline setting. If no value is set false is
// returned.
func (s Style) GetInline() bool {
return s.getAsBool(inlineKey, false)
}
// GetMaxWidth returns the style's max width setting. If no value is set 0 is
// returned.
func (s Style) GetMaxWidth() int {
return s.getAsInt(maxWidthKey)
}
// GetMaxHeight returns the style's max width setting. If no value is set 0 is
// returned.
func (s Style) GetMaxHeight() int {
return s.getAsInt(maxHeightKey)
}
// GetUnderlineSpaces returns whether or not the style is set to underline
// spaces. If not value is set false is returned.
func (s Style) GetUnderlineSpaces() bool {
return s.getAsBool(underlineSpacesKey, false)
}
// GetStrikethroughSpaces returns whether or not the style is set to underline
// spaces. If not value is set false is returned.
func (s Style) GetStrikethroughSpaces() bool {
return s.getAsBool(strikethroughSpacesKey, false)
}
// GetHorizontalFrameSize returns the sum of the style's horizontal margins, padding
// and border widths.
//
// Provisional: this method may be renamed.
func (s Style) GetHorizontalFrameSize() int {
return s.GetHorizontalMargins() + s.GetHorizontalPadding() + s.GetHorizontalBorderSize()
}
// GetVerticalFrameSize returns the sum of the style's horizontal margins, padding
// and border widths.
//
// Provisional: this method may be renamed.
func (s Style) GetVerticalFrameSize() int {
return s.GetVerticalMargins() + s.GetVerticalPadding() + s.GetVerticalBorderSize()
}
// GetFrameSize returns the sum of the margins, padding and border width for
// both the horizontal and vertical margins.
func (s Style) GetFrameSize() (x, y int) {
return s.GetHorizontalFrameSize(), s.GetVerticalFrameSize()
}
// Returns whether or not the given property is set.
func (s Style) isSet(k propKey) bool {
_, exists := s.rules[k]
return exists
}
func (s Style) getAsBool(k propKey, defaultVal bool) bool {
v, ok := s.rules[k]
if !ok {
return defaultVal
}
if b, ok := v.(bool); ok {
return b
}
return defaultVal
}
func (s Style) getAsColor(k propKey) TerminalColor {
v, ok := s.rules[k]
if !ok {
return NoColor{}
}
if c, ok := v.(TerminalColor); ok {
return c
}
return NoColor{}
}
func (s Style) getAsInt(k propKey) int {
v, ok := s.rules[k]
if !ok {
return 0
}
if i, ok := v.(int); ok {
return i
}
return 0
}
func (s Style) getAsPosition(k propKey) Position {
v, ok := s.rules[k]
if !ok {
return Position(0)
}
if p, ok := v.(Position); ok {
return p
}
return Position(0)
}
func (s Style) getBorderStyle() Border {
v, ok := s.rules[borderStyleKey]
if !ok {
return noBorder
}
if b, ok := v.(Border); ok {
return b
}
return noBorder
}
// 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 lines, widest
}
lipgloss-0.4.0/go.mod 0000664 0000000 0000000 00000000447 14114427461 0014466 0 ustar 00root root 0000000 0000000 module github.com/charmbracelet/lipgloss
go 1.15
require (
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-runewidth v0.0.13
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68
github.com/muesli/termenv v0.9.0
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4
)
lipgloss-0.4.0/go.sum 0000664 0000000 0000000 00000003066 14114427461 0014513 0 ustar 00root root 0000000 0000000 github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
lipgloss-0.4.0/join.go 0000664 0000000 0000000 00000010071 14114427461 0014640 0 ustar 00root root 0000000 0000000 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 {
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 {
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()))
left := w - split
right := w - left
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()
}
lipgloss-0.4.0/position.go 0000664 0000000 0000000 00000006440 14114427461 0015552 0 ustar 00root root 0000000 0000000 package lipgloss
import (
"math"
"strings"
"github.com/muesli/reflow/ansi"
)
// Position represents a position along a horizontal or vertical axis. It's in
// situations where an axis is involved, like alignment, joining, placement and
// so on.
//
// A value of 0 represents the start (the left or top) and 1 represents the end
// (the right or bottom). 0.5 represents the center.
//
// There are constants Top, Bottom, Center, Left and Right in this package that
// can be used to aid readability.
type Position float64
func (p Position) value() float64 {
return math.Min(1, math.Max(0, float64(p)))
}
// Position aliases.
const (
Top Position = 0.0
Bottom Position = 1.0
Center Position = 0.5
Left Position = 0.0
Right Position = 1.0
)
// Place places a string or text block vertically in an unstyled box of a given
// width or height.
func Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string {
return PlaceVertical(height, vPos, PlaceHorizontal(width, hPos, str, opts...), opts...)
}
// PlaceHorizontal places a string or text block horizontally in an unstyled
// block of a given width. If the given width is shorter than the max width of
// the string (measured by it's longest line) this will be a noöp.
func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string {
lines, contentWidth := getLines(str)
gap := width - contentWidth
if gap <= 0 {
return str
}
ws := &whitespace{}
for _, opt := range opts {
opt(ws)
}
var b strings.Builder
for i, l := range lines {
// Is this line shorter than the longest line?
short := max(0, contentWidth-ansi.PrintableRuneWidth(l))
switch pos {
case Left:
b.WriteString(l)
b.WriteString(ws.render(gap + short))
case Right:
b.WriteString(ws.render(gap + short))
b.WriteString(l)
default: // somewhere in the middle
totalGap := gap + short
split := int(math.Round(float64(totalGap) * pos.value()))
left := totalGap - split
right := totalGap - left
b.WriteString(ws.render(left))
b.WriteString(l)
b.WriteString(ws.render(right))
}
if i < len(lines)-1 {
b.WriteRune('\n')
}
}
return b.String()
}
// PlaceVertical places a string or text block vertically in an unstyled block
// of a given height. If the given height is shorter than the height of the
// string (measured by it's newlines) then this will be a noöp.
func PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string {
contentHeight := strings.Count(str, "\n") + 1
gap := height - contentHeight
if gap <= 0 {
return str
}
ws := &whitespace{}
for _, opt := range opts {
opt(ws)
}
_, width := getLines(str)
emptyLine := ws.render(width)
b := strings.Builder{}
switch pos {
case Top:
b.WriteString(str)
b.WriteRune('\n')
for i := 0; i < gap; i++ {
b.WriteString(emptyLine)
if i < gap-1 {
b.WriteRune('\n')
}
}
case Bottom:
b.WriteString(strings.Repeat(emptyLine+"\n", gap))
b.WriteString(str)
default: // Somewhere in the middle
split := int(math.Round(float64(gap) * pos.value()))
top := gap - split
bottom := gap - top
b.WriteString(strings.Repeat(emptyLine+"\n", top))
b.WriteString(str)
for i := 0; i < bottom; i++ {
b.WriteRune('\n')
b.WriteString(emptyLine)
}
}
return b.String()
}
lipgloss-0.4.0/runes.go 0000664 0000000 0000000 00000001573 14114427461 0015044 0 ustar 00root root 0000000 0000000 package lipgloss
import (
"strings"
)
// StyleRunes applys a given style to runes at the given indicesin the string.
// Note that you must provide styling options for both matched and unmatched
// runes. Indices out of bounds will be ignored.
func StyleRunes(str string, indices []int, matched, unmatched Style) string {
// Convert slice of indices to a map for easier lookups
m := make(map[int]struct{})
for _, i := range indices {
m[i] = struct{}{}
}
var (
out strings.Builder
group strings.Builder
style Style
runes = []rune(str)
)
for i, r := range runes {
group.WriteRune(r)
_, matches := m[i]
_, nextMatches := m[i+1]
if matches != nextMatches || i == len(runes)-1 {
// Flush
if matches {
style = matched
} else {
style = unmatched
}
out.WriteString(style.Render(group.String()))
group.Reset()
}
}
return out.String()
}
lipgloss-0.4.0/runes_test.go 0000664 0000000 0000000 00000002070 14114427461 0016074 0 ustar 00root root 0000000 0000000 package lipgloss
import (
"strings"
"testing"
)
func TestStyleRunes(t *testing.T) {
t.Parallel()
matchedStyle := NewStyle().Reverse(true)
unmatchedStyle := NewStyle()
tt := []struct {
input string
indices []int
expected string
}{
{
"hello",
[]int{0},
"\x1b[7mh\x1b[0mello",
},
{
"你好",
[]int{1},
"你\x1b[7m好\x1b[0m",
},
{
"hello 你好",
[]int{6, 7},
"hello \x1b[7m你好\x1b[0m",
},
{
"hello",
[]int{1, 3},
"h\x1b[7me\x1b[0ml\x1b[7ml\x1b[0mo",
},
{
"你好",
[]int{0, 1},
"\x1b[7m你好\x1b[0m",
},
}
fn := func(str string, indices []int) string {
return StyleRunes(str, indices, matchedStyle, unmatchedStyle)
}
for i, tc := range tt {
res := fn(tc.input, tc.indices)
if fn(tc.input, tc.indices) != tc.expected {
t.Errorf("Test %d, expected:\n\n`%s`\n`%s`\n\nActual Output:\n\n`%s`\n`%s`\n\n",
i, tc.expected, formatEscapes(tc.expected),
res, formatEscapes(res))
}
}
}
func formatEscapes(str string) string {
return strings.ReplaceAll(str, "\x1b", "\\x1b")
}
lipgloss-0.4.0/set.go 0000664 0000000 0000000 00000040123 14114427461 0014475 0 ustar 00root root 0000000 0000000 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:
// We don't allow negative integers on any of our 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
// renderUnderlinesOnSpaces.
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
// renderStrikethroughOnSpaces.
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 width 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 sets a text alignment rule.
func (s Style) Align(p Position) Style {
s.set(alignKey, 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 a 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(), 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
}
// BorderTopForegroundColor set the top color of the border.
func (s Style) BorderTopForeground(c TerminalColor) Style {
s.set(borderTopForegroundKey, c)
return s
}
// BorderRightForegroundColor set the top color of the border.
func (s Style) BorderRightForeground(c TerminalColor) Style {
s.set(borderRightForegroundKey, c)
return s
}
// BorderBottomForegroundColor set the top color of the border.
func (s Style) BorderBottomForeground(c TerminalColor) Style {
s.set(borderBottomForegroundKey, c)
return s
}
// BorderLeftForegroundColor set the top color of the border.
func (s Style) BorderLeftForeground(c TerminalColor) Style {
s.set(borderLeftForegroundKey, c)
return s
}
// BorderBackgroundColor 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
}
// BorderTopBackgroundColor set the top color of the border.
func (s Style) BorderTopBackground(c TerminalColor) Style {
s.set(borderTopBackgroundKey, c)
return s
}
// BorderRightBackgroundColor set the top color of the border.
func (s Style) BorderRightBackground(c TerminalColor) Style {
s.set(borderRightBackgroundKey, c)
return s
}
// BorderBottomBackgroundColor set the top color of the border.
func (s Style) BorderBottomBackground(c TerminalColor) Style {
s.set(borderBottomBackgroundKey, c)
return s
}
// BorderLeftBackgroundColor set the top color 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 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.
func (s Style) MaxHeight(n int) Style {
o := s.Copy()
o.set(maxHeightKey, n)
return o
}
// 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
}
// 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:
top = i[0]
bottom = i[0]
left = i[1]
right = i[1]
ok = true
case 3:
top = i[0]
left = i[1]
right = i[1]
bottom = i[2]
ok = true
case 4:
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:
top = i[0]
bottom = i[0]
left = i[1]
right = i[1]
ok = true
case 3:
top = i[0]
left = i[1]
right = i[1]
bottom = i[2]
ok = true
case 4:
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:
top = i[0]
bottom = i[0]
left = i[1]
right = i[1]
ok = true
case 3:
top = i[0]
left = i[1]
right = i[1]
bottom = i[2]
ok = true
case 4:
top = i[0]
right = i[1]
bottom = i[2]
left = i[3]
ok = true
}
return top, right, bottom, left, ok
}
lipgloss-0.4.0/size.go 0000664 0000000 0000000 00000002223 14114427461 0014653 0 ustar 00root root 0000000 0000000 package lipgloss
import (
"strings"
"github.com/muesli/reflow/ansi"
)
// Width returns the cell width of characters in the string. ANSI sequences are
// ignored and characters wider than one cell (such as Chinese characters and
// emojis) are appropriately measured.
//
// You should use this instead of len(string) len([]rune(string) as neither
// will give you accurate results.
func Width(str string) (width int) {
for _, l := range strings.Split(str, "\n") {
w := ansi.PrintableRuneWidth(l)
if w > width {
width = w
}
}
return width
}
// Height returns height of a string in cells. This is done simply by
// counting \n characters. If your strings use \r\n for newlines you should
// convert them to \n first, or simply write a separate function for measuring
// height.
func Height(str string) int {
return strings.Count(str, "\n") + 1
}
// Size returns the width and height of the string in cells. ANSI sequences are
// ignored and characters wider than one cell (such as Chinese characters and
// emojis) are appropriately measured.
func Size(str string) (width, height int) {
width = Width(str)
height = Height(str)
return width, height
}
lipgloss-0.4.0/style.go 0000664 0000000 0000000 00000023256 14114427461 0015052 0 ustar 00root root 0000000 0000000 package lipgloss
import (
"strings"
"unicode"
"github.com/muesli/reflow/truncate"
"github.com/muesli/reflow/wordwrap"
"github.com/muesli/reflow/wrap"
"github.com/muesli/termenv"
)
// Property for a key.
type propKey int
// Available properties.
const (
boldKey propKey = iota
italicKey
underlineKey
strikethroughKey
reverseKey
blinkKey
faintKey
foregroundKey
backgroundKey
widthKey
heightKey
alignKey
// Padding.
paddingTopKey
paddingRightKey
paddingBottomKey
paddingLeftKey
colorWhitespaceKey
// Margins.
marginTopKey
marginRightKey
marginBottomKey
marginLeftKey
marginBackgroundKey
// Border runes.
borderStyleKey
// Border edges.
borderTopKey
borderRightKey
borderBottomKey
borderLeftKey
// Border foreground colors.
borderTopForegroundKey
borderRightForegroundKey
borderBottomForegroundKey
borderLeftForegroundKey
// Border background colors.
borderTopBackgroundKey
borderRightBackgroundKey
borderBottomBackgroundKey
borderLeftBackgroundKey
inlineKey
maxWidthKey
maxHeightKey
underlineSpacesKey
strikethroughSpacesKey
)
// A set of properties.
type rules map[propKey]interface{}
// NewStyle returns a new, empty Style. While it's syntactic sugar for the
// Style{} primitive, it's recommended to use this function for creating styles
// incase the underlying implementation changes.
func NewStyle() Style {
return Style{}
}
// Style contains a set of rules that comprise a style as a whole.
type Style struct {
rules map[propKey]interface{}
value string
}
// SetString sets the underlying string value for this style. To render once
// the underlying string is set, use the Style.String. This method is
// a convenience for cases when having a stringer implementation is handy, such
// as when using fmt.Sprintf. You can also simply define a style and render out
// strings directly with Style.Render.
func (s Style) SetString(str string) Style {
s.value = str
return s
}
// String implements stringer for a Style, returning the rendered result based
// on the rules in this style. An underlying string value must be set with
// Style.SetString prior to using this method.
func (s Style) String() string {
return s.Render(s.value)
}
// Copy returns a copy of this style, including any underlying string values.
func (s Style) Copy() Style {
o := NewStyle()
o.init()
for k, v := range s.rules {
o.rules[k] = v
}
o.value = s.value
return o
}
// Inherit takes values from the style in the argument applies them to this
// style, overwriting existing definitions. Only values explicitly set on the
// style in argument will be applied.
//
// Margins, padding, and underlying string values are not inherited.
func (s Style) Inherit(i Style) Style {
s.init()
for k, v := range i.rules {
switch k {
case marginTopKey, marginRightKey, marginBottomKey, marginLeftKey:
// Margins are not inherited
continue
case paddingTopKey, paddingRightKey, paddingBottomKey, paddingLeftKey:
// Padding is not inherited
continue
case backgroundKey:
s.rules[k] = v
// The margins also inherit the background color
if !s.isSet(marginBackgroundKey) && !i.isSet(marginBackgroundKey) {
s.rules[marginBackgroundKey] = v
}
}
if _, exists := s.rules[k]; exists {
continue
}
s.rules[k] = v
}
return s
}
// Render applies the defined style formatting to a given string.
func (s Style) Render(str string) string {
var (
te termenv.Style
teSpace termenv.Style
teWhitespace termenv.Style
bold = s.getAsBool(boldKey, false)
italic = s.getAsBool(italicKey, false)
underline = s.getAsBool(underlineKey, false)
strikethrough = s.getAsBool(strikethroughKey, false)
reverse = s.getAsBool(reverseKey, false)
blink = s.getAsBool(blinkKey, false)
faint = s.getAsBool(faintKey, false)
fg = s.getAsColor(foregroundKey)
bg = s.getAsColor(backgroundKey)
width = s.getAsInt(widthKey)
height = s.getAsInt(heightKey)
align = s.getAsPosition(alignKey)
topPadding = s.getAsInt(paddingTopKey)
rightPadding = s.getAsInt(paddingRightKey)
bottomPadding = s.getAsInt(paddingBottomKey)
leftPadding = s.getAsInt(paddingLeftKey)
colorWhitespace = s.getAsBool(colorWhitespaceKey, true)
inline = s.getAsBool(inlineKey, false)
maxWidth = s.getAsInt(maxWidthKey)
maxHeight = s.getAsInt(maxHeightKey)
underlineSpaces = underline && s.getAsBool(underlineSpacesKey, true)
strikethroughSpaces = strikethrough && s.getAsBool(strikethroughSpacesKey, true)
// Do we need to style whitespace (padding and space outside
// paragraphs) separately?
styleWhitespace = reverse
// Do we need to style spaces separately?
useSpaceStyler = underlineSpaces || strikethroughSpaces
)
if len(s.rules) == 0 {
return str
}
// Enable support for ANSI on the legacy Windows cmd.exe console. This is a
// no-op on non-Windows systems and on Windows runs only once.
enableLegacyWindowsANSI()
if bold {
te = te.Bold()
}
if italic {
te = te.Italic()
}
if underline {
te = te.Underline()
}
if reverse {
if reverse {
teWhitespace = teWhitespace.Reverse()
}
te = te.Reverse()
}
if blink {
te = te.Blink()
}
if faint {
te = te.Faint()
}
if fg != noColor {
fgc := fg.color()
te = te.Foreground(fgc)
if styleWhitespace {
teWhitespace = teWhitespace.Foreground(fgc)
}
if useSpaceStyler {
teSpace = teSpace.Foreground(fgc)
}
}
if bg != noColor {
bgc := bg.color()
te = te.Background(bgc)
if colorWhitespace {
teWhitespace = teWhitespace.Background(bgc)
}
if useSpaceStyler {
teSpace = teSpace.Background(bgc)
}
}
if underline {
te = te.Underline()
}
if strikethrough {
te = te.CrossOut()
}
if underlineSpaces {
teSpace = teSpace.Underline()
}
if strikethroughSpaces {
teSpace = teSpace.CrossOut()
}
// Strip newlines in single line mode
if inline {
str = strings.Replace(str, "\n", "", -1)
}
// Word wrap
if !inline && width > 0 {
wrapAt := width - leftPadding - rightPadding
str = wordwrap.String(str, wrapAt)
str = wrap.String(str, wrapAt) // force-wrap long strings
}
// Render core text
{
var b strings.Builder
l := strings.Split(str, "\n")
for i := range l {
if useSpaceStyler {
// Look for spaces and apply a different styler
for _, r := range l[i] {
if unicode.IsSpace(r) {
b.WriteString(teSpace.Styled(string(r)))
continue
}
b.WriteString(te.Styled(string(r)))
}
} else {
b.WriteString(te.Styled(l[i]))
}
if i != len(l)-1 {
b.WriteRune('\n')
}
}
str = b.String()
}
// Padding
if !inline {
if leftPadding > 0 {
var st *termenv.Style
if colorWhitespace || styleWhitespace {
st = &teWhitespace
}
str = padLeft(str, leftPadding, st)
}
if rightPadding > 0 {
var st *termenv.Style
if colorWhitespace || styleWhitespace {
st = &teWhitespace
}
str = padRight(str, rightPadding, st)
}
if topPadding > 0 {
str = strings.Repeat("\n", topPadding) + str
}
if bottomPadding > 0 {
str += strings.Repeat("\n", bottomPadding)
}
}
// Height
if height > 0 {
h := strings.Count(str, "\n") + 1
if height > h {
str += strings.Repeat("\n", height-h)
}
}
// Set alignment. This will also pad short lines with spaces so that all
// lines are the same length, so we run it under a few different conditions
// beyond alignment.
{
numLines := strings.Count(str, "\n")
if !(numLines == 0 && width == 0) {
var st *termenv.Style
if colorWhitespace || styleWhitespace {
st = &teWhitespace
}
str = alignText(str, align, width, st)
}
}
if !inline {
str = s.applyBorder(str)
str = s.applyMargins(str, inline)
}
// Truncate according to MaxWidth
if maxWidth > 0 {
lines := strings.Split(str, "\n")
for i := range lines {
lines[i] = truncate.String(lines[i], uint(maxWidth))
}
str = strings.Join(lines, "\n")
}
// Truncate according to MaxHeight
if maxHeight > 0 {
lines := strings.Split(str, "\n")
str = strings.Join(lines[:min(maxHeight, len(lines))], "\n")
}
return str
}
func (s Style) applyMargins(str string, inline bool) string {
var (
topMargin = s.getAsInt(marginTopKey)
rightMargin = s.getAsInt(marginRightKey)
bottomMargin = s.getAsInt(marginBottomKey)
leftMargin = s.getAsInt(marginLeftKey)
styler termenv.Style
)
bgc := s.getAsColor(marginBackgroundKey)
if bgc != noColor {
styler = styler.Background(bgc.color())
}
// Add left and right margin
str = padLeft(str, leftMargin, &styler)
str = padRight(str, rightMargin, &styler)
// Top/bottom margin
if !inline {
_, width := getLines(str)
spaces := strings.Repeat(" ", width)
if topMargin > 0 {
str = styler.Styled(strings.Repeat(spaces+"\n", topMargin)) + str
}
if bottomMargin > 0 {
str += styler.Styled(strings.Repeat("\n"+spaces, bottomMargin))
}
}
return str
}
// Apply left padding.
func padLeft(str string, n int, style *termenv.Style) string {
if n == 0 {
return str
}
sp := strings.Repeat(" ", n)
if style != nil {
sp = style.Styled(sp)
}
b := strings.Builder{}
l := strings.Split(str, "\n")
for i := range l {
b.WriteString(sp)
b.WriteString(l[i])
if i != len(l)-1 {
b.WriteRune('\n')
}
}
return b.String()
}
// Apply right right padding.
func padRight(str string, n int, style *termenv.Style) string {
if n == 0 || str == "" {
return str
}
sp := strings.Repeat(" ", n)
if style != nil {
sp = style.Styled(sp)
}
b := strings.Builder{}
l := strings.Split(str, "\n")
for i := range l {
b.WriteString(l[i])
b.WriteString(sp)
if i != len(l)-1 {
b.WriteRune('\n')
}
}
return b.String()
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
lipgloss-0.4.0/unset.go 0000664 0000000 0000000 00000016655 14114427461 0015055 0 ustar 00root root 0000000 0000000 package lipgloss
// UnsetBold removes the bold style rule, if set.
func (s Style) UnsetBold() Style {
delete(s.rules, boldKey)
return s
}
// UnsetItalic removes the italic style rule, if set.
func (s Style) UnsetItalic() Style {
delete(s.rules, italicKey)
return s
}
// UnsetItalic removes the underline style rule, if set.
func (s Style) UnsetUnderline() Style {
delete(s.rules, underlineKey)
return s
}
// UnsetStrikethrough removes the strikethrough style rule, if set.
func (s Style) UnsetStrikethrough() Style {
delete(s.rules, strikethroughKey)
return s
}
// UnsetReverse removes the reverse style rule, if set.
func (s Style) UnsetReverse() Style {
delete(s.rules, reverseKey)
return s
}
// UnsetBlink removes the bold style rule, if set.
func (s Style) UnsetBlink() Style {
delete(s.rules, blinkKey)
return s
}
// UnsetFaint removes the faint style rule, if set.
func (s Style) UnsetFaint() Style {
delete(s.rules, faintKey)
return s
}
// UnsetForeground removes the foreground style rule, if set.
func (s Style) UnsetForeground() Style {
delete(s.rules, foregroundKey)
return s
}
// UnsetBackground removes the background style rule, if set.
func (s Style) UnsetBackground() Style {
delete(s.rules, backgroundKey)
return s
}
// UnsetWidth removes the width style rule, if set.
func (s Style) UnsetWidth() Style {
delete(s.rules, widthKey)
return s
}
// UnsetHeight removes the height style rule, if set.
func (s Style) UnsetHeight() Style {
delete(s.rules, heightKey)
return s
}
// UnsetAlign removes the text alignment style rule, if set.
func (s Style) UnsetAlign() Style {
delete(s.rules, alignKey)
return s
}
// UnsetPadding removes all padding style rules.
func (s Style) UnsetPadding() Style {
delete(s.rules, paddingLeftKey)
delete(s.rules, paddingRightKey)
delete(s.rules, paddingTopKey)
delete(s.rules, paddingBottomKey)
return s
}
// UnsetPaddingLeft removes the left padding style rule, if set.
func (s Style) UnsetPaddingLeft() Style {
delete(s.rules, paddingLeftKey)
return s
}
// UnsetPaddingRight removes the right padding style rule, if set.
func (s Style) UnsetPaddingRight() Style {
delete(s.rules, paddingRightKey)
return s
}
// UnsetPaddingTop removes the top padding style rule, if set.
func (s Style) UnsetPaddingTop() Style {
delete(s.rules, paddingTopKey)
return s
}
// UnsetPaddingBottom removes the bottom style rule, if set.
func (s Style) UnsetPaddingBottom() Style {
delete(s.rules, paddingBottomKey)
return s
}
// UnsetColorWhitespace removes the rule for coloring padding, if set.
func (s Style) UnsetColorWhitespace() Style {
delete(s.rules, colorWhitespaceKey)
return s
}
// UnsetMargins removes all margin style rules.
func (s Style) UnsetMargins() Style {
delete(s.rules, marginLeftKey)
delete(s.rules, marginRightKey)
delete(s.rules, marginTopKey)
delete(s.rules, marginBottomKey)
return s
}
// UnsetMarginLeft removes the left margin style rule, if set.
func (s Style) UnsetMarginLeft() Style {
delete(s.rules, marginLeftKey)
return s
}
// UnsetMarginRight removes the right margin style rule, if set.
func (s Style) UnsetMarginRight() Style {
delete(s.rules, marginRightKey)
return s
}
// UnsetMarginTop removes the top margin style rule, if set.
func (s Style) UnsetMarginTop() Style {
delete(s.rules, marginTopKey)
return s
}
// UnsetMarginBottom removes the bottom margin style rule, if set.
func (s Style) UnsetMarginBottom() Style {
delete(s.rules, marginBottomKey)
return s
}
// UnsetMarginBackground removes the margin's background color. Note that the
// margin's background color can be set from the background color of another
// style during inheritance.
func (s Style) UnsetMarginBackground() Style {
delete(s.rules, marginBackgroundKey)
return s
}
// UnsetBorderStyle removes the border style rule, if set.
func (s Style) UnsetBorderStyle() Style {
delete(s.rules, borderStyleKey)
return s
}
// UnsetBorderTop removes the border top style rule, if set.
func (s Style) UnsetBorderTop() Style {
delete(s.rules, borderTopKey)
return s
}
// UnsetBorderRight removes the border right style rule, if set.
func (s Style) UnsetBorderRight() Style {
delete(s.rules, borderRightKey)
return s
}
// UnsetBorderBottom removes the border bottom style rule, if set.
func (s Style) UnsetBorderBottom() Style {
delete(s.rules, borderBottomKey)
return s
}
// UnsetBorderLeft removes the border left style rule, if set.
func (s Style) UnsetBorderLeft() Style {
delete(s.rules, borderLeftKey)
return s
}
// UnsetBorderForeground removes all border foreground color styles, if set.
func (s Style) UnsetBorderForeground() Style {
delete(s.rules, borderTopForegroundKey)
delete(s.rules, borderRightForegroundKey)
delete(s.rules, borderBottomForegroundKey)
delete(s.rules, borderLeftForegroundKey)
return s
}
// UnsetBorderTopForeground removes the top border foreground color rule,
// if set.
func (s Style) UnsetBorderTopForeground() Style {
delete(s.rules, borderTopForegroundKey)
return s
}
// UnsetBorderRightForeground removes the right border foreground color rule,
// if set.
func (s Style) UnsetBorderRightForeground() Style {
delete(s.rules, borderRightForegroundKey)
return s
}
// UnsetBorderBottomForeground removes the bottom border foreground color
// rule, if set.
func (s Style) UnsetBorderBottomForeground() Style {
delete(s.rules, borderBottomForegroundKey)
return s
}
// UnsetBorderLeftForeground removes the left border foreground color rule,
// if set.
func (s Style) UnsetBorderLeftForeground() Style {
delete(s.rules, borderLeftForegroundKey)
return s
}
// UnsetBorderBackground removes all border background color styles, if
// set.
func (s Style) UnsetBorderBackground() Style {
delete(s.rules, borderTopBackgroundKey)
delete(s.rules, borderRightBackgroundKey)
delete(s.rules, borderBottomBackgroundKey)
delete(s.rules, borderLeftBackgroundKey)
return s
}
// UnsetBorderTopBackground removes the top border background color rule,
// if set.
func (s Style) UnsetBorderTopBackgroundColor() Style {
delete(s.rules, borderTopBackgroundKey)
return s
}
// UnsetBorderRightBackground removes the right border background color
// rule, if set.
func (s Style) UnsetBorderRightBackground() Style {
delete(s.rules, borderRightBackgroundKey)
return s
}
// UnsetBorderBottomBackground removes the bottom border background color
// rule, if set.
func (s Style) UnsetBorderBottomBackground() Style {
delete(s.rules, borderBottomBackgroundKey)
return s
}
// UnsetBorderLeftBackground removes the left border color rule, if set.
func (s Style) UnsetBorderLeftBackground() Style {
delete(s.rules, borderLeftBackgroundKey)
return s
}
// UnsetInline removes the inline style rule, if set.
func (s Style) UnsetInline() Style {
delete(s.rules, inlineKey)
return s
}
// UnsetMaxWidth removes the max width style rule, if set.
func (s Style) UnsetMaxWidth() Style {
delete(s.rules, maxWidthKey)
return s
}
// UnsetMaxHeight removes the max height style rule, if set.
func (s Style) UnsetMaxHeight() Style {
delete(s.rules, maxHeightKey)
return s
}
// UnsetUnderlineSpaces removes the value set by UnderlineSpaces.
func (s Style) UnsetUnderlineSpaces() Style {
delete(s.rules, underlineSpacesKey)
return s
}
// UnsetUnderlineSpaces removes the value set by UnsetStrikethroughSpaces.
func (s Style) UnsetStrikethroughSpaces() Style {
delete(s.rules, strikethroughSpacesKey)
return s
}
// UnsetString sets the underlying string value to the empty string.
func (s Style) UnsetString() Style {
s.value = ""
return s
}
lipgloss-0.4.0/whitespace.go 0000664 0000000 0000000 00000003067 14114427461 0016044 0 ustar 00root root 0000000 0000000 package lipgloss
import (
"strings"
"github.com/muesli/reflow/ansi"
"github.com/muesli/termenv"
)
// whitespace is a whitespace renderer.
type whitespace struct {
style termenv.Style
chars string
}
// Render whitespaces.
func (w whitespace) render(width int) string {
if w.chars == "" {
w.chars = " "
}
r := []rune(w.chars)
j := 0
b := strings.Builder{}
// Cycle through runes and print them into the whitespace.
for i := 0; i < width; {
b.WriteRune(r[j])
j++
if j >= len(r) {
j = 0
}
i += ansi.PrintableRuneWidth(string(r[j]))
}
// Fill any extra gaps white spaces. This might be necessary if any runes
// are more than one cell wide, which could leave a one-rune gap.
short := width - ansi.PrintableRuneWidth(b.String())
if short > 0 {
b.WriteString(strings.Repeat(" ", short))
}
return w.style.Styled(b.String())
}
// WhiteSpaceOption sets a styling rule for rendering whitespace.
type WhitespaceOption func(*whitespace)
// WithWhitespaceForeground sets the color of the characters in the whitespace.
func WithWhitespaceForeground(c TerminalColor) WhitespaceOption {
return func(w *whitespace) {
w.style = w.style.Foreground(c.color())
}
}
// WithWhiteSpaceBackground sets the background color of the whitespace.
func WithWhitespaceBackground(c TerminalColor) WhitespaceOption {
return func(w *whitespace) {
w.style = w.style.Background(c.color())
}
}
// WithWhitespaceChars sets the characters to be rendered in the whitespace.
func WithWhitespaceChars(s string) WhitespaceOption {
return func(w *whitespace) {
w.chars = s
}
}