Skip to content

Commit

Permalink
feat: support terminal color profiles
Browse files Browse the repository at this point in the history
This adds support for detecting the terminal's color profile. It adds a
new `ColorProfileMsg` message that get sent when the program starts. You
can force a specific color profile using the `WithColorProfile` option.

When a program requests the `RGB` or `Tc` terminfo capabilities, Bubble
Tea will read the response, if there is one, and upgrade the cached
color profile and send the new profile to the program again.

Supersedes: #1142
Supersedes: #1143
  • Loading branch information
aymanbagabas committed Sep 16, 2024
1 parent a26ecc5 commit 8b51296
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 1 deletion.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/charmbracelet/bubbletea

go 1.18
go 1.23.1

require (
github.com/charmbracelet/lipgloss v0.13.0
Expand All @@ -17,6 +17,7 @@ require (

require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/colorprofile v0.1.0 h1:klE4lnH3GpQ0iI05UqEhExt5JHZUJFEBR5VxGzbbGG4=
github.com/charmbracelet/colorprofile v0.1.0/go.mod h1:Bx3b9S36dLIjuGZOO9z1WytjG4sALQQNtu97AmKYW24=
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
github.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyfE6USY=
Expand Down
13 changes: 13 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"sync/atomic"

"github.com/charmbracelet/colorprofile"
"github.com/charmbracelet/x/ansi"
)

Expand Down Expand Up @@ -336,3 +337,15 @@ func WithoutGraphemeClustering() ProgramOption {
p.startupOptions |= withoutGraphemeClustering
}
}

// WithColorProfile sets the color profile that the program will use. This is
// useful when you want to force a specific color profile. By default, Bubble
// Tea will try to detect the terminal's color profile from environment
// variables and terminfo capabilities. Use [tea.WithEnvironment] to set custom
// environment variables.
func WithColorProfile(profile colorprofile.Profile) ProgramOption {
return func(p *Program) {
p.startupOptions |= withColorProfile
p.profile = profile
}
}
15 changes: 15 additions & 0 deletions profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package tea

import "github.com/charmbracelet/colorprofile"

// ColorProfileMsg is a message that describes the terminal's color profile.
// This message is send to the program's update function when the program is
// started.
//
// To upgrade the terminal color profile, use the `tea.RequestCapability`
// command to request the `RGB` and `Tc` terminfo capabilities. Bubble Tea will
// then cache the terminal's color profile and send a `ColorProfileMsg` to the
// program's update function.
type ColorProfileMsg struct {
colorprofile.Profile
}
19 changes: 19 additions & 0 deletions tea.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"syscall"
"time"

"github.com/charmbracelet/colorprofile"
"github.com/charmbracelet/x/ansi"
"github.com/charmbracelet/x/term"
"github.com/muesli/ansi/compressor"
Expand Down Expand Up @@ -105,6 +106,7 @@ const (
withModifyOtherKeys
withWindowsInputMode
withoutGraphemeClustering
withColorProfile
)

// channelHandlers manages the series of channels returned by various processes.
Expand Down Expand Up @@ -166,6 +168,8 @@ type Program struct {
finished chan struct{}
shutdownOnce sync.Once

profile colorprofile.Profile // the terminal color profile

// where to send output, this will usually be os.Stdout.
output *safeWriter

Expand Down Expand Up @@ -401,6 +405,15 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
p.suspend()
}

case CapabilityMsg:
switch msg {
case "RGB", "Tc":
if p.profile != colorprofile.TrueColor {
p.profile = colorprofile.TrueColor
go p.Send(ColorProfileMsg{p.profile})
}
}

case modeReportMsg:
switch msg.Mode {
case graphemeClustering:
Expand Down Expand Up @@ -635,6 +648,12 @@ func (p *Program) Run() (Model, error) {
return p.initialModel, err
}

// Get the color profile and send it to the program.
if !p.startupOptions.has(withColorProfile) {
p.profile = colorprofile.Detect(p.output.Writer(), p.environ)
}
go p.Send(ColorProfileMsg{p.profile})

// If no renderer is set use the standard one.
var output io.Writer
output = p.output
Expand Down
21 changes: 21 additions & 0 deletions termcap.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@ type requestCapabilityMsg string

// RequestCapability is a command that requests the terminal to send its
// Termcap/Terminfo response for the given capability.
//
// Bubble Tea recognizes the following capabilities and will use them to
// upgrade the program's color profile:
// - "RGB" Xterm direct color
// - "Tc" True color support
//
// Note: that some terminal's like Apple's Terminal.app do not support this and
// will send the wrong response to the terminal breaking the program's output.
//
// When the Bubble Tea advertises a non-TrueColor profile, you can use this
// command to query the terminal for its color capabilities. Example:
//
// switch msg := msg.(type) {
// case tea.ColorProfileMsg:
// if msg.Profile != colorprofile.TrueColor {
// return m, tea.Batch(
// tea.RequestCapability("RGB"),
// tea.RequestCapability("Tc"),
// )
// }
// }
func RequestCapability(s string) Cmd {
return func() Msg {
return requestCapabilityMsg(s)
Expand Down

0 comments on commit 8b51296

Please sign in to comment.