Skip to content

Releases: charmbracelet/lipgloss

v2.0.0-alpha.2

12 Nov 22:00
v2.0.0-alpha.2
Compare
Choose a tag to compare
v2.0.0-alpha.2 Pre-release
Pre-release

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

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.

The Charm logo

Charm็ƒญ็ˆฑๅผ€ๆบ โ€ข Charm loves open source โ€ข ู†ุญู†ู ู†ุญุจ ุงู„ู…ุตุงุฏุฑ ุงู„ู…ูุชูˆุญุฉ

v1.0.0

31 Oct 14:10
v1.0.0
Compare
Choose a tag to compare

At last: v1.0.0

This is an honorary release indicating that Lip Gloss is now stable. Thank you, open source community, for all your love, support, contributions, and great style.

Stay tuned for a v2 alpha!

v0.13.1

22 Oct 20:24
v0.13.1
284c0c5
Compare
Choose a tag to compare

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

Other Stuff

  • fix(render): strip carriage returns from strings by @bashbunni in #386

Bonus

New Contributors

Full Changelog: v0.13.0...v0.13.1


The Charm logo

Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or on Discord.

v0.13.0

20 Aug 23:07
bb3e339
Compare
Choose a tag to compare

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)

Tree Example (simple)

Trees can be customized via their enumeration function as well as using
lipgloss.Styles.

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.

Tree Example (makeup)

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

See all the tree examples.


Changelog

New Features

Bug fixes

Documentation updates


The Charm logo

Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or on Discord.

v0.12.1

12 Jul 16:17
v0.12.1
670898d
Compare
Choose a tag to compare

Border width calcs: back to normal

This release fixes a regression with regard to border calculations introduced in Lip Gloss v0.11.1.


The Charm logo

Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or on Discord.

v0.12.0

11 Jul 21:34
v0.12.0
6348d59
Compare
Choose a tag to compare

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)

image

Lists can be customized via their enumeration function as well as using
lipgloss.Styles.

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.

List example

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:

image

If you need, you can also build lists incrementally:

l := list.New()

for i := 0; i < repeat; i++ {
    l.Item("Lip Gloss")
}

The Charm logo

Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or on Discord.

v0.11.1

10 Jul 14:10
v0.11.1
e6edbac
Compare
Choose a tag to compare

A lilโ€™ truncation fix

This release is a small patch release to fix text truncation in table cells. For details see: #324.

Other stuff

Full Changelog: v0.11.0...v0.11.1


The Charm logo

Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or Discord.

v0.11.0

23 May 18:57
5cd858c
Compare
Choose a tag to compare

Immutable Styles and Raw Speed, Baby

So! The big news in this release is:

  • Style methods will now always return new styles
  • Style and ANSI operations under the hood are faster

There are also a handful of great lil' bug fixes. Read on for more.

Immutable Styles

Every Style method now returns a completely new style with its own underlying data structure no matter what. This means working with Styles is a lot easier. No more need for Copy()!

// Before
s := lipgloss.NewStyle().Bold(true)
newStyle := s.Copy()

// After
s := lipgloss.NewStyle().Bold(true)
newStyle := s // this is a true copy

Okay, but why are styles easier to work with now? Consider this:

// Before
baseStyle := lipgloss.NewStyle().Background(lipgloss.Color("59"))
styleAtRuntime := baseStyle.Copy().Width(m.Width)

// After
baseStyle := lipgloss.NewStyle().Padding(1, 2)
styleAtRuntime := baseStyle.Width(m.Width)

It might seem small, but eliminating the risk of mutations in persistent styles in an enormous usability improvement.

How to upgrade

There's nothing to do, however Style.Copy() is now deprecated and only returns itself, so you can just remove Style.Copy() calls. If you need to just copy a style without any changes to it you can simply b := a.

Faster ANSI

Sometimes watch companies brag about their "in-house" watch movement. Well, now we're bragging about our in-house-amazing x/ansi library by our own @aymanbagabas. It's a fine-tuned, low-level way to manage ANSI sequencing and, because we're pretty nerdy, weโ€™re super excited about it.


What's Changed

New!

Changed

Fixed

New Contributors

Full Changelog: v0.10.0...v0.11.0


The Charm logo

Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or Discord.

v0.10.0

05 Mar 15:57
v0.10.0
439c06f
Compare
Choose a tag to compare

String Transforms ๐Ÿ’„

Lip Gloss v0.10.0 features a brand new Transform function for Styles to alter strings at render time. As well as some bug fixes, like ANSI-aware table cell truncation. ๐Ÿงน

Simply define a Transform function as func (string) string and apply it to any style:

// Example:
s := NewStyle().Transform(strings.ToUpper)
fmt.Println(s.Render("raow!") // "RAOW!"

Or, if you prefer:

// Example:
reverse := func(s string) string {
    n := 0
    rune := make([]rune, len(s))
    for _, r := range s {
        rune[n] = r
	n++
    }
    rune = rune[0:n]
    for i := 0; i < n/2; i++ {
        rune[i], rune[n-1-i] = rune[n-1-i], rune[i]
    }
    return string(rune)
}

s := NewStyle().Transform(reverse)
fmt.Println(s.Render("The quick brown ็‹ jumped over the lazy ็Šฌ")
// "็Šฌ yzal eht revo depmuj ็‹ nworb kciuq ehT",

What's Changed?

New Contributors

Full Changelog: v0.9.1...v0.10.0


The Charm logo

Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or Discord.

v0.9.1

12 Oct 05:14
v0.9.1
f093bc1
Compare
Choose a tag to compare

This bugfix release changes the Table Headers API to accept []string for consistency with Row / Rows and downgrades Lip Gloss to Go version v1.17.

What's Changed

Full Changelog: v0.9.0...v0.9.1