Releases: charmbracelet/lipgloss
v2.0.0-beta.3
Padding, Schmadding
This new beta release reverts back to a major change when it comes to using regular spacing for padding. We found that using NBSP \u00a0
causes more problems than it solves. Things like simply copying the output from the terminal and then paste it back wouldn't work if NBSPs exist in a command.
To solve this, we've added style.PaddingChar(rune)
and style.MarginChar(rune)
to customize the characters used in padding and margins respectively.
Changelog
New Features
- d2233fa: feat: add padding and margin character support (@aymanbagabas)
- 045a87b: feat: add padding and margin character support (#546) (@aymanbagabas)
Bug fixes
- 99fc0ff: fix: correct nbsp codepoint (@aymanbagabas)
- 8e1c474: fix: ensure we strip out \r\n from strings when getting lines (@aymanbagabas)
- 4cb83b5: fix: make padding char a style prop (@caarlos0)
- 07cadae: fix: revert back to regular space (@caarlos0)
Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or on Discord.
v2.0.0-beta.2
So Hot Right Now: Lip Gloss v2 Beta 2
This release builds on top of the last beta 1 release. It includes a new API for compositing layers and views, table enhancements, and a bunch of bug fixes. Let's get into it!
Compositing
The big news in this release is compositing. Here's what it looks like:
box := lipgloss.NewStyle().
Width(10).
Height(5).
Border(lipgloss.NormalBorder())
// Make some layers.
a := lipgloss.NewLayer(box.Render("Who wants marmalade?"))
b := lipgloss.NewLayer(box.Render("I do!"))
// Put layers in a canvas.
canvas := lipgloss.NewCanvas(
a.X(5).Y(10).Z(1),
b.X(3).Y(7)
)
// Render it all out.
lipgloss.Println(canvas.Render())
Also note that layers can also be nested (see Layer.AddLayers
).
Otherwise, that’s all there is to it!
For more info see Layer
, Canvas
, and the compositing example.
Table Enhancements
Tables are one of the most beloved Charm components, and we've been working to
make them as polished as possible. In this release several bugs were fixed,
and many other rendering enhancements were made. You can check most of the fixes
on #526.
We're also refactoring the Bubbles' table component to use the Lip Gloss' table package, and making their APIs similar, as before they were relatively different. This means that if you use Tables via Bubbles in your Bubble Tea app, you'll be able to reap the benefits soon too!
Changelog
New Features
- ad4ad6d: feat(examples): add clickable example (@aymanbagabas)
- c20d404: feat(examples): clickable: make it "pure" (@aymanbagabas)
- 2790cd0: feat(table): add
BaseStyle
to set background color for the whole table (#519) (@andreynering) - e2227d9: feat(table): add some extra getters needed on bubbles (@andreynering)
- 488eb37: feat(table): expose getters for borders (@andreynering)
- 587de15: feat(table): further enhance and fix table height handling (@andreynering)
- 4b89e65: feat(table): rework height rendering logic to fix related bugs (@andreynering)
- 6002441: feat: export
GetHeaders
,GetData
andDataToMatrix
(@andreynering) - 75c0569: feat: initial compositing implementation (@aymanbagabas)
- 2c4751e: feat: initial compositing implementation (#471) (@meowgorithm)
Bug fixes
- 9ae54a0: fix(border): fix returned border sizes when only style was set (@andreynering)
- c80afba: fix(canvas): negative coordinates calculation (@aymanbagabas)
- c201d59: fix(lint): GoDoc (@meowgorithm)
- f274d05: fix(lint): fix linting issues after golangci-lint v2 upgrade (@andreynering)
- 1427d82: fix(table): add tests + fix scenario of offset + manual table height (@andreynering)
- c0f4b07: fix(table): do not print final empty row when overflow (@andreynering)
- bc517ae: fix(table): fix headers with custom vertical padding (@andreynering)
- baa7611: fix(table): fix horizontal shrink with outline borders only (@andreynering)
- 63e53c1: fix(table): fix layout for tables with inner borders only (@andreynering)
- c6b946e: fix(table): fix panic when style func is
nil
(#508) (@andreynering) - a3d464b: fix(table): fix rendering for bordered cells (@andreynering)
- b36834a: fix(table): improve height handling for cells with vertical padding (@andreynering)
Documentation updates
- e9f399e: docs(example): add compositing example (#544) (@meowgorithm)
- fabe514: docs(examples): add compositing to layout example (@meowgorithm)
- 4781de2: docs(examples): update Bubble Tea examples to use new API (@aymanbagabas)
- 946081c: docs(table): document that headers are never wrapped (@andreynering)
Other work
- 7edbc41: ci: fix linting (@andreynering)
- c7f615e: ci: fix new lint issues after golangci-lint v2 upgrade (#510) (@andreynering)
- d102bea: ci: sync golangci-lint config (#509) (@github-actions[bot])
- 40640fe: refactor: modernize loops (@andreynering)
- 23e0f7f: refactor: v2 rename offset (#512) (@bashbunni)
How’s it going?
Feel free to reach out, ask questions, give feedback, and let us know how it's going. We’d love to know what you think.
Part of Charm.
Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة
v2.0.0-beta.1
Who said a beta release can’t be exciting?
We're thrilled to announce the first beta release of Lip Gloss v2! This release builds on top of the last alpha 2 release. Very little has changed since the last alpha, which means we’re getting closer to a proper v2.0.0
.
The only change here is that you can no longer use hexadecimal and integer format when defining colors. We found there were just to many gotchas and this way the API remains backwards compatible.
// Before in alpha 2
// This is a bug! It's not intuitive to use integers here.
// Should this be a hex color or ANSI(204)?
a := lipgloss.Color(0x0000cc) // 0xcc is 204, which was interpreted as an ANSI color, not #0000cc
// After
a := lipgloss.Color("#0000cc") // This is a hex color
b := lipgloss.Color("204") // This is an ANSI color
c := lipgloss.ANSIColor(204) // Equivalent to b
🌈 More on Lip Gloss v2
Just getting into Lip Gloss v2? Check out the full v2 release notes and upgrade guide.
💝 How’s it going?
Feel free to reach out, ask questions, give feedback, and let us know how it's going. We’d love to know what you think.
Part of Charm.
Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة
v1.1.0
Tables, Improved
In this release, the inimitable @andreynering and @bashbunni majorly overhauled on the table sizing and content wrapping algorithms. Tables will now be much smarter on deciding the ideal width of each column, and contents now wraps by default inside cells.
// Table content wraps by default.
t := table.New().
Headers(someHeaders...).
Rows(someRows...).
Width(80)
fmt.Println(t)
// Actually, let's not wrap the content.
t := table.New().
Headers(someHeaders...).
Rows(someRows...).
Width(80).
Wrap(false)
fmt.Println(t)
New Border Styles
Also, we added two new border styles that you can use to generate tables in Markdown and ASCII styles.
Markdown Tables
To render tables correctly for Markdown you'll want to use lipgloss.MarkdownBorder
and disable the top and bottom borders.
t := table.New().
Headers(someHeaders...).
Rows(someRows).
Border(lipgloss.MarkdownBorder()).
BorderTop(false).
BorderBottom(false)
fmt.Println(t)
ASCII Tables
To render an ASCII-style table use lipgloss.ASCIIBorder
.
t := table.New().
Headers(someHeaders...).
Rows(someRows).
Border(lipgloss.ASCIIBorder())
fmt.Println(t)
Thanks everyone
Special thanks to @aymanbagabas, @bashbunni, @andreynering, and @caarlos0 for or all the work on this release!
Changelog
New Features
- 7862f52: feat(table): improve sizing and behavior: wrap by default, overflow optionally (@andreynering)
- 1f1209e: feat(table): use cellbuf to preserve styles for wrapped content (@bashbunni)
- c454a0a: feat(tables): add markdown and ascii border style for tables (#480) (@andreynering)
- bafb8fd: feat(tree): hide children (#460) (@bashbunni)
- 9942166: feat: style ranges (#458) (@caarlos0)
Bug fixes
- 9500f10: fix(table): ensure we're passing the right row index to
styleFunc
(@andreynering) - 7b191c5: fix(test): make table wrapping tests use golden files (@bashbunni)
- 9b8304f: fix: border size getters when implicit borders are present (#411) (@meowgorithm)
- 022e967: fix: range test (@caarlos0)
Other work
- ecc1bd0: fix: comment on min func in utils (@derezzolution)
- 5cd2074: style(table): improve naming of the resizer functions (@andreynering)
- 9cfb7dd: test(table): check truncation logic for overflow and nowrap (@bashbunni)
- 2aa2eb0: test(table): test wrapping cell styles (@bashbunni)
- ca67d0f: chore(lint): apply De Morgans law to the if statement (@aymanbagabas)
- 0fbb070: chore(lint): fix lint ignore comments (@andreynering)
Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or on Discord.
v2.0.0-alpha.2
Do you think you can handle Lip Gloss v2?
We’re really excited for you to try Lip Gloss v2! Keep in mind that this is an early alpha release and things may change.
Note
We take API changes seriously and strive to make the upgrade process as simple as possible. We believe the changes bring necessary improvements as well as pave the way for the future. If something feels way off, let us know.
The big changes are that Styles are now deterministic (λipgloss!) and you can be much more intentional with your inputs and outputs. Why does this matter?
Playing nicely with others
v2 gives you precise control over I/O. One of the issues we saw with the Lip Gloss and Bubble Tea v1s is that they could fight over the same inputs and outputs, producing lock-ups. The v2s now operate in lockstep.
Querying the right inputs and outputs
In v1, Lip Gloss defaulted to looking at stdin
and stdout
when downsampling colors and querying for the background color. This was not always necessarily what you wanted. For example, if your application was writing to stderr
while redirecting stdout
to a file, the program would erroneously think output was not a TTY and strip colors. Lip Gloss v2 gives you control and intentionality over this.
Going beyond localhost
Did you know TUIs and CLIs can be served over the network? For example, Wish allows you to serve Bubble Tea and Lip Gloss over SSH. In these cases, you need to work with the input and output of the connected clients as opposed to stdin
and stdout
, which belong to the server. Lip Gloss v2 gives you flexibility around this in a more natural way.
🧋 Using Lip Gloss with Bubble Tea?
Make sure you get all the latest v2s as they’ve been designed to work together.
go get github.com/charmbracelet/bubbletea/v2@v2.0.0-alpha.2
go get github.com/charmbracelet/bubbles/v2@v2.0.0-alpha.2
go get github.com/charmbracelet/lipgloss/v2@v2.0.0-alpha.2
🐇 Quick upgrade
If you don't have time for changes and just want to upgrade to Lip Gloss v2 as fast as possible, do the following:
Use the compat
package
The compat
package provides adaptive colors, complete colors, and complete adaptive colors:
import "github.com/charmbracelet/lipgloss/v2/compat"
// Before
color := lipgloss.AdaptiveColor{Light: "#f1f1f1", Dark: "#cccccc"}
// After
color := compat.AdaptiveColor{Light: "#f1f1f1", Dark: "#cccccc"}
compat
works by looking at stdin
and stdout
on a global basis. Want to change the inputs and outputs? Knock yourself out:
import (
"github.com/charmbracelet/lipgloss/v2/compat"
"github.com/charmbracelet/colorprofile"
)
func init() {
// Let’s use stderr instead of stdout.
compat.HasDarkBackground = lipgloss.HasDarkBackground(os.Stdin, os.Stderr)
compat.Profile = colorprofile.Detect(os.Stderr, os.Environ())
}
Use the new Lip Gloss writer
If you’re using Bubble Tea with Lip Gloss you can skip this step. If you're using Lip Gloss in a standalone fashion, use lipgloss.Println
(and lipgloss.Printf
and so on) when printing your output:
s := someStyle.Render("Fancy Lip Gloss Output")
// Before
fmt.Println(s)
// After
lipgloss.Println(s)
That’s it!
All this said, we encourage you to read on to get the full benefit of v2.
👀 What’s changing?
Only a couple main things that are changing in Lip Gloss v2:
- Color downsampling in non-Bubble-Tea uses cases is now a manual proccess (don't worry, it's easy)
- Background color detection and adaptive colors are manual, and intentional (but optional)
🪄 Downsampling colors with a writer
One of the best things about Lip Gloss is that it can automatically downsample colors to the best available profile, stripping colors (and ANSI) entirely when output is not a TTY.
If you're using Lip Gloss with Bubble Tea there's nothing to do here: downsampling is built into Bubble Tea v2. If you're not using Bubble Tea you now need to use a writer to downsample colors. Lip Gloss writers are a drop-in replacement for the usual functions found in the fmt
package:
s := someStyle.Render("Hello!")
// Downsample and print to stdout.
lipgloss.Println(s)
// Render to a variable.
downsampled := lipgloss.Sprint(s)
// Print to stderr.
lipgloss.Fprint(os.Stderr, s)
🌛 Background color detection and adaptive colors
Rendering different colors depending on whether the terminal has a light or dark background is an awesome power. Lip Gloss v2 gives you more control over this progress. This especially matters when input and output are not stdin
and stdout
.
If that doesn’t matter to you and you're only working with stdout
you skip this via compat
above, though encourage you to explore this new functionality.
With Bubble Tea
In Bubble Tea, request the background color, listen for a BackgroundColorMsg
in your update, and respond accordingly.
// Query for the background color.
func (m model) Init() (tea.Model, tea.Cmd) {
return m, tea.RequestBackgroundColor
}
// Listen for the response and initialize your styles accordigly.
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.BackgroundColorMsg:
// Initialize your styles now that you know the background color.
m.styles = newStyles(msg.IsDark())
return m, nil
}
}
type styles {
myHotStyle lipgloss.Style
}
func newStyles(bgIsDark bool) (s styles) {
lightDark := lipgloss.LightDark(bgIsDark) // just a helper function
return styles{
myHotStyle := lipgloss.NewStyle().Foreground(lightDark("#f1f1f1", "#333333"))
}
}
Standalone
If you're not using Bubble Tea you simply can perform the query manually:
// Detect the background color. Notice we're writing to stderr.
hasDarkBG, err := lipgloss.HasDarkBackground(os.Stdin, os.Stderr)
if err != nil {
log.Fatal("Oof:", err)
}
// Create a helper for choosing the appropriate color.
lightDark := lipgloss.LightDark(hasDarkBG)
// Declare some colors.
thisColor := lightDark("#C5ADF9", "#864EFF")
thatColor := lightDark("#37CD96", "#22C78A")
// Render some styles.
a := lipgloss.NewStyle().Foreground(thisColor).Render("this")
b := lipgloss.NewStyle().Foreground(thatColor).Render("that")
// Print to stderr.
lipgloss.Fprintf(os.Stderr, "my fave colors are %s and %s...for now.", a, b)
🥕 Other stuff
Colors are now color.Color
lipgloss.Color()
now produces an idomatic color.Color
, whereas before colors were type lipgloss.TerminalColor
. Generally speaking, this is more of an implementation detail, but it’s worth noting the structural differences.
// Before
type TerminalColor interface{/* ... */}
type Color string
// After
func Color(any) color.Color
type ANSIColor uint
type RGBColor struct { R, G, B uint8 }
Quotes are now optional in colors
There are also some quality-of-life niceties around color UX:
a := lipgloss.Color("#f1f1f1") // This still works
b := lipgloss.Color(0xf1f1f1) // But this also works
c := lipgloss.Color("212") // You can still do this
d := lipgloss.Color(212) // But you can also do this too
Changelog
- (v2) adaptive colors + writers by @meowgorithm in #397
- (v2) feat: add adaptive color package by @aymanbagabas in #359
- chore: rename LightDark to Adapt per @bashbunni's acute suggestion by @meowgorithm in #392
- refactor: unexport isDarkColor helper by @bashbunni in #410
- (v2) fix: query both stdin and stdout for background color on non-Windows … by @aymanbagabas in #416
- Sync golangci-lint config by @github-actions in #421
- Sync golangci-lint config by @github-actions in #422
- chore(lint): update soft lint directives; fix soft lint issues by @meowgorithm in #423
- V2 examples by @meowgorithm in #426
- fix: manually query terminal for background color by @aymanbagabas in #429
- (v2) feat: complete color support by @aymanbagabas in #420
- (v2) feat: add compat package by @aymanbagabas in #419
Full Changelog: v1.0.0...v2.0.0-alpha.2
🌈 Feedback
That's a wrap! Feel free to reach out, ask questions, and let us know how it's going. We'd love to know what you think.
Part of Charm.
Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة
v1.0.0
v0.13.1
Table improvements, on stream
@bashbunni went to town in this release and fixed a bunch of bugs, mostly around table. Best of all, she did most of it on stream.
Changelog
Table
- fix(table): use table height by @Broderick-Westrope in #358
- fix(table): unset data rows without causing nil pointer err by @bashbunni in #372
- fix(table): shared indices for first row of data and headers (StyleFunc bug) by @bashbunni in #377
- fix(table): do not shrink table with offset by @bashbunni in #373
- fix(table): include margins for cell width by @bashbunni in #401
Other Stuff
- fix(render): strip carriage returns from strings by @bashbunni in #386
Bonus
New Contributors
- @Broderick-Westrope made their first contribution in #358
- @swrenn made their first contribution in #364
Full Changelog: v0.13.0...v0.13.1
Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or on Discord.
v0.13.0
Woodn’t you know, Lip Gloss has trees!
Lip Gloss ships with a tree rendering sub-package.
import "github.com/charmbracelet/lipgloss/tree"
Define a new tree.
t := tree.Root(".").
Child("A", "B", "C")
Print the tree.
fmt.Println(t)
// .
// ├── A
// ├── B
// └── C
Trees have the ability to nest.
t := tree.Root(".").
Child("macOS").
Child(
tree.New().
Root("Linux").
Child("NixOS").
Child("Arch Linux (btw)").
Child("Void Linux"),
).
Child(
tree.New().
Root("BSD").
Child("FreeBSD").
Child("OpenBSD"),
)
Print the tree.
fmt.Println(t)
Trees can be customized via their enumeration function as well as using
lipgloss.Style
s.
enumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("63")).MarginRight(1)
rootStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("35"))
itemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("212"))
t := tree.
Root("⁜ Makeup").
Child(
"Glossier",
"Fenty Beauty",
tree.New().Child(
"Gloss Bomb Universal Lip Luminizer",
"Hot Cheeks Velour Blushlighter",
),
"Nyx",
"Mac",
"Milk",
).
Enumerator(tree.RoundedEnumerator).
EnumeratorStyle(enumeratorStyle).
RootStyle(rootStyle).
ItemStyle(itemStyle)
Print the tree.
The predefined enumerators for trees are DefaultEnumerator
and RoundedEnumerator
.
If you need, you can also build trees incrementally:
t := tree.New()
for i := 0; i < repeat; i++ {
t.Child("Lip Gloss")
}
There’s more where that came from
Changelog
New Features
- 0618c73: feat(test): add test for
JoinHorizontal
(#346) (@aditipatelpro) - feb42a9: feat: move tree to root (#342) (@caarlos0)
Bug fixes
- 8a0e640: fix: remove unnecessary if (@aymanbagabas)
Documentation updates
- bc0de5c: docs(README): make tree example match output (@bashbunni)
- bb3e339: docs(README): match tree example alignment with list examples (@bashbunni)
- 185fde3: docs(README): update tree images (@bashbunni)
- ed7f56e: docs: fix
CompleteColor
example (#345) (@bashbunni) - cf0a7c6: docs: fix tree screenshot (@caarlos0)
Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or on Discord.
v0.12.1
Border width calcs: back to normal
This release fixes a regression with regard to border calculations introduced in Lip Gloss v0.11.1.
Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or on Discord.
v0.12.0
Lists, Check ✓
This release adds a new sub-package for rendering trees and lists.
import "github.com/charmbracelet/lipgloss/list"
Define a new list.
l := list.New("A", "B", "C")
Print the list.
fmt.Println(l)
// • A
// • B
// • C
Lists have the ability to nest.
l := list.New(
"A", list.New("Artichoke"),
"B", list.New("Baking Flour", "Bananas", "Barley", "Bean Sprouts"),
"C", list.New("Cashew Apple", "Cashews", "Coconut Milk", "Curry Paste", "Currywurst"),
"D", list.New("Dill", "Dragonfruit", "Dried Shrimp"),
"E", list.New("Eggs"),
"F", list.New("Fish Cake", "Furikake"),
"J", list.New("Jicama"),
"K", list.New("Kohlrabi"),
"L", list.New("Leeks", "Lentils", "Licorice Root"),
)
Print the list.
fmt.Println(l)
Lists can be customized via their enumeration function as well as using
lipgloss.Style
s.
enumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("99")).MarginRight(1)
itemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("212")).MarginRight(1)
l := list.New(
"Glossier",
"Claire’s Boutique",
"Nyx",
"Mac",
"Milk",
).
Enumerator(list.Roman).
EnumeratorStyle(enumeratorStyle).
ItemStyle(itemStyle)
Print the list.
In addition to the predefined enumerators (Arabic
, Alphabet
, Roman
, Bullet
, Tree
),
you may also define your own custom enumerator:
l := list.New("Duck", "Duck", "Duck", "Duck", "Goose", "Duck", "Duck")
func DuckDuckGooseEnumerator(l list.Items, i int) string {
if l.At(i).Value() == "Goose" {
return "Honk →"
}
return ""
}
l = l.Enumerator(DuckDuckGooseEnumerator)
Print the list:
If you need, you can also build lists incrementally:
l := list.New()
for i := 0; i < repeat; i++ {
l.Item("Lip Gloss")
}
Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or on Discord.