Skip to content

[v2] tea.Println output gets overwritten by the new model's View() when switching models #1613

@jmwielandt

Description

@jmwielandt

Describe the bug
I'm trying to make a generic and recursive menu navigation model.

I want to add some kind of logs with tea.Println.

When I'm in a menu, generate a specific println call and go back, the new view is printed on top of the latest println text instead of below it.

Step by step: Menu 0 -> Menu 1 -> is empty, enter twice, go back, latest println gets printed on top.

Image Image Image Image Image

Setup
Please complete the following information along with version numbers, if applicable.

  • OS: Ubuntu 24.04
  • Shell: Bash
  • Terminal Emulator: gnome terminal
  • Terminal Multiplexer: none.

To Reproduce
Steps to reproduce the behavior:

  1. Run the code below
  2. Right arrow key
  3. Right arrow key
  4. Right arrow key
  5. Left arrow key
  6. See error

Source Code

Click to see the code ---
package main

import (
	"fmt"
	"os"

	tea "charm.land/bubbletea/v2"
)

func main() {
	p := tea.NewProgram(initMainMenu())
	_, err := p.Run()
	if err != nil {
		fmt.Printf("there was an error: %v\n", err)
		os.Exit(1)
	}
}

func initMainMenu() tea.Model {
	return *NewCharmingMap2("menu 0", []CharmingOption2{
		{Option: "menu 1", Result: initMenu1},
		{Option: "menu 2", Result: initMenu2},
	}, "press q to exit", nil)
}

func initMenu1() tea.Model {
	return *NewCharmingMap2("menu 1", nil, "press q to exit, <- to go back", initMainMenu)
}

func initMenu2() tea.Model {
	return *NewCharmingMap2("menu 2", []CharmingOption2{
		{Option: "menu 3", Result: initMenu3},
	}, "press q to exit, <- to go back", initMainMenu)
}

func initMenu3() tea.Model {
	return *NewCharmingMap2("menu 3", nil, "press q to exit, <- to go back", initMenu2)
}

type CharmingOption2 struct {
	Option string
	Result func() tea.Model
}

func NewCharm2(option string, result func() tea.Model) CharmingOption2 {
	return CharmingOption2{option, result}
}

type CharmingMap2 struct {
	title   string
	footer  string
	options []string
	cursor  int
	results []func() tea.Model
	back    func() tea.Model
}

func NewCharmingMap2(title string, options []CharmingOption2, footer string, back func() tea.Model) *CharmingMap2 {
	optionTexts := []string{}
	results := []func() tea.Model{}

	for _, option := range options {
		optionTexts = append(optionTexts, option.Option)
		results = append(results, option.Result)
	}

	return &CharmingMap2{
		title:   title,
		footer:  footer,
		options: optionTexts,
		cursor:  0,
		results: results,
		back:    back,
	}
}

// ----- TUI functions -----

func (m CharmingMap2) Init() tea.Cmd {
	return nil
}

func (m CharmingMap2) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyPressMsg:
		switch msg.String() {
		case "ctrl+c", "q":
			return m, tea.Quit
		case "up", "k":
			if len(m.options) > 0 {
				if m.cursor > 0 {
					m.cursor--
				} else {
					m.cursor = len(m.options) - 1
				}
			}
		case "down", "j":
			if len(m.options) > 0 {
				if m.cursor < len(m.options)-1 {
					m.cursor++
				} else {
					m.cursor = 0
				}
			}
		case "enter", "space", "right":
			if m.cursor < len(m.results) {
				if m.results[m.cursor] != nil {
					return m.results[m.cursor](), nil
				} else {
					// doesn't get deleted
					return m, tea.Println("no result defined, i'm in " + m.title)
				}
			} else {
				// gets deleted by new view render
				return m, tea.Println("no childs found, i'm in " + m.title)
			}
		case "backspace", "left":
			if m.back != nil {
				return m.back(), nil
			}
		}
	}
	return m, nil
}

func (m CharmingMap2) View() tea.View {
	s := m.title
	s += "\n\n"

	// draw options
	for i, option := range m.options {
		cursor := " "
		if m.cursor == i {
			cursor = ">"
		}

		s += fmt.Sprintf("  %s %s\n", cursor, option)
	}

	// the footer
	s += "\n" + m.footer + "\n"

	return tea.NewView(s)
}

Expected behavior

New view is printed below the latest println line.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions