[GH-ISSUE #899] Question about textview refreshing content in real time #656

Closed
opened 2026-03-04 01:06:48 +03:00 by kerem · 11 comments
Owner

Originally created by @7yyo on GitHub (Oct 12, 2023).
Original GitHub issue: https://github.com/rivo/tview/issues/899

After adding setDoneFunc to an item, execute a piece of code logic. This process will print some logs. These logs will be transferred to another textview for display. However, the program can only display all logs after setDoneFunc ends and will not output them in real time. This Why? I can provide pseudocode if necessary. The code is similar to the demo of textview

Originally created by @7yyo on GitHub (Oct 12, 2023). Original GitHub issue: https://github.com/rivo/tview/issues/899 After adding setDoneFunc to an item, execute a piece of code logic. This process will print some logs. These logs will be transferred to another textview for display. However, the program can only display all logs after setDoneFunc ends and will not output them in real time. This Why? I can provide pseudocode if necessary. The code is similar to the demo of textview
kerem closed this issue 2026-03-04 01:06:49 +03:00
Author
Owner

@rivo commented on GitHub (Oct 12, 2023):

Yes, please provide some brief code that can be run to reproduce this.

<!-- gh-comment-id:1759603382 --> @rivo commented on GitHub (Oct 12, 2023): Yes, please provide some brief code that can be run to reproduce this.
Author
Owner

@7yyo commented on GitHub (Oct 13, 2023):

@rivo Thanks for the response. The following code can be run directly. There is a treeView and a textView. When setDoneFunc is executed, some strings will be output to textView for display, but they can only be displayed after setDoneFunc is executed. Strings cannot be printed out in textView in real time.


package main

import (
	"fmt"
	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"
	"time"
)

func main() {
	app := tview.NewApplication()
	grid := tview.NewGrid().SetRows(0, 0).SetColumns(0, 0)
	treeView := tview.NewTreeView()
	root := tview.NewTreeNode("hello world - tree")
	treeView.SetRoot(root).SetCurrentNode(root).SetTitle("tree").SetBorder(true)
	textView := tview.NewTextView()
	textView.SetTitle("text").SetBorder(true)
	textView.SetChangedFunc(func() {
		app.Draw()
	})
	grid.AddItem(treeView, 0, 0, 1, 1, 0, 0, true)
	grid.AddItem(textView, 1, 0, 1, 1, 0, 0, true)

	treeView.SetDoneFunc(func(key tcell.Key) {
		fmt.Fprintf(textView, "%v - %s\n", time.Now(), "hello")
		time.Sleep(1000 * time.Millisecond)
		fmt.Fprintf(textView, "%v - %s\n", time.Now(), "world")
		time.Sleep(5000 * time.Millisecond)
		fmt.Fprintf(textView, "%v - %s\n", time.Now(), "tview")
	})

	if err := app.SetRoot(grid, true).EnableMouse(true).Run(); err != nil {
		panic(err)
	}
}
<!-- gh-comment-id:1761174940 --> @7yyo commented on GitHub (Oct 13, 2023): @rivo Thanks for the response. The following code can be run directly. There is a treeView and a textView. When setDoneFunc is executed, some strings will be output to textView for display, but they can only be displayed after setDoneFunc is executed. Strings cannot be printed out in textView in real time. ```go package main import ( "fmt" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" "time" ) func main() { app := tview.NewApplication() grid := tview.NewGrid().SetRows(0, 0).SetColumns(0, 0) treeView := tview.NewTreeView() root := tview.NewTreeNode("hello world - tree") treeView.SetRoot(root).SetCurrentNode(root).SetTitle("tree").SetBorder(true) textView := tview.NewTextView() textView.SetTitle("text").SetBorder(true) textView.SetChangedFunc(func() { app.Draw() }) grid.AddItem(treeView, 0, 0, 1, 1, 0, 0, true) grid.AddItem(textView, 1, 0, 1, 1, 0, 0, true) treeView.SetDoneFunc(func(key tcell.Key) { fmt.Fprintf(textView, "%v - %s\n", time.Now(), "hello") time.Sleep(1000 * time.Millisecond) fmt.Fprintf(textView, "%v - %s\n", time.Now(), "world") time.Sleep(5000 * time.Millisecond) fmt.Fprintf(textView, "%v - %s\n", time.Now(), "tview") }) if err := app.SetRoot(grid, true).EnableMouse(true).Run(); err != nil { panic(err) } }
Author
Owner

@rivo commented on GitHub (Oct 20, 2023):

@digitallyserviced is generally right in that your SetDoneFunc is part of the main goroutine. So if you're blocking that function (with your sleep calls, in this example), you're blocking everything basically. If you want to stream text to your TextView, you need to do it from a separate goroutine. If you know all text in advance, however, just use SetText() without the time.Sleep calls (no goroutine needed in that case).

A few additional notes:

  • Just call app.Draw() from your SetChangedFunc. No need to call QueueUpdateDraw() like @digitallyserviced suggested (see the documentation for details).
  • The SetDoneFunc can be used in theory but it's usually reserved to be used internally for forms. (Yes, it shouldn't have been exposed in the first place.) Maybe SetSelectedFunc or SetInputCapture with your own bindings is better.
  • The .SetRows(0, 0).SetColumns(0, 0) calls are not needed: "A value of 0 is assumed for any undefined column.".
<!-- gh-comment-id:1772687562 --> @rivo commented on GitHub (Oct 20, 2023): @digitallyserviced is generally right in that your `SetDoneFunc` is part of the main goroutine. So if you're blocking that function (with your sleep calls, in this example), you're blocking everything basically. If you want to stream text to your `TextView`, you need to do it from a separate goroutine. If you know all text in advance, however, just use [`SetText()`](https://pkg.go.dev/github.com/rivo/tview#TextView.SetText) without the `time.Sleep` calls (no goroutine needed in that case). A few additional notes: * Just call `app.Draw()` from your `SetChangedFunc`. No need to call `QueueUpdateDraw()` like @digitallyserviced suggested (see the [documentation](https://pkg.go.dev/github.com/rivo/tview#TextView.SetChangedFunc) for details). * The `SetDoneFunc` can be used in theory but it's usually reserved to be used internally for forms. (Yes, it shouldn't have been exposed in the first place.) Maybe [`SetSelectedFunc`](https://pkg.go.dev/github.com/rivo/tview#TreeView.SetSelectedFunc) or [`SetInputCapture`](https://pkg.go.dev/github.com/rivo/tview#Box.SetInputCapture) with your own bindings is better. * The `.SetRows(0, 0).SetColumns(0, 0)` calls are not needed: ["A value of 0 is assumed for any undefined column."](https://pkg.go.dev/github.com/rivo/tview#Grid.SetColumns).
Author
Owner

@7yyo commented on GitHub (Oct 23, 2023):

@rivo @digitallyserviced thanks!
To summarize, this code solves the problem I raised

package main

import (
	"fmt"
	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"
	"time"
)

func main() {
	app := tview.NewApplication()
	grid := tview.NewGrid().SetRows(0, 0).SetColumns(0, 0)
	treeView := tview.NewTreeView()
	root := tview.NewTreeNode("hello world - tree")
	treeView.SetRoot(root).SetCurrentNode(root).SetTitle("tree").SetBorder(true)
	textView := tview.NewTextView()
	textView.SetTitle("text").SetBorder(true)
	textView.SetChangedFunc(func() {
		app.Draw()
	})
	grid.AddItem(treeView, 0, 0, 1, 1, 0, 0, true)
	grid.AddItem(textView, 1, 0, 1, 1, 0, 0, true)

	treeView.SetDoneFunc(func(key tcell.Key) {
		go func() {
			fmt.Fprintf(textView, "%v - %s\n", time.Now(), "hello")
			time.Sleep(1000 * time.Millisecond)
			fmt.Fprintf(textView, "%v - %s\n", time.Now(), "world")
			time.Sleep(5000 * time.Millisecond)
			fmt.Fprintf(textView, "%v - %s\n", time.Now(), "tview")
			grid.RemoveItem(treeView)
		}()
	})

	if err := app.SetRoot(grid, true).EnableMouse(true).Run(); err != nil {
		panic(err)
	}
}

However, I added grid.RemoveItem(treeView) and tried to delete the treeview before the goroutine ended, but it failed. Why is this?

<!-- gh-comment-id:1775235308 --> @7yyo commented on GitHub (Oct 23, 2023): @rivo @digitallyserviced thanks! To summarize, this code solves the problem I raised ```go package main import ( "fmt" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" "time" ) func main() { app := tview.NewApplication() grid := tview.NewGrid().SetRows(0, 0).SetColumns(0, 0) treeView := tview.NewTreeView() root := tview.NewTreeNode("hello world - tree") treeView.SetRoot(root).SetCurrentNode(root).SetTitle("tree").SetBorder(true) textView := tview.NewTextView() textView.SetTitle("text").SetBorder(true) textView.SetChangedFunc(func() { app.Draw() }) grid.AddItem(treeView, 0, 0, 1, 1, 0, 0, true) grid.AddItem(textView, 1, 0, 1, 1, 0, 0, true) treeView.SetDoneFunc(func(key tcell.Key) { go func() { fmt.Fprintf(textView, "%v - %s\n", time.Now(), "hello") time.Sleep(1000 * time.Millisecond) fmt.Fprintf(textView, "%v - %s\n", time.Now(), "world") time.Sleep(5000 * time.Millisecond) fmt.Fprintf(textView, "%v - %s\n", time.Now(), "tview") grid.RemoveItem(treeView) }() }) if err := app.SetRoot(grid, true).EnableMouse(true).Run(); err != nil { panic(err) } } ``` However, I added grid.RemoveItem(treeView) and tried to delete the treeview before the goroutine ended, but it failed. Why is this?
Author
Owner

@rivo commented on GitHub (Oct 23, 2023):

You can't make other, especially structural changes to your widgets in a separate goroutine. For example, if the application is currently re-drawing the screen while you change objects that need to be drawn, you will run in to race issues. (The io.Reader interface of TextView is an exception here.)

Please read the following article about concurrency in tview:

https://github.com/rivo/tview/wiki/Concurrency

For your specific program, you will have to wrap grid.RemoveItem(treeView) in a app.QueueUpdateDraw().

<!-- gh-comment-id:1775488383 --> @rivo commented on GitHub (Oct 23, 2023): You can't make other, especially structural changes to your widgets in a separate goroutine. For example, if the application is currently re-drawing the screen while you change objects that need to be drawn, you will run in to race issues. (The `io.Reader` interface of `TextView` is an exception here.) Please read the following article about concurrency in `tview`: https://github.com/rivo/tview/wiki/Concurrency For your specific program, you will have to wrap `grid.RemoveItem(treeView)` in a `app.QueueUpdateDraw()`.
Author
Owner

@7yyo commented on GitHub (Oct 23, 2023):

@rivo I modified it like this but it still doesn't work. What's the problem?

	treeView.SetDoneFunc(func(key tcell.Key) {
		go func() {
			fmt.Fprintf(textView, "%v - %s\n", time.Now(), "hello")
			time.Sleep(1000 * time.Millisecond)
			fmt.Fprintf(textView, "%v - %s\n", time.Now(), "world")
			time.Sleep(1000 * time.Millisecond)
			fmt.Fprintf(textView, "%v - %s\n", time.Now(), "tview")
			time.Sleep(1000 * time.Millisecond)
			go func() {
				app.QueueUpdateDraw(func() {
					grid.RemoveItem(textView)
				})
			}()
		}()
	})
<!-- gh-comment-id:1775581630 --> @7yyo commented on GitHub (Oct 23, 2023): @rivo I modified it like this but it still doesn't work. What's the problem? ```go treeView.SetDoneFunc(func(key tcell.Key) { go func() { fmt.Fprintf(textView, "%v - %s\n", time.Now(), "hello") time.Sleep(1000 * time.Millisecond) fmt.Fprintf(textView, "%v - %s\n", time.Now(), "world") time.Sleep(1000 * time.Millisecond) fmt.Fprintf(textView, "%v - %s\n", time.Now(), "tview") time.Sleep(1000 * time.Millisecond) go func() { app.QueueUpdateDraw(func() { grid.RemoveItem(textView) }) }() }() }) ```
Author
Owner

@rivo commented on GitHub (Oct 23, 2023):

You don't need the second goroutine.

Can you be a bit more specific? What exactly does not work?

<!-- gh-comment-id:1775840221 --> @rivo commented on GitHub (Oct 23, 2023): You don't need the second goroutine. Can you be a bit more specific? What exactly does not work?
Author
Owner

@7yyo commented on GitHub (Oct 24, 2023):

@rivo The code is like this, but textview cannot be automatically hidden

func main() {
	app := tview.NewApplication()
	grid := tview.NewGrid().SetRows(0, 0).SetColumns(0, 0)
	treeView := tview.NewTreeView()
	root := tview.NewTreeNode("hello world - tree")
	treeView.SetRoot(root).SetCurrentNode(root).SetTitle("tree").SetBorder(true)
	textView := tview.NewTextView()
	textView.SetTitle("text").SetBorder(true)
	textView.SetChangedFunc(func() {
		app.Draw()
	})
	grid.AddItem(treeView, 0, 0, 1, 1, 0, 0, true)
	grid.AddItem(textView, 1, 0, 1, 1, 0, 0, true)

	treeView.SetDoneFunc(func(key tcell.Key) {
		go func() {
			fmt.Fprintf(textView, "%v - %s\n", time.Now(), "hello")
			time.Sleep(1000 * time.Millisecond)
			fmt.Fprintf(textView, "%v - %s\n", time.Now(), "world")
			time.Sleep(1000 * time.Millisecond)
			fmt.Fprintf(textView, "%v - %s\n", time.Now(), "tview")
			time.Sleep(1000 * time.Millisecond)
			app.QueueUpdateDraw(func() {
				grid.RemoveItem(textView)
			})
		}()
	})
	if err := app.SetRoot(grid, true).EnableMouse(true).Run(); err != nil {
		panic(err)
	}
}
<!-- gh-comment-id:1776489240 --> @7yyo commented on GitHub (Oct 24, 2023): @rivo The code is like this, but textview cannot be automatically hidden ```go func main() { app := tview.NewApplication() grid := tview.NewGrid().SetRows(0, 0).SetColumns(0, 0) treeView := tview.NewTreeView() root := tview.NewTreeNode("hello world - tree") treeView.SetRoot(root).SetCurrentNode(root).SetTitle("tree").SetBorder(true) textView := tview.NewTextView() textView.SetTitle("text").SetBorder(true) textView.SetChangedFunc(func() { app.Draw() }) grid.AddItem(treeView, 0, 0, 1, 1, 0, 0, true) grid.AddItem(textView, 1, 0, 1, 1, 0, 0, true) treeView.SetDoneFunc(func(key tcell.Key) { go func() { fmt.Fprintf(textView, "%v - %s\n", time.Now(), "hello") time.Sleep(1000 * time.Millisecond) fmt.Fprintf(textView, "%v - %s\n", time.Now(), "world") time.Sleep(1000 * time.Millisecond) fmt.Fprintf(textView, "%v - %s\n", time.Now(), "tview") time.Sleep(1000 * time.Millisecond) app.QueueUpdateDraw(func() { grid.RemoveItem(textView) }) }() }) if err := app.SetRoot(grid, true).EnableMouse(true).Run(); err != nil { panic(err) } } ```
Author
Owner

@rivo commented on GitHub (Oct 24, 2023):

It did disappear but the screen was not cleared before redrawing everything so you could still see the remainders of the last app.Draw cycle. With the latest commit, the application will always clear the screen before drawing all elements. Try your example with the newest version of tview.

<!-- gh-comment-id:1777112223 --> @rivo commented on GitHub (Oct 24, 2023): It did disappear but the screen was not cleared before redrawing everything so you could still see the remainders of the last `app.Draw` cycle. With the latest commit, the application will always clear the screen before drawing all elements. Try your example with the newest version of `tview`.
Author
Owner

@7yyo commented on GitHub (Oct 24, 2023):

@rivo After updating to the latest version, it really works. This project is so cool and the updates are timely. Thanks for the help, I will always support it!

<!-- gh-comment-id:1777124943 --> @7yyo commented on GitHub (Oct 24, 2023): @rivo After updating to the latest version, it really works. This project is so cool and the updates are timely. Thanks for the help, I will always support it!
Author
Owner

@rivo commented on GitHub (Oct 24, 2023):

@7yyo Thank you for the nice words. I try to fix bugs quickly but there may be times when I'm too busy to respond as quickly as I have in this case. Any way, I'm glad it works now.

<!-- gh-comment-id:1777383851 --> @rivo commented on GitHub (Oct 24, 2023): @7yyo Thank you for the nice words. I try to fix bugs quickly but there may be times when I'm too busy to respond as quickly as I have in this case. Any way, I'm glad it works now.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/tview#656
No description provided.