charmbracelet/bubbletea

Automatically open terminal when Stdout is not a terminal

wolfmagnate opened this issue · 4 comments

Is your feature request related to a problem? Please describe.

description

Currently, to redirect output results to another file or pipe them to another command is not supported. Therefore, if you write bubbles tea's standard output to a file with the current implementation, the file will have ANSI control characters written directly to it.
this problem is reported in #823 and #792.

why is this a problem?

  • TUI applications will be able to take full advantage of the CLI application, such as the convenience of pipes and redirection.
  • It is strange that the input supports pipes and redirects here in tea.go, but the output does not.

Describe the solution you'd like

If the standard output of the bubble tea program is connected to something other than a terminal, the program will determine this at startup and change the destination of bubble tea's TUI to the terminal and the destination of stdout to the output specified at startup.

Add the following process

  • check whether stdout is a terminal at the start of the program.
  • if it is a terminal, proceed with the process as it is now. (just output to stdout)
  • if it is not a terminal, open a new "dev/tty" and output to it.

Describe alternatives you've considered

  • You can use a golang program to write an output to a file, but it is inconvenient.
  • open dev/tty and use WithOutput option
func main() {
	tty, err := os.OpenFile("/dev/tty", os.O_WRONLY, 0)
	if err != nil {
		panic(err)
	}
	defer tty.Close()

	p := tea.NewProgram(initialModel(), tea.WithOutput(tty))
	fmt.Println("result of command")
	if _, err := p.Run(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

This works on linux, but this is not crossplatform solution. Also, this is inconsistent interface because you don't have to write any code for input, but have to write for output.

Additional context

I am not sure where i should write the statements to solve this problem.
Currently, Program.output is initialized here. I should add the new process here in terms of extending the existing process.
Here is a program from tea.go that creates a new TTY for the input source. The input and output processing are similar in purpose and content, so I should add the processing here.

I would like to write code to solve this problem and create a pull request, but before I do so I would like to hear some design advice on how it should be implemented.

Hi @wolfmagnate,

I'm trying to use the workaround you mentioned and it seems that color formatting doesn't work when I pass /dev/tty as output to NewProgram().

I've reproduced the issue using two of examples in this repo.
I've ran the commands as echo $(go run main.go).

For examples/list-fancy/main.go no colors are showing up:
CleanShot 2024-05-04 at 18 17 14@2x

For examples/glamour/main.go colors from glamour markdown auto-styling do work but normal lipstick styles still do not work - i.e. the border should be purple (see another screenshot below):
CleanShot 2024-05-04 at 18 16 00@2x

Outputs when running the commands normally as go run main.go for comparison:
CleanShot 2024-05-04 at 18 16 45@2x
CleanShot 2024-05-04 at 18 16 24@2x

Gist with full code I used for both examples:

Do you have any pointers or advice on how to make all styles work? 🙏
Sorry for hijacking this issue a bit - I'm happy to create a new one if that work better.

Many thanks!
Simon

It took me way too long to find this workaround. The first improvement may be to make this info a bit more accessible. Because of Gum, I expected this to be an out-of-the-box behaviour.

Following the gum train of thought, I tried to model my program from it, but it's hard to follow since the call stack delves into Kong cli, which relies on reflection and other unsafe code. Learning the internals of a framework I won't even use seems like a step too far into procrastination so I am happy to find a workaround.

If maybe any of you know how Gum is doing it, please let me know in this discussion thread.