charmbracelet / bubbletea Goto Github PK
View Code? Open in Web Editor NEWA powerful little TUI framework 🏗
License: MIT License
A powerful little TUI framework 🏗
License: MIT License
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.
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?
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! 🙏
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.
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:
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?
This repo needs a guide to inform users about contributing
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.
Using bubbletea, I would like to have a progress bar similar to apt
's:
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 ?
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! 😄
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?
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
}
Any help is much appreciated
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
In the original Elm Architecture, Update()
just sends updated model to View()
. Just like the figure below:
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)?
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.
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!
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:
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
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.
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)
Would be cool to be able to display images in the CLI (think displaying graphs with a higher resolution than ASCII characters) - this kind of thing has been implemented in projects like neofetch using various image backends (see https://sw.kovidgoyal.net/kitty/kittens/icat/ for example)
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.
(I ran ls
just before running the program, to illustrate how it writes over top of the existing terminal contents)
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
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:
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):
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.
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?
👋 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?
Hello i tried to input unicode but it is not recognized by bubbletea, Thank you.
go version go1.17 windows/amd64
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
}
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.
For codes in https://github.com/charmbracelet/bubbletea/tree/master/examples
$ grun
examples/http
# examples/http
./main.go:28:21: not enough arguments in call to tea.NewProgram
have (model)
want (tea.Init, tea.Update, tea.View)
Can you please fix this for beginner like me?
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
We have WithOutput(output *os.File)
, but Program.output is io.Writer
, is this on purpose?
Running the example in the README in Windows is not working as expected (see screenshot).
Windows 10 19041.508
Go 1.15.2
Terminal applications tested:
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.
If multiple tea.Program
s 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.
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.
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.
l.SetFilteringEnabled(true)
setting this to true enables the search but now the issue is it shows nothing in the search like so:
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.
I'd like to ask why you decided to go with that architecture. tcell is much more cross-platform and performant than printf.
In a tcell-like environment View() string
could become View([][]Cell)
, which could reduce memory allocation. Cell
would be a struct containing https://godoc.org/github.com/gdamore/tcell#CellBuffer.SetContent parameters
The example should work just as well if the initialModel is instantiated in func main, rather than as a global variable. Teach good habits, not bad ones :)
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.
How to enable this feature (drag and drop), seen on the official Bubbletea website.
When a empty string is returned by the View function the prior output is untouched or frozen.
The expected behavior would be a plank screen.
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! ❤️
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!
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-- {
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)
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 :)
For the basic tutorial, if the number of the choices
is more than the height of the terminal, only the last lines can be viewed.
So is there any solutions to handle cases like this?
I've tried this example on Windows Terminal which is new replacement for cmd
nor cmd nor power shell mode are working and most of the time (x y)
bar is not rendering
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.