Giter Site home page Giter Site logo

bubbletea's People

Contributors

adjective-object avatar ajeetdsouza avatar aymanbagabas avatar bashbunni avatar caarlos0 avatar dependabot[bot] avatar dhth avatar geodimm avatar inkel avatar irevenko avatar jon4hz avatar kiyonlin avatar knz avatar lusingander avatar maaslalani avatar meowgorithm avatar michelefiladelfia avatar mrusme avatar muesli avatar nderjung avatar pheon-dev avatar rubysolo avatar sharunkumar avatar siddhantac avatar stefanlogue avatar superpaintman avatar taigrr avatar tearingitup786 avatar timmattison avatar tjovicic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bubbletea's Issues

Need examples on lipgloss integration

lipgloss looks great, but its example seems to indicate dynamic, updateable, switchable widget-based content, which is not the case at all. The example is in fact completely static, and none of the bubbletea examples have any kind of layout or focus switching, so it's unclear if a dynamic, switchable version of the lipgloss example is even possible, or how the two projects might integrate together.

Allow both native text selection and mouse wheel scrolling

I tried using either WithMouseCellMotion or WithMouseAllMotion and they both seem to allow for scrolling a viewport with the mouse wheel but disable text selection/highlighting completely. Tried passing different flags to muesli's termenv to facilitate this, to no avail.

Would it be possible to allow both scrolling with mouse and text selection?

Tea with Alt screen gets messed up after openning sub command.

Hi,

I'm starting tea with:

p := tea.NewProgram(m, tea.WithOutput(os.Stderr))

Then in Update() call sub process:

				cmd := exec.Command("vim")
				cmd.Stdin = os.Stdin
				cmd.Stdout = os.Stderr // Render to stderr.
				m.editMode = true
				_ = cmd.Run()
				m.editMode = false
				return m, tea.HideCursor

After the exit of vim tea program all messed up and no alt screen in place.

Not sure if it is supported at all. Please, help me to debug. Trying to add AltScreen to https://github.com/antonmedv/llama.

Thanks! 🙏

Navigating between Models

In Elm you can use an Application in order to switch between models/views (application).

For more complicated applications how would one do that in Bubbletea? In the views example the views effectively share a Model, but for more complex applications that's not feasible.

examples/spinners: Spinner not spinning

The spinners demo spinner stops spinning when changing the spinner. Should Update return spinner.Tick to get the new spinner spinning when changing the spinner?

diff --git a/examples/spinners/main.go b/examples/spinners/main.go
index e3488bd..165b453 100644
--- a/examples/spinners/main.go
+++ b/examples/spinners/main.go
@@ -57,14 +57,14 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
                                m.index = len(spinners) - 1
                        }
                        m.resetSpinner()
-                       return m, nil
+                       return m, spinner.Tick
                case "l", "right":
                        m.index++
                        if m.index >= len(spinners) {
                                m.index = 0
                        }
                        m.resetSpinner()
-                       return m, nil
+                       return m, spinner.Tick
                case "ctrl+c", "q":
                        return m, tea.Quit
                default:

Awesome project

Thank you for the awesome project, really look forward to using it more. You are free to close this issue, I just wanted to give my appreciation. Also, was curious if you had considered making project stickers?

Read input events instead of raw bytes in Windows (mouse support/window change events/canceling inputloop)

Disclaimer: This issue is meant to supersede #103 and #24. The proposed change would be based on #120. This issue is based on a comment I made in #103.

Windows supports reading INPUT_RECORDs with ReadConsoleInput. However, this means that the Windows input event parsing logic (which would then be based on INPUT_RECORD structs) would have to be decoupled from the Unix logic.

Changing the input handling to use INPUT_RECORDs would allow bubbletea to receive MOUSE_EVENT_RECORDS (closing #103). It also enables the usage of PeekConsoleInput to augments the WaitForMultipleObjects mechanism introduced in #120 in order to solve #24 for the Windows Terminal where #120 does not work reliably.

INPUT_RECORD can also be a WINDOW_BUFFER_SIZE_EVENT which sounds like a good replacement for SIGWINCH on Windows.

There is also already a PR for the Go standard library to add ReadConsoleInput but it has become a bit stale over the last few months. However, we probably need PeekConsoleInput to augments the WaitForMultipleObjects anyway so the implementation has to use windows.NewLazySystemDLL("kernel32.dll").NewProc("...").Call(...) anyway.

Allow unmanaged console output

Using bubbletea, I would like to have a progress bar similar to apt's:
apt-progress-bar

Progress bar is updated and the above text does not get cleared. I could not achieve this in bubbletea. The closest attempt I got resulted in an unexpected error (see this cast. the code is a modification of examples/spinners/main.go).

I have also tried using bubbletea.ScrollDown() but it instantly clears the screen. Any ideas ?

Nested components

Hi there,

first of all, sorry because I do not know if this is the place where people usually ask for information. I am currently developing a complex cli and I just want to use this framework to build it. My main idea was to nest components inside other complex components as javascript framework does such as vue, react, etc...

It is possible to to something like this?

TextInput component definition

type TextInput struct {
	textinput textinput.Model
	err       error
}

func (model TextInput) Init() tea.Cmd {
	return textinput.Blink
}

func (model TextInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	var cmd tea.Cmd
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.Type {
		case tea.KeyEnter, tea.KeyCtrlC, tea.KeyEsc:
			return model, tea.Quit
		}

	case error:
		model.err = msg
		return model, nil
	}

	model.textinput, cmd = model.textinput.Update(msg)
	return model, cmd
}

func (model TextInput) View() string {
	return fmt.Sprintf(
		"?\n\n%s\n\n%s",
		model.textinput.View(),
		"(esc to quit)",
	) + "\n"
}

func NewTextInput(placeholder string) TextInput {
	textinput := textinput.NewModel()
	textinput.Placeholder = placeholder
	textinput.CharLimit = TEXT_INPUT_LIMIT
	textinput.Width = TEXT_INPUT_WIDTH
	textinput.Focus()

	return TextInput{textinput, nil}
}

View definition

type husky struct {
	name TextInput
	stun TextInput
	peer TextInput

	current tea.Model
	cursor  int
}

func (view husky) Init() tea.Cmd {
	return view.current.Init()
}

func (view husky) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

	if view.name.textinput.Value() == "" {
		return view.name.Update(msg)
	}

	if view.stun.textinput.Value() == "" {
		return view.name.Update(msg)
	}

	if view.peer.textinput.Value() == "" {
		return view.name.Update(msg)
	}

	return nil, nil
}

func (view husky) View() string {
	return view.current.View()
}

func Husky() husky {
	name := NewTextInput("name")
	stun := NewTextInput("stun")
	peer := NewTextInput("peer")
	return husky{name, stun, peer, name, 0}
}

Thanks in advance! 😄

Injecting messages from outside the program loop

I'm not sure I haven't overlooked something, but I miss a way of injecting messages into a running tea.Program from outside the program's execution loop. Currently, programs only process messages returned from either Init() or Update() or implicitly created for keyboard events and system signals.

In scenarios where a TUI is used to show progress (spinner, progress bar), some concurrent progress will need to indicate progress and/or having finished to the program. If there was a way of injecting messages, this could be done cleanly; as things are now, one needs to resort to atomic fields or channels in the model — or have I misunderstood or overlooked something?

Questions about viewport

  • Maybe im not going about this the correct way but im working on a file manager just for fun here https://github.com/knipferrc/fm. Trying to figure out how to implement the viewport and its somewhat working but I have a couple issues.
  1. ) Whenever I check if m.ready inside the view, it just sits in a loading state infinitely. Maybe im not initializing something correctly?
case tea.WindowSizeMsg:
		if !m.ready {
			m.screenwidth = msg.Width
			m.screenheight = msg.Height
			m.viewport = viewport.Model{
				Width:  msg.Width,
				Height: msg.Height - 1,
			}
			m.viewport.YPosition = 0
			m.ready = true
		} else {
			m.screenwidth = msg.Width
			m.screenheight = msg.Height
			m.viewport.Width = msg.Width
			m.viewport.Height = msg.Height - 1
		}
  1. ) Whenever I call viewport.LineUp or LineDown it seems to take no effect on the viewport? Attempting to get it so that you can scroll through your directly tree when the height goes outside the terminal

Any help is much appreciated

Multiple choices

Hi,

I'm looking in the doc but don't fully understand how to create multiple model and view in the same file.
like:
create your own drink:

~ choose two flavors:
[] orange
[] strawberrie
[] watermelon
[] apple

~ select adds:
[] candies
[] mixed colors
[] strange glass shape

Question about Design of `Cmd` in `Update(Msg) (Model, Cmd)`

In the original Elm Architecture, Update() just sends updated model to View(). Just like the figure below:

drawing

Whereas in Bubble Tea, Update() returns an newly updated model and also a command to execute asynchronously, which returns another message and henceforth invokes Update() again.

So can you please explain why Bubble Tea should be designed so, and what's the impact if the Update() is just Update(Msg) Model without Cmd in return (command would be executed in Update() directly sync-or-asynchronously then)?

Why not use interfaces?

Just found this lib and something that seems a bit off to me is the API with regards to actually implementing your "program".

func NewProgram(init Init, update Update, view View) *Program

The setup function firstly takes a number of custom types, now there's nothing wrong with these however when using an editor with intellisense like vscode, the actual meaning of those types is obfuscated and means you have to open the docs or Ctrl+click a few times to know what you're trying to satisfy.

Second, the update and view function also takes our model as a parameter, only it's not really our model now, it's a blank interface so every time I want my data I need to cast it back into the real model, check that it wasn't passed something else and then return a copy of it? I would like to know the reasoning behind this as it seems like a bit of a runabout way of doing things.

As an alternative I would propose that NewProgram takes an interface that satisfies the library:

type Model interface {
	Init() tea.Cmd
	Update(tea.Msg) tea.Cmd
	View() string
}

func NewProgram(from Model) *Program {...}

A program could then be initialised like so:

type myModel struct {
	content  string
	ready    bool
	viewport viewport.Model
}

func (m *myModel) Init() tea.Cmd {...} // Could possibly omit this in favour of a smaller interface?
func (m *myModel) Update(tea.Msg) tea.Cmd {...}
func (m *myModel) View() string {...}

myInstance := myModel{
	content: "hello",
}

p := tea.NewProgram(myInstance)
if err := p.Start(); err != nil {
	fmt.Println("could not run program:", err)
	os.Exit(1)
}

Again I have not looked to deeply at the library sources to see anything jump out as to why this would not be possible, I just think this would allow a slightly cleaner API for users and allow for more fancy use cases such as type composition defined views.

Mouse clickable buttons

Do you have an examples of making certain text a clickable button with keyboard or mouse? For example the dialogBox in the lipgloss example? How would you make those buttons clickable?

Thank you!

Display issue when line content is greater than terminal width

Hi!
Thanks for this awesome library but I experience an issue with my tui when the displayed content is longer than the terminal width.
Here a gif:
issue_bubbletea

Maybe it's from my side, here my View() code for the main menu:

s = fmt.Sprintf("\n🦄 Welcome to %s, the (soon) most powerful and versatile %s bot!\n\n", keyword("IGopher"), keyword("Instagram"))

for i, choice := range m.homeScreen.choices {
	cursor := " "
	if m.homeScreen.cursor == i {
		cursor = cursorColor(">")
	}
	s += fmt.Sprintf("%s %s\n", cursor, choice)
}

s += subtle("\nup/down: select") + dot + subtle("enter: choose") + dot + subtle("ctrl+c: quit")
break

Btw all of my tui code is available here : tui.go
If you have any ideas

Node-based rendering

Hello, I am trying to make a complex TUI application but unfortunately the single view model that bubbletea offers is severely limiting and only allows for the creation of linear CLI apps. Manipulating a string to add a popup window to the view has proven to be very difficult and inefficient. My proposed solution is to add child views.

Optimize refresh on remote server or slow network

On the device with slow refresh rate, every "View()" will cause a blink since it re-renders the whole area or fullscreen. Do you have any plans to only re-render the the part it changes in order to avoid those blinks? (just like Kindle devices, not every page-up/page-down will cause a whole refresh)

Alternate screen doesn't clear the terminal when running in GNU Screen

Steps to reproduce

  1. Run GNU Screen (I'm running it in an OSX Terminal)
  2. In the bubbletea repo, go run examples/fullscreen/main.go

Expected result: Screen is cleared and you only see the countdown message

Actual result: Your terminal prompt, history, and whatever else is still visible alongside the output of the program.

However, once the program exits, the contents of the terminal are cleared.

This bug does not happen in tmux or in a bare Terminal.

Screenshot

(I ran ls just before running the program, to illustrate how it writes over top of the existing terminal contents)

Screen Shot 2021-07-13 at 9 38 44 PM

Spawning editors

Hi,

Thanks for the awesome project!

Question: how to properly start subcommand with reuse of Stdout/Stdin?

I'm doing it like so:
https://github.com/antonmedv/llama/blob/58c042eb06a5a661a1388beb4ac09cedf2764ad0/main.go#L183-L190

func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	if m.editMode {
		return m, nil
	}
				cmd := exec.Command(lookup([]string{"LLAMA_EDITOR", "EDITOR"}, "less"), filepath.Join(m.path, m.cursorFileName()))
				cmd.Stdin = os.Stdin
				cmd.Stdout = os.Stdout
				// Note: no Stderr as redirect `llama 2> /tmp/path` can be used.
				m.editMode = true
				_ = cmd.Run()
				m.editMode = false
				return m, tea.HideCursor

But what to do if a redraw is needed after cmd exits? And is there a better way to halt the program until cmd is exited?

Thanks.

Originally posted by @antonmedv in #170

Development hot reload

During development, I'd like to be able to make changes to the code and then run the program in order to visually see the results. Of course it gets repetitive:

  1. Make changes, save file(s)
  2. Run program
  3. Repeat

I'd like then a tool that rolls up these steps into just one step: make changes, save file(s); tool automatically restarts program. Ideally the two would run side-by-side on my monitor.

For example, with web development, there is livereload.js which embeds some javascript in the website you're developing, and when you make changes the website page automatically reloads.

Whereas for my bubbletea-based program I've tried using:

But none of them support running the program with a TTY. The following issue describes the problem (although it concludes it's not an insurmountable problem):

eradman/entr#81

So has anyone found a way to support this particular development feedback loop for programs developed with bubbletea? Or perhaps I'm missing an alternative approach that works just as well?

Note: I appreciate this is no substitute for unit tests.

Bit literals in mouse.go break build on pre-1.12

02a0509 broke builds on pre-1.12 environments.

/gocode/src/github.com/charmbracelet/bubbletea/mouse.go:70:16: syntax error: unexpected b0000_0100, expecting semicolon or newline or )

Any chance you could control this using build tags?

Swapping out current model dynamically

👋 Not sure if I'm just approaching this incorrectly. I'm building a UI with an index that has a list of items, and selecting the item drills down into the details for the item.

I created a top level model as recommended in #13, but because all my child models are dynamic, I was generating them and sending up to the parent via a channel. Here's a simplified, contrived example:

package main

import (
	"fmt"

	tea "github.com/charmbracelet/bubbletea"
)

type item struct {
	details string
}

func (i item) String() string {
	return i.details
}

/* Parent model *****************************************************/
type model struct {
	current tea.Model
	ch      chan tea.Model
}

func (m *model) Watch() {
	for {
		select {
		case mm := <-m.ch:
			m.current = mm
		}
	}
}

func (m model) Init() tea.Cmd {
	return tea.EnterAltScreen
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	return m.current.Update(msg)
}

func (m model) View() string {
	return m.current.View()
}

/* First child model ************************************************/
type listModel struct {
	cursor int
	msgs   []item
	ch     chan tea.Model
}

func (l listModel) Init() tea.Cmd {
	return tea.EnterAltScreen
}

func (l listModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "q":
			return l, tea.Quit
		case "k":
			if l.cursor > 0 {
				l.cursor--
			}
		case "j":
			if l.cursor < len(l.msgs)-1 {
				l.cursor++
			}
		case "enter", " ":
			l.ch <- itemModel{item: l.msgs[l.cursor], ch: l.ch}
		}
	}
	return l, nil
}

func (l listModel) View() string {
	str := "List view\n"
	for i, msg := range l.msgs {
		cursor := " "
		if l.cursor == i {
			cursor = ">"
		}
		str += fmt.Sprintf("%s %s\n", cursor, msg)
	}
	str += "\nPress q to quit\n"
	return str
}

func newListModel(ch chan tea.Model) listModel {
	return listModel{
		ch: ch,
		msgs: []item{
			{"one"},
			{"two"},
			{"three"},
		},
	}
}

/* Drilldown child model ********************************************/

type itemModel struct {
	item item
	ch   chan tea.Model
}

func (i itemModel) Init() tea.Cmd {
	return tea.EnterAltScreen
}

func (i itemModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "q":
			return i, tea.Quit
		case "b":
			i.ch <- newListModel(i.ch)
		}
	}
	return i, nil
}

func (i itemModel) View() string {
	return fmt.Sprintf("Item view\n %s\n q to quit, b to go back\n", i.item)
}

/* main *************************************************************/

func main() {
	ch := make(chan tea.Model, 1)
	m := model{
		current: newListModel(ch),
		ch:      ch,
	}

	go m.Watch()

	p := tea.NewProgram(m)
	p.Start()
}

The models never seem to update. I've tried manually kicking off Init() and Update() after setting in Watch(), but no luck.

Is there some lifecycle event I'm missing? Am I just approaching this in a dumb way?

Unicode support

Hello i tried to input unicode but it is not recognized by bubbletea, Thank you.
go version go1.17 windows/amd64

image

image
image

type tuiModel struct {
	p string
}
func (m *tuiModel) Init() tui.Cmd {
	return func() tui.Msg {
		return nil
	}
}
func (m *tuiModel) Update(msg tui.Msg) (tui.Model, tui.Cmd) {
	switch msg := msg.(type) {
	case tui.KeyMsg:
		return m.handleKeyboardInput(msg)
	}
	return m, nil
}
func (m *tuiModel) View() string {
	return m.p
}
func (m *tuiModel) handleKeyboardInput(msg tui.KeyMsg) (tui.Model, tui.Cmd) {
	switch msg.String() {
	case "ctrl+c":
		return m, tui.Quit
	}
	m.p = m.p + fmt.Sprintf("\n%s (%+#v)", msg, msg)
	return m, nil
}

Where to look for components?

Sorry for such a question but I'm kind of lost in the docs as to where I'm supposed to look for docs on components like the list item, buttons, labels etc?

I want to make something to make bulk transferring files over MTP (to Android phones) easier and I'd like to use your library because of its aesthetics but have no idea where to start.

Thanks.

Mouse scroll doesn't work in foot terminal

In the foot terminal mouse scroll events aren't triggered at all (though other mouse events trigger okay). Mouse scrolling works in other terminals such as alacritty so I don't think the issue is with my code, and other programs such as htop have working mouse scrolling support in foot making me think the issue is on bubbletea's side

PowerShell windows resize

Hi,

I'm playing with the new Windows Terminal and PowerShell. And looks like something is wrong with resizing there.

Here is what I'm getting after resizing the terminal in llama.
Снимок экрана (3)

Non-Interactive in Windows Shells

Issue

Running the example in the README in Windows is not working as expected (see screenshot).

Screenshot 2020-10-12 185102

Expected Behavior

  • Build and run the example in the README.
  • Update the cursor position in the view via the up, down, j, k, keys, etc.
  • Exit cleanly by pressing Ctrl+c or q

Actual Behavior

  • Cursor position does not update on key press, using any of up, down, j, k, enter, space
  • Pressing q does not quit the application.
  • Pressing Ctrl+c exits with an error code.

Environment

Windows 10 19041.508
Go 1.15.2
Terminal applications tested:

  • cmd
  • Powershell
  • Windows terminal with Powershell and cmd
  • Cmder with cmd, Powershell, cygwin

Other Notes

I was able to get the same example running as expected using WSL Ubuntu 20.04 with Windows Terminal.

The project looks exciting and I'm interested in diving deeper. Please let me know if there's any additional info I can provide.

Input is still read after program quits

If multiple tea.Programs are run in succession, each of them appears to set up a system signal handler (for catching SIGINT etc.), but does not clean it up after the program has quit without being interrupted this way. If you run n programs in succession that each want to catch "ctrl+c", for the n-th such program, you'll need to press Ctrl-C (n-1-m) times, where m is the number of previous programs that were interrupted. Of course, the interrupt-catching functionality should work independently of what has happened before.

Altscreen enter and exit messages

Right now the only way to enter and exit the altscreen (full window mode) is via Program.EnterAltScreen() and Program.ExitAltScreen(). This means that, without employing hacks, one can only enter and exit when starting and quitting programs. It probably makes sense to have library-level commands, similar to tea.Quit, for entering and exiting the altscreen to give Bubble Tea programs the opportunity to jump in and out of the altscreen in-program.

Filter search not working in list-simple example

After running the example list-simple/main.go I noticed that the search function is not getting triggered.

I see the FilterValue() is also defined just like in other list examples.

Not sure if this is the default behavior or some issue.

Edit

l.SetFilteringEnabled(true) setting this to true enables the search but now the issue is it shows nothing in the search like so:

issue-bubble

Arrow keys no longer work on Windows after upgrading to version 0.16

I can reproduce this on all of the examples in this repo that require the arrow keys or mouse. After poking around a bit, I was able to fix this by changing this line: newMode &^= windows.ENABLE_VIRTUAL_TERMINAL_INPUT here: https://github.com/charmbracelet/bubbletea/blob/master/cancelreader_windows.go#L209 to newMode |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT. I'm a complete novice when it comes to terminal inputs so I don't know what the consequence of this change is, but figured I'd throw it out there in case this is a valid fix.

Question: How print output from TODO

I try it example and it's great library, but I can't figure out how you think we can work with output.

Example:

I can just add print for selected output:

for i := range m.selected {
  fmt.Printf("\n%s", m.choices[i])
}

I can put into update function to quit and get ugly output:

What should we buy at the market?

> [x] Buy carrots
  [ ] Buy celery
  [x] Buy kohlrabi

Press q to quit.

Buy kohlrabi
            Buy carrots%

Do I need extra view for that? Or is some simple way how get pass output via program interface to main function to continue for example with other program? I see example with Msg but look overcomplicated for just work with output from CLI.

WithOutput should receive an io.Writer

Currently, WithOutput receives an *os.File which makes it harder to write tests.

I do not see any reason for this not to be changed, as the output field in Program is already an io.Writer.

PS: I'm loving this library! ❤️

Automatically set correct color profile

I want to create something like fzf. The fzf renders to stderr, while returning content to stdour.
So next is possible (fzf will open interactive TUI):

fzf | cat

But termenv do this by default:

func ColorProfile() Profile {
	if !isatty.IsTerminal(os.Stdout.Fd()) {
		return Ascii
	}

	return colorProfile()
}

I know what I can manually call lipgloss.SetColorProfile, but it will be cool if creating stderr apps will be easy, and the next code just work:

p := tea.NewProgram(m, tea.WithOutput(os.Stderr))

But now I need to copy-paste colorProfile() logic to be able to detect the correct profile.

Thanks!

Race condition - flush vs. write

Hi, it seems there is a race condition in the core library?

WARNING: DATA RACE
Read at 0x00c0000ee0a0 by goroutine 11:
  bytes.(*Buffer).Len()
      /home/chlunde/opt/go/src/bytes/buffer.go:73 +0x64
  github.com/charmbracelet/bubbletea.(*renderer).flush()
      /home/chlunde/src/bubbletea/renderer.go:91 +0x45
  github.com/charmbracelet/bubbletea.(*renderer).listen()
      /home/chlunde/src/bubbletea/renderer.go:76 +0x185

Previous write at 0x00c0000ee0a0 by main goroutine:
  bytes.(*Buffer).Reset()
      /home/chlunde/opt/go/src/bytes/buffer.go:98 +0xf5
  github.com/charmbracelet/bubbletea.(*renderer).write()
      /home/chlunde/src/bubbletea/renderer.go:195 +0x13c
  github.com/charmbracelet/bubbletea.(*Program).Start()
      /home/chlunde/src/bubbletea/tea.go:330 +0xbbb
  main.main()
      /home/chlunde/src/bubbletea/examples/spinner/main.go:27 +0x229

The fix seems to be to acquire the mutex a bit earlier in flush:

diff --git a/renderer.go b/renderer.go
index e919801..4a3263b 100644
--- a/renderer.go
+++ b/renderer.go
@@ -88,6 +88,9 @@ func (r *renderer) listen() {
 
 // flush renders the buffer.
 func (r *renderer) flush() {
+       r.mtx.Lock()
+       defer r.mtx.Unlock()
+
        if r.buf.Len() == 0 || r.buf.String() == r.lastRender {
                // Nothing to do
                return
@@ -112,9 +115,6 @@ func (r *renderer) flush() {
 
        out := new(bytes.Buffer)
 
-       r.mtx.Lock()
-       defer r.mtx.Unlock()
-
        // Clear any lines we painted in the last render.
        if r.linesRendered > 0 {
                for i := r.linesRendered - 1; i > 0; i-- {
Full log
chlunde@fedora ~/.../examples/spinner$ go build -race .
go: downloading github.com/muesli/termenv v0.7.4
go: downloading github.com/charmbracelet/bubbles v0.7.6
chlunde@fedora ~/.../examples/spinner$ ./spinner 
==================
WARNING: DATA RACE
Read at 0x00c0000ee0a0 by goroutine 11:
  bytes.(*Buffer).Len()
      /home/chlunde/opt/go/src/bytes/buffer.go:73 +0x64
  github.com/charmbracelet/bubbletea.(*renderer).flush()
      /home/chlunde/src/bubbletea/renderer.go:91 +0x45
  github.com/charmbracelet/bubbletea.(*renderer).listen()
      /home/chlunde/src/bubbletea/renderer.go:76 +0x185

Previous write at 0x00c0000ee0a0 by main goroutine:
  bytes.(*Buffer).Reset()
      /home/chlunde/opt/go/src/bytes/buffer.go:98 +0xf5
  github.com/charmbracelet/bubbletea.(*renderer).write()
      /home/chlunde/src/bubbletea/renderer.go:195 +0x13c
  github.com/charmbracelet/bubbletea.(*Program).Start()
      /home/chlunde/src/bubbletea/tea.go:330 +0xbbb
  main.main()
      /home/chlunde/src/bubbletea/examples/spinner/main.go:27 +0x229

Goroutine 11 (running) created at:
  github.com/charmbracelet/bubbletea.(*renderer).start()
      /home/chlunde/src/bubbletea/renderer.go:61 +0xc5
  github.com/charmbracelet/bubbletea.(*Program).Start()
      /home/chlunde/src/bubbletea/tea.go:246 +0x664
  main.main()
      /home/chlunde/src/bubbletea/examples/spinner/main.go:27 +0x229
==================
==================
WARNING: DATA RACE
Read at 0x00c0000ee0b8 by goroutine 11:
  bytes.(*Buffer).Len()
      /home/chlunde/opt/go/src/bytes/buffer.go:73 +0x8d
  github.com/charmbracelet/bubbletea.(*renderer).flush()
      /home/chlunde/src/bubbletea/renderer.go:91 +0x45
  github.com/charmbracelet/bubbletea.(*renderer).listen()
      /home/chlunde/src/bubbletea/renderer.go:76 +0x185

Previous write at 0x00c0000ee0b8 by main goroutine:
  bytes.(*Buffer).Reset()
      /home/chlunde/opt/go/src/bytes/buffer.go:99 +0x112
  github.com/charmbracelet/bubbletea.(*renderer).write()
      /home/chlunde/src/bubbletea/renderer.go:195 +0x13c
  github.com/charmbracelet/bubbletea.(*Program).Start()
      /home/chlunde/src/bubbletea/tea.go:330 +0xbbb
  main.main()
      /home/chlunde/src/bubbletea/examples/spinner/main.go:27 +0x229

Goroutine 11 (running) created at:
  github.com/charmbracelet/bubbletea.(*renderer).start()
      /home/chlunde/src/bubbletea/renderer.go:61 +0xc5
  github.com/charmbracelet/bubbletea.(*Program).Start()
      /home/chlunde/src/bubbletea/tea.go:246 +0x664
  main.main()
      /home/chlunde/src/bubbletea/examples/spinner/main.go:27 +0x229
==================
==================
WARNING: DATA RACE
Read at 0x00c0000c20c0 by goroutine 11:
  runtime.slicebytetostring()
      /home/chlunde/opt/go/src/runtime/string.go:80 +0x0
  bytes.(*Buffer).String()
      /home/chlunde/opt/go/src/bytes/buffer.go:65 +0x137
  github.com/charmbracelet/bubbletea.(*renderer).flush()
      /home/chlunde/src/bubbletea/renderer.go:91 +0x45
  github.com/charmbracelet/bubbletea.(*renderer).listen()
      /home/chlunde/src/bubbletea/renderer.go:76 +0x185

Previous write at 0x00c0000c20c0 by main goroutine:
  runtime.slicecopy()
      /home/chlunde/opt/go/src/runtime/slice.go:247 +0x0
  bytes.(*Buffer).WriteString()
      /home/chlunde/opt/go/src/bytes/buffer.go:186 +0x147
  github.com/charmbracelet/bubbletea.(*renderer).write()
      /home/chlunde/src/bubbletea/renderer.go:196 +0x164
  github.com/charmbracelet/bubbletea.(*Program).Start()
      /home/chlunde/src/bubbletea/tea.go:330 +0xbbb
  main.main()
      /home/chlunde/src/bubbletea/examples/spinner/main.go:27 +0x229

Goroutine 11 (running) created at:
  github.com/charmbracelet/bubbletea.(*renderer).start()
      /home/chlunde/src/bubbletea/renderer.go:61 +0xc5
  github.com/charmbracelet/bubbletea.(*Program).Start()
      /home/chlunde/src/bubbletea/tea.go:246 +0x664
  main.main()
      /home/chlunde/src/bubbletea/examples/spinner/main.go:27 +0x229
==================


   ⡿  Loading forever...press q to quit


Found 3 data race(s)

Allow to change output Writer

Hi,
I've come across this library lately while doing som TUI stuff and I really like the approach here.
I'm using mostly Windows 10, but was trying to use it on Windows 7 and there is a bit of problem, because Win7 doesn't support ANSI characters.
In reality Windows 7 is out of support, so I guess it's no big deal (I will be upgrading too anyway), but I was wondering if it wouldn't be worth to try to add a function which could override the default output *os.File. Maybe it's not worth just for that, but could be used also in other cases, when you want to dump it in a file or logger with your specific Writer (even though, I'm not sure if that's worth it or useful at all).

What I did was, I tried to run it with some Program function like this p.SetOutput(colorable.NewColorableStdout()), where colorable is github.com/mattn/go-colorable package. It allows colors even on older version of Windows console (translating ANSI to API calls to write the output) and makes it more usable. It still doesn't resolve all the problems, but makes some basic stuff work.
For simplicity, I just split output to original os.File for inputs and io.Writer for output.

Anyway, this is more like a suggestion or a thing to think about. So it's up to you, if you find it worth it. I don't mind and will definitely use it either way :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.