-
Notifications
You must be signed in to change notification settings - Fork 832
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
tea.Listen()
to automatically listen on a chan tea.Msg
#1135
Comments
This is an interesting idea (I'd probably call it |
Returning from the function would be sufficient, with the exact mechanism up to the user to decide. Here's the above example adapted to use a func listenForActivity(done chan struct{}) func(chan<- tea.Msg) {
return func(sub chan<- tea.Msg) {
for {
time.Sleep(time.Millisecond * time.Duration(rand.Int63n(900)+100)) // nolint:gosec
sub <- responseMsg{}
select {
case <-done:
return
default:
}
}
}
}
func (m model) Init() tea.Cmd {
return tea.Batch(
m.spinner.Tick,
tea.Subscribe(listenForActivity(m.done)),
)
}
func (m model) somewhere() {
// ...
close(m.done)
} In practice, I suspect most real-world usage would involve progress indicators and be vastly easier to follow (which would be the whole point of this feature request): func downloadFiles(sub chan<- tea.Msg) {
fileList := []string{ /* ... */ }
for i, file := range fileList {
download(file)
sub <- progressMsg{i * 100 / len(fileList)}
sub <- statusMsg{"downloading " + file}
}
sub <- progressMsg{100}
sub <- statusMsg{"finished downloading"}
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// switch case { ...
return m, tea.Subscribe(downloadFiles)
// ... }
} Note how being able to send multiple |
I think this generally makes sense, however we'd also need a way to cancel subscriptions from the outside as well. For example, in this case, the user might press a key to cancel the download and we'd need to stop the subscription before the download completed. I suppose For some reference, Elm programs have a dedicated function for subscriptions which is called after update. It works something like this. subscriptions : model -> Sub msg
subscriptions m =
if m.someState then
Sub.batch [ someSub, someOtherSub ]
else
Sub.none There's a lot of magic in cancelling subscriptions, though, and I know it was a tricky thing to get right. |
Let me take a stab at what this might look like: func downloadFiles(ctx context.Context, fileList []string) func(chan<- tea.Msg) {
return func(sub chan<- tea.Msg) {
for i, file := range fileList {
select {
case <-ctx.Done():
sub <- statusMsg{"download cancelled"}
return
default:
}
download(file)
sub <- progressMsg{i * 100 / len(fileList)}
sub <- statusMsg{"downloading " + file}
}
sub <- progressMsg{100}
sub <- statusMsg{"finished downloading"}
}
}
type model struct {
// ...
cancels []context.CancelFunc
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch {
// case ...
case "d":
fileList := []string{ /* ... */ }
ctx, cancel := context.WithCancel(context.Background())
m.cancels = append(m.cancels, cancel)
return m, tea.Subscribe(downloadFiles(ctx, fileList))
case "esc":
for _, cancel := range m.cancels {
cancel()
}
m.cancels = nil
return m, nil
}
} Does that look right? If so, then it seems like there's no need for Bubble Tea itself to be aware of On a side note, it dawned on me while I was typing this that Bubble Tea doesn't expose any concurrency primitives anywhere, so perhaps -func downloadFiles(ctx context.Context, fileList []string) func(chan<- tea.Msg) {
- return func(sub chan<- tea.Msg) {
+func downloadFiles(ctx context.Context, fileList []string) func(func(tea.Msg)) {
+ return func(send func(tea.Msg)) {
for i, file := range fileList {
select {
case <-ctx.Done():
- sub <- statusMsg{"download cancelled"}
+ send(statusMsg{"download cancelled"})
return
default:
}
download(file)
- sub <- progressMsg{i * 100 / len(fileList)}
- sub <- statusMsg{"downloading " + file}
+ send(progressMsg{i * 100 / len(fileList)})
+ send(statusMsg{"downloading " + file})
}
- sub <- progressMsg{100}
- sub <- statusMsg{"finished downloading"}
+ send(progressMsg{100})
+ send(statusMsg{"finished downloading"})
}
} Footnotes
|
Is your feature request related to a problem? Please describe.
Currently,
tea.Cmd
s can only send a singletea.Msg
before terminating, making things awkward when there is a need for a continuously running function that sends multipletea.Msg
s.This can be seen in the realtime example, where a wrapper function
waitForActivity()
must be used to relay messages fromlistenForActivity()
.Describe the solution you'd like
A
tea.Listen(func(chan<- tea.Msg)) tea.Cmd
function to accompany functions liketea.Batch()
ortea.Every()
. It should create achan tea.Msg
, pass it to the given function and run it in a separate goroutine, and relay messages that it receives on said channel to theUpdate()
function.Below is how the realtime example would be changed. Note how there is no longer a need to manually resend
waitForActivity()
commands or pass around a channel.The text was updated successfully, but these errors were encountered: