[GH-ISSUE #1064] os functions seem to mess up the displayed UI #771

Closed
opened 2026-03-04 01:07:37 +03:00 by kerem · 1 comment
Owner

Originally created by @Ares1605 on GitHub (Dec 28, 2024).
Original GitHub issue: https://github.com/rivo/tview/issues/1064

WSL2: go version go1.23.4 linux/amd64
Windows: go version go1.22.5 windows/amd64

Building an application with tview and seeming to get very strange UI malformities when I open and read through a file with the os pakckage. Here's the relevant code:
`func LogViewer(app *types.App, client *configtypes.BaseClient) (*tview.Flex, *tview.Flex) {
main := tview.NewFlex()
main.SetDirection(tview.FlexRow)

section := tview.NewForm()
section.AddFormItem(tview.NewInputField())

go func() {
stages, err := client.GetLogs(3, 0)
app.TviewApp.QueueUpdateDraw(func() {
if err == nil {
for _, stage := range stages {
main.AddItem(tview.NewTextView().SetText(stage.Title), 0, 1, false)
}
} else {
main.AddItem(tview.NewTextView().SetText(fmt.Sprintf("Error occurred while getting logs: %s", err.Error())), 0, 1, false)
}
})
}()

return main, tview.NewFlex().AddItem(section, 0, 1, false)
}

// baseclient.go
func (c *BaseClient) GetLogs(minCount int, offset int64) ([]log.Stage, error) {
return log.GetLogs(c.FolderName, minCount, offset)
}

// info.go
func GetLogs(clientFolder string, minCount int, offset int64) ([]Stage, error) {
clientLogPath, err := getWebhookLogPath(clientFolder)
if err != nil {
return nil, err
}
newestLog, err := getNewestLogPath(clientLogPath)
if err != nil {
return nil, err
}

path := filepath.Join(newestLog, "info.log")
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()

var stages []Stage

line := ""
var cursor int64 = -offset
stat, _ := file.Stat()
filesize := stat.Size()
for {
cursor -= 1
file.Seek(cursor, io.SeekEnd)

char := make([]byte, 1)
file.Read(char)

if char[0] == 10 || char[0] == 13 { // check if \n check
  if cursor == -1 { // cursor == -1 skips the \n at the end of the file
    continue
  }

  logEntry, err := parseLine(line)
  if err != nil {
    return stages, err
  }
  switch v := logEntry.(type) {
  case *Stage:
    stages = append(stages, *v)
  default:
    addToStages(stages, logEntry)
  }
  line = "" // reset line buffer

  if len(stages) == minCount { // check termination status
    return stages, nil
  }
}

line = fmt.Sprintf("%s%s", string(char), line) // there is more efficient way

if cursor == -filesize { // stop if we are at the begining
  break
}

}

return stages, nil
}
`

I have an event on a tview component that triggers LogViewer. From my debugging, as soon as GetLogs is run, no matter if the return value is used or not, the tview UI become malformed. I believe this is because I'm reading the file through a buffer in GetLogs, and maybe messing with the UI. I run it through a go func to move it to another "thread," but that doesn't change anything. Here is the before the after of the LogViewer function being triggered (by pressing '1'):
image
image
I have red arrows to show where the borders seem to "shift."

Originally created by @Ares1605 on GitHub (Dec 28, 2024). Original GitHub issue: https://github.com/rivo/tview/issues/1064 WSL2: go version go1.23.4 linux/amd64 Windows: go version go1.22.5 windows/amd64 Building an application with tview and seeming to get very strange UI malformities when I open and read through a file with the os pakckage. Here's the relevant code: `func LogViewer(app *types.App, client *configtypes.BaseClient) (*tview.Flex, *tview.Flex) { main := tview.NewFlex() main.SetDirection(tview.FlexRow) section := tview.NewForm() section.AddFormItem(tview.NewInputField()) go func() { stages, err := client.GetLogs(3, 0) app.TviewApp.QueueUpdateDraw(func() { if err == nil { for _, stage := range stages { main.AddItem(tview.NewTextView().SetText(stage.Title), 0, 1, false) } } else { main.AddItem(tview.NewTextView().SetText(fmt.Sprintf("Error occurred while getting logs: %s", err.Error())), 0, 1, false) } }) }() return main, tview.NewFlex().AddItem(section, 0, 1, false) } // baseclient.go func (c *BaseClient) GetLogs(minCount int, offset int64) ([]log.Stage, error) { return log.GetLogs(c.FolderName, minCount, offset) } // info.go func GetLogs(clientFolder string, minCount int, offset int64) ([]Stage, error) { clientLogPath, err := getWebhookLogPath(clientFolder) if err != nil { return nil, err } newestLog, err := getNewestLogPath(clientLogPath) if err != nil { return nil, err } path := filepath.Join(newestLog, "info.log") file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() var stages []Stage line := "" var cursor int64 = -offset stat, _ := file.Stat() filesize := stat.Size() for { cursor -= 1 file.Seek(cursor, io.SeekEnd) char := make([]byte, 1) file.Read(char) if char[0] == 10 || char[0] == 13 { // check if \n check if cursor == -1 { // cursor == -1 skips the \n at the end of the file continue } logEntry, err := parseLine(line) if err != nil { return stages, err } switch v := logEntry.(type) { case *Stage: stages = append(stages, *v) default: addToStages(stages, logEntry) } line = "" // reset line buffer if len(stages) == minCount { // check termination status return stages, nil } } line = fmt.Sprintf("%s%s", string(char), line) // there is more efficient way if cursor == -filesize { // stop if we are at the begining break } } return stages, nil } ` I have an event on a tview component that triggers LogViewer. From my debugging, as soon as GetLogs is run, no matter if the return value is used or not, the tview UI become malformed. I believe this is because I'm reading the file through a buffer in GetLogs, and maybe messing with the UI. I run it through a go func to move it to another "thread," but that doesn't change anything. Here is the before the after of the LogViewer function being triggered (by pressing '1'): ![image](https://github.com/user-attachments/assets/7b6dbf06-4c48-43c6-998c-658626d85bae) ![image](https://github.com/user-attachments/assets/f4067147-ee16-48b7-b468-94bdd23ea5d9) I have red arrows to show where the borders seem to "shift."
kerem closed this issue 2026-03-04 01:07:37 +03:00
Author
Owner

@xxxserxxx commented on GitHub (Mar 25, 2025):

Edit

I went back in history and found a version that doesn't exhibit this behavior. I grabbed the go.mod and go.sum from that version, ran tidy, and ran the program and it exhibits the behavior. So I don't think this is an issue with tview, per se. The changes that could be causing it are: I'm now changing the title(s) of the boxes; and I changed the contents of the status bar itself -- gui_helpers.go#21 (formatPlayerStatus()). So: any hints about how I could be using the API incorrectly that's causing this would be much appreciated, but it's not a tview issue.

Edit 2

I can cause this to happen on application start if I printf something to Stdout during set-up. Or, put another way, this behavior can be caused by printing to stdout (and probably stderr) when the program is running. I've confirmed, however, that my code is calling neither fmt.Printf nor fmt.Fprintf to either stderr or stdout; and while a dependency might be, I see nothing printed when I exit (as I did whn I intentionally printed a value).

Original comment

This was closed as completed; what was the fix?

I'm seeing something similar. The source code for the the entire project is here, This issue appeared only recently; however, development on the project is active, and I almost certainly updated the dependencies at some point, upgrading the tview version.

Here's how it looks when I first run the program:

Image

Here's after I start playing music:

Image

The code for laying out that top bar is at gui.go#163, and it's added to the main GUI at gui.go#197:

	rootFlex := tview.NewFlex().
		SetDirection(tview.FlexRow).
		AddItem(topBarFlex, 1, 0, false).
		AddItem(ui.pages, 0, 1, true).
		AddItem(ui.menuWidget.Root, 1, 0, false)

The handler for the event that causes the rendering issue is at gui_handlers.go#53 -- it does no drawing, but it does trigger a cascade that changes the contents of the text in that top bar.

This happens in alacritty and ghostty, and within (or outside of) tmux. To replicate it, you'll need a Subsonic server -- the easiest is probably gonic, which you can just point at a directory of music and go. It happens when the event is triggered regardless of which page is shown; even the log page, which contains no boxes, displays corrupt rendering of the text in topBarFlex.

It's as if a newline is being drawn at the bottom of the page, shifting everything up, before the UI is refreshed; only, the blank space at the top bar (and, so far only the top line is affected) is not cleared. Unlike @Ares1605, I do not see bottom bars being drawn incorrectly, and there are no other UI pages in the UI where there are multiple boxes with borders in a column.

I didn't note when I first saw this, but I did confirm it was an issue with v0.0.0-20241227133733-17b7edb88c57, as well as the most recent v0.0.0-20250325173046-7b72abf45814. I will try to bisect backwards and see if I can find a version that doesn't behave this way -- however, as @Ares1605 closed this ticket, I'm hoping there's something I can change in the code to prevent this.

<!-- gh-comment-id:2752570473 --> @xxxserxxx commented on GitHub (Mar 25, 2025): ### Edit I went back in history and found a version that doesn't exhibit this behavior. I grabbed the go.mod and go.sum from that version, ran tidy, and ran the program and it exhibits the behavior. So I don't think this is an issue with tview, per se. The changes that could be causing it are: I'm now changing the title(s) of the boxes; and I changed the contents of the status bar itself -- `gui_helpers.go#21` (`formatPlayerStatus()`). So: any hints about how I could be using the API incorrectly that's causing this would be much appreciated, but it's not a tview issue. ### Edit 2 I can cause this to happen on application start if I printf something to Stdout during set-up. Or, put another way, this behavior can be caused by printing to stdout (and probably stderr) when the program is running. I've confirmed, however, that my code is calling neither `fmt.Printf` nor `fmt.Fprintf` to either stderr or stdout; and while a dependency might be, I see nothing printed when I exit (as I did whn I intentionally printed a value). ### Original comment This was closed as completed; what was the fix? I'm seeing something similar. The source code for the the entire project is here, This issue appeared only recently; however, development on the project is active, and I almost certainly updated the dependencies at some point, upgrading the tview version. Here's how it looks when I first run the program: ![Image](https://github.com/user-attachments/assets/15a5ed9b-143b-4d33-930f-6fbffcd91131) Here's after I start playing music: ![Image](https://github.com/user-attachments/assets/b17ca017-5715-4790-884b-fa726068d8c3) The code for laying out that top bar is at `gui.go#163`, and it's added to the main GUI at `gui.go#197`: ```go rootFlex := tview.NewFlex(). SetDirection(tview.FlexRow). AddItem(topBarFlex, 1, 0, false). AddItem(ui.pages, 0, 1, true). AddItem(ui.menuWidget.Root, 1, 0, false) ``` The handler for the event that causes the rendering issue is at `gui_handlers.go#53` -- it does no drawing, but it does trigger a cascade that changes the contents of the text in that top bar. This happens in alacritty and ghostty, and within (or outside of) tmux. To replicate it, you'll need a Subsonic server -- the easiest is probably [gonic](https://github.com/sentriz/gonic), which you can just point at a directory of music and go. It happens when the event is triggered regardless of which page is shown; even the `log` page, which contains no boxes, displays corrupt rendering of the text in `topBarFlex`. It's as if a newline is being drawn at the bottom of the page, shifting everything up, before the UI is refreshed; only, the blank space at the top bar (and, so far _only_ the top line is affected) is not cleared. Unlike @Ares1605, I do not see bottom bars being drawn incorrectly, and there are no other UI pages in the UI where there are multiple boxes with borders in a column. I didn't note when I first saw this, but I did confirm it was an issue with v0.0.0-20241227133733-17b7edb88c57, as well as the most recent v0.0.0-20250325173046-7b72abf45814. I will try to bisect backwards and see if I can find a version that doesn't behave this way -- however, as @Ares1605 closed this ticket, I'm hoping there's something I can change in the code to prevent this.
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#771
No description provided.