bubbletea icon indicating copy to clipboard operation
bubbletea copied to clipboard

`tea.Listen()` to automatically listen on a `chan tea.Msg`

Open myaaaaaaaaa opened this issue 5 months ago • 4 comments

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

Currently, tea.Cmds can only send a single tea.Msg before terminating, making things awkward when there is a need for a continuously running function that sends multiple tea.Msgs.

This can be seen in the realtime example, where a wrapper function waitForActivity() must be used to relay messages from listenForActivity().

Describe the solution you'd like

A tea.Listen(func(chan<- tea.Msg)) tea.Cmd function to accompany functions like tea.Batch() or tea.Every(). It should create a chan 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 the Update() 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.

diff --git a/examples/realtime/main.go b/examples/realtime/main.go
index 4abddd3..1e1a71c 100644
--- a/examples/realtime/main.go
+++ b/examples/realtime/main.go
@@ -21,25 +21,15 @@ type responseMsg struct{}
-func listenForActivity(sub chan struct{}) tea.Cmd {
-	return func() tea.Msg {
-		for {
-			time.Sleep(time.Millisecond * time.Duration(rand.Int63n(900)+100)) // nolint:gosec
-			sub <- struct{}{}
-		}
-	}
-}
-
-// A command that waits for the activity on a channel.
-func waitForActivity(sub chan struct{}) tea.Cmd {
-	return func() tea.Msg {
-		return responseMsg(<-sub)
+func listenForActivity(sub chan<- tea.Msg) {
+	for {
+		time.Sleep(time.Millisecond * time.Duration(rand.Int63n(900)+100)) // nolint:gosec
+		sub <- responseMsg{}
 	}
 }
 
 type model struct {
-	sub       chan struct{} // where we'll receive activity notifications
-	responses int           // how many responses we've received
+	responses int // how many responses we've received
 	spinner   spinner.Model
 	quitting  bool
 }
@@ -47,8 +37,7 @@ type model struct {
 func (m model) Init() tea.Cmd {
 	return tea.Batch(
 		m.spinner.Tick,
-		listenForActivity(m.sub), // generate activity
-		waitForActivity(m.sub),   // wait for activity
+		tea.Listen(listenForActivity), // generate activity
 	)
 }
@@ -58,8 +47,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	case responseMsg:
-		m.responses++                    // record external activity
-		return m, waitForActivity(m.sub) // wait for next event
+		m.responses++ // record external activity
+		return m, nil
@@ -79,7 +68,6 @@ func (m model) View() string {
 	p := tea.NewProgram(model{
-		sub:     make(chan struct{}),
 		spinner: spinner.New(),
 	})

myaaaaaaaaa avatar Sep 11 '24 01:09 myaaaaaaaaa