Skip to content

Instantly share code, notes, and snippets.

@wesen
Last active September 28, 2022 19:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wesen/045b8ef7227ddd8a6951cd308e5d4634 to your computer and use it in GitHub Desktop.
Save wesen/045b8ef7227ddd8a6951cd308e5d4634 to your computer and use it in GitHub Desktop.

BubbleTea/Termenv stderr output

Problem statement

While you can easily print a bubble tea UI out to stderr, color profile information is looked up using os.Stdout in a variety of places (the most important being termenv.ColorProfile, but also in the various lipgloss.Style and termenv.Style instantiation.

Having colored output when stdout is not a TTY, but stderr is would be very useful. It would allow bubbletea applications to be used in the context of shell scripting (for example, like fzf).

Muesli has a new branch for termenv at https://github.com/muesli/termenv/tree/termenv-next . This supports more options for specifying termenv style based on file descriptor. However, lipgloss and bubbles need to be updated to take advantage of this functionality, so that proper color profiles are used for stderr.

Proposed solutions

There are different ways of tackling this issue.

Proposal 1: just use the FORCE_COLOR environment variable

The simplest solution that would work with the current releases is to just set an environment variable before starting the UI, forcing colors on stderr. This would also force colors on stdout, but usually bubbles/lipgloss is rendered to either stdout or stderr, and normal style-less formatting is used for the other output.

For example, this works against bubbletea master:

diff --git a/examples/list-simple/main.go b/examples/list-simple/main.go
index 97628db..443c443 100644
--- a/examples/list-simple/main.go
+++ b/examples/list-simple/main.go
@@ -111,6 +111,7 @@ func main() {
 
 	const defaultWidth = 20
 
+	os.Setenv("CLICOLOR_FORCE", "1")
 	l := list.New(items, itemDelegate{}, defaultWidth, listHeight)
 	l.Title = "What do you want for dinner?"
 	l.SetShowStatusBar(false)
@@ -121,7 +122,7 @@ func main() {
 
 	m := model{list: l}
 
-	if err := tea.NewProgram(m).Start(); err != nil {
+	if err := tea.NewProgram(m, tea.WithOutput(os.Stderr)).Start(); err != nil {
 		fmt.Println("Error running program:", err)
 		os.Exit(1)
 	}

Proposal 2: bubble the color profile down

The second “clean” solution is to bubble color profile information all the way down to every method instantiating an output style. This means forbidding the creation of lipgloss/termenv styles without explicitly providing a color profile, to avoid misuse.

This adds a fair amount of API overhead to every model / style in lipgloss, but means that every API user will support stderr and stdout properly.

This is what I tried to achieve with :

At the end, I introduced the global output file variable described in Proposal 3, because I would have had to bubble down color profile information to all the color styled components.

Proposal 3: configure “global” output file

The third option would be to allow setting a global “styled output” file descriptor (in termenv), so that each future default instantiation of a style uses said descriptor. This means no API change to bubbles and bubble tea, but at least makes behavior configurable without setting a global environment variable as workaround.

Opinion

I personally prefer proposal 3. It’s not super clean, but for all practical instances it should work perfectly fine and be backwards compatible.

@muesli
Copy link

muesli commented Sep 28, 2022

Quick heads up: with termenv-next being merged into termenv, we're working towards proposal 3.

@wesen
Copy link
Author

wesen commented Sep 28, 2022

👍 exciting!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment