[GH-ISSUE #85] Crash when calling pages.HidePage #65

Closed
opened 2026-03-04 01:01:36 +03:00 by kerem · 3 comments
Owner

Originally created by @muesli on GitHub (Mar 24, 2018).
Original GitHub issue: https://github.com/rivo/tview/issues/85

I'm experiencing a crash when calling pages.HidePage under certain circumstances. I've written and attached a little example that reproduces the issue, below.

You can trigger the crash by starting the app, pressing F3 and then triggering the DoneFunc of searchInput. It will try to hide itself again, which causes a null-pointer access. See the backtrace below.

Interestingly, I've used this in pretty much the same fashion before, without running into this issue (see https://github.com/muesli/service-tools/blob/master/service-monitor/logscmd.go), but can't figure out what I'm doing different here. Being a null-pointer access triggered inside tview, it probably can't harm to guard against it regardless.

Backtrace:

github.com/rivo/tview.(*Application).Run.func1(0xc42010a2a0)
        /home/muesli/Sources/go/src/github.com/rivo/tview/application.go:96 +0x82
panic(0x576d00, 0x667f10)
        /usr/lib/go/src/runtime/panic.go:505 +0x229
github.com/rivo/tview.(*Pages).Focus(0xc42007c9f0, 0x0)
        /home/muesli/Sources/go/src/github.com/rivo/tview/pages.go:228 +0x88
github.com/rivo/tview.(*Pages).HidePage(0xc42007c9f0, 0x5a2e57, 0x6, 0xc420049c30)
        /home/muesli/Sources/go/src/github.com/rivo/tview/pages.go:142 +0xf3
main.main.func1(0xc40000000d)
        /home/muesli/Sources/go/src/github.com/muesli/service-tools/service-monitor/foo/foo.go:45 +0x3f
github.com/rivo/tview.(*InputField).InputHandler.func1(0xc42029a8e0, 0xc42029c050)
        /home/muesli/Sources/go/src/github.com/rivo/tview/inputfield.go:324 +0x188
github.com/rivo/tview.(*Box).WrapInputHandler.func1(0xc42029a8e0, 0xc42029c050)
        /home/muesli/Sources/go/src/github.com/rivo/tview/box.go:149 +0x5d
github.com/rivo/tview.(*Application).Run(0xc42010a2a0, 0x0, 0x0)
        /home/muesli/Sources/go/src/github.com/rivo/tview/application.go:154 +0x2f8
main.main()
        /home/muesli/Sources/go/src/github.com/muesli/service-tools/service-monitor/foo/foo.go:64 +0xe31

Example:

package main

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

func main() {
	app := tview.NewApplication()

	list := tview.NewList()
	list.
		SetBorder(true)

	serviceView := tview.NewTextView()
	serviceView.
		SetDynamicColors(true).
		SetScrollable(true)
	serviceView.
		SetBorder(true)

	flex := tview.NewFlex().
		AddItem(list, 0, 1, true).
		AddItem(serviceView, 40, 1, false)

	menu := tview.NewTextView().
		SetDynamicColors(true).
		SetRegions(true).
		SetWrap(false)
	menu.Write([]byte("F3 Search"))

	pages := tview.NewPages()
	pages.AddPage("flex", flex, true, true)

	menuPages := tview.NewPages()
	searchInput := tview.NewInputField()
	menuPages.AddPage("menu", menu, true, true)
	menuPages.AddPage("search", searchInput, true, false)

	searchInput.
		SetLabel("Search for: ").
		SetFieldWidth(40).
		SetAcceptanceFunc(nil).
		SetDoneFunc(func(key tcell.Key) {
			menuPages.HidePage("search")
		})

	app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		switch event.Key() {
		case tcell.KeyF3:
			menuPages.ShowPage("search")
			app.SetFocus(searchInput)
		}

		return event
	})

	// Create the main layout.
	layout := tview.NewFlex().
		SetDirection(tview.FlexRow).
		AddItem(pages, 0, 1, true).
		AddItem(menuPages, 1, 1, false)

	if err := app.SetRoot(layout, true).Run(); err != nil {
		panic(err)
	}
}
Originally created by @muesli on GitHub (Mar 24, 2018). Original GitHub issue: https://github.com/rivo/tview/issues/85 I'm experiencing a crash when calling pages.HidePage under certain circumstances. I've written and attached a little example that reproduces the issue, below. You can trigger the crash by starting the app, pressing F3 and then triggering the `DoneFunc` of `searchInput`. It will try to hide itself again, which causes a null-pointer access. See the backtrace below. Interestingly, I've used this in pretty much the same fashion before, without running into this issue (see https://github.com/muesli/service-tools/blob/master/service-monitor/logscmd.go), but can't figure out what I'm doing different here. Being a null-pointer access triggered inside tview, it probably can't harm to guard against it regardless. Backtrace: ``` github.com/rivo/tview.(*Application).Run.func1(0xc42010a2a0) /home/muesli/Sources/go/src/github.com/rivo/tview/application.go:96 +0x82 panic(0x576d00, 0x667f10) /usr/lib/go/src/runtime/panic.go:505 +0x229 github.com/rivo/tview.(*Pages).Focus(0xc42007c9f0, 0x0) /home/muesli/Sources/go/src/github.com/rivo/tview/pages.go:228 +0x88 github.com/rivo/tview.(*Pages).HidePage(0xc42007c9f0, 0x5a2e57, 0x6, 0xc420049c30) /home/muesli/Sources/go/src/github.com/rivo/tview/pages.go:142 +0xf3 main.main.func1(0xc40000000d) /home/muesli/Sources/go/src/github.com/muesli/service-tools/service-monitor/foo/foo.go:45 +0x3f github.com/rivo/tview.(*InputField).InputHandler.func1(0xc42029a8e0, 0xc42029c050) /home/muesli/Sources/go/src/github.com/rivo/tview/inputfield.go:324 +0x188 github.com/rivo/tview.(*Box).WrapInputHandler.func1(0xc42029a8e0, 0xc42029c050) /home/muesli/Sources/go/src/github.com/rivo/tview/box.go:149 +0x5d github.com/rivo/tview.(*Application).Run(0xc42010a2a0, 0x0, 0x0) /home/muesli/Sources/go/src/github.com/rivo/tview/application.go:154 +0x2f8 main.main() /home/muesli/Sources/go/src/github.com/muesli/service-tools/service-monitor/foo/foo.go:64 +0xe31 ``` Example: ``` package main import ( "github.com/gdamore/tcell" "github.com/rivo/tview" ) func main() { app := tview.NewApplication() list := tview.NewList() list. SetBorder(true) serviceView := tview.NewTextView() serviceView. SetDynamicColors(true). SetScrollable(true) serviceView. SetBorder(true) flex := tview.NewFlex(). AddItem(list, 0, 1, true). AddItem(serviceView, 40, 1, false) menu := tview.NewTextView(). SetDynamicColors(true). SetRegions(true). SetWrap(false) menu.Write([]byte("F3 Search")) pages := tview.NewPages() pages.AddPage("flex", flex, true, true) menuPages := tview.NewPages() searchInput := tview.NewInputField() menuPages.AddPage("menu", menu, true, true) menuPages.AddPage("search", searchInput, true, false) searchInput. SetLabel("Search for: "). SetFieldWidth(40). SetAcceptanceFunc(nil). SetDoneFunc(func(key tcell.Key) { menuPages.HidePage("search") }) app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { switch event.Key() { case tcell.KeyF3: menuPages.ShowPage("search") app.SetFocus(searchInput) } return event }) // Create the main layout. layout := tview.NewFlex(). SetDirection(tview.FlexRow). AddItem(pages, 0, 1, true). AddItem(menuPages, 1, 1, false) if err := app.SetRoot(layout, true).Run(); err != nil { panic(err) } } ```
kerem closed this issue 2026-03-04 01:01:37 +03:00
Author
Owner

@rivo commented on GitHub (Mar 25, 2018):

Right. I fixed this. Thanks.

<!-- gh-comment-id:375980902 --> @rivo commented on GitHub (Mar 25, 2018): Right. I fixed this. Thanks.
Author
Owner

@muesli commented on GitHub (Mar 26, 2018):

Thanks and confirmed as fixed! I did notice a related issue though: in the attached example, if you're now successfully hiding the InputField searchInput again, the cursor remains sitting there even though the field is correctly hidden. Note: In those cases where it didn't crash before your fix, no remaining cursor is visible.

It's a bit difficult to explain with words probably, but if you run the attached example again, just press F3 to show the search, then press ESC to hide it again, and you'll see.

<!-- gh-comment-id:376027967 --> @muesli commented on GitHub (Mar 26, 2018): Thanks and confirmed as fixed! I did notice a related issue though: in the attached example, if you're now successfully hiding the InputField `searchInput` again, the cursor remains sitting there even though the field is correctly hidden. Note: In those cases where it didn't crash before your fix, no remaining cursor is visible. It's a bit difficult to explain with words probably, but if you run the attached example again, just press F3 to show the search, then press ESC to hide it again, and you'll see.
Author
Owner

@rivo commented on GitHub (Mar 26, 2018):

Right. I noticed that. The cursor gets removed when the input field loses focus. However, in your example, it never does. (Simply keeping it from being drawn is not the same. There is no notification for a primitive that it was hidden.) In your SetDoneFunc() callback, you should also call app.SetFocus() to take the focus away from the input field. If you don't want any other primitive to have focus, you can provide nil.

I believe this should solve the cursor problem.

Btw, if you set the focus to the Pages primitive instead of the input field directly, then Pages will pass it on to the input field and, upon hiding it, call app.SetFocus() on the top-most visible child. This will also lead to the cursor being removed, without you having to call app.SetFocus() yourself. (Although I'm guessing that for your application, you will probably have to do it at some point anyway unless the input field is its only interactive element.)

<!-- gh-comment-id:376212547 --> @rivo commented on GitHub (Mar 26, 2018): Right. I noticed that. The cursor gets removed when the input field loses focus. However, in your example, it never does. (Simply keeping it from being drawn is not the same. There is no notification for a primitive that it was hidden.) In your `SetDoneFunc()` callback, you should also call `app.SetFocus()` to take the focus away from the input field. If you don't want any other primitive to have focus, you can provide `nil`. I believe this should solve the cursor problem. Btw, if you set the focus to the `Pages` primitive instead of the input field directly, then `Pages` will pass it on to the input field and, upon hiding it, call `app.SetFocus()` on the top-most visible child. This will also lead to the cursor being removed, without you having to call `app.SetFocus()` yourself. (Although I'm guessing that for your application, you will probably have to do it at some point anyway unless the input field is its only interactive element.)
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#65
No description provided.