charmbracelet / bubbletea

A powerful little TUI framework 🏗
MIT License
27.48k stars 794 forks source link

Init() method of model never get's called #725

Closed drenkmann closed 1 year ago

drenkmann commented 1 year ago

Describe the bug For some reason, Init() method of model never get's called. I tried adding os.Exit(1) in the Init function and the program doesn't exit. This happens when the new model gets returned within Update, creating it directly from main() and running it using program.Run() works perfectly fine.

Additional info: I am using the bubbles spinner. It should start spinning, as its Tick message get's returned within Init(). It doesn't start spinning, as Init() never get's called. When creating the model, I am returning it together with nil as the tea.Cmd.

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

To Reproduce Steps to reproduce the behavior:

  1. Create the new model to be returned
  2. Return it from within Update()
  3. Init() doesn't happen

Source Code This is the model (I only included the important stuff)

// The model
type singleCommandModel struct {
    spinner    spinner.Model
    successMsg string
    ch         chan cmdOut
}

// Not important here, but referenced in createSingleCommandModel
type cmdOut struct {
    err  error
    buff []byte
}

func createSingleCommandModel(cmd *exec.Cmd, msg string) singleCommandModel {
    // Create the spinner
    s := spinner.New()
    s.Spinner = spinner.MiniDot
    s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))

    // Stuff to do with the *exec.Cmd
    ch := make(chan cmdOut)

    go func() {
        var outbuff bytes.Buffer
        cmd.Stdout = &outbuff
        cmd.Stderr = &outbuff

        err := cmd.Run()

        ch <- cmdOut{err: err, buff: outbuff.Bytes()}
    }()

    // Create model with configured vars
    return singleCommandModel{
        spinner:    s,
        successMsg: msg,
        ch:         ch,
    }
}

// Never get's called
func (m singleCommandModel) Init() tea.Cmd {
    return m.spinner.Tick
}

Which get's created from here (With an actual exec.Command):

return createSingleCommandModel(exec.Command(""), "Command succeeded!"), nil

It always get's returned together with nil, which I think might be the problem?

Expected behavior Init() should get called and the spinner should start spinning.

Additional context I am creating the issue here and not in the bubbles repo because I feel like Init() not happening has more to do with bubbletea than it does with bubbles. Also sorry if this has been answered or is somewhere within the documentation, I just couldn't find anything so I'm not sure if it's a bug or if returning nil is the problem.

eolso commented 1 year ago

Returning nil is the problem. tea.Program.Run() calls the Init() function of the model for you.

    // Initialize the program.
    model := p.initialModel
    if initCmd := model.Init(); initCmd != nil {
    ...
    }

If you want to emulate that behavior you just need to return the Init() function as the tea.Cmd return val.

commandModel := createSingleCommandModel(exec.Command(""), "Command succeeded!")
return commandModel, commandModel.Init()

I agree that it's a little misleading since the Model interface has the comment

    // Init is the first function that will be called. It returns an optional
    // initial command. To not perform an initial command return nil.
    Init() Cmd

but luckily the fix is pretty easy!

drenkmann commented 1 year ago

-_-

The comments really are quite misleading. There isn't really any documentation, so I just read the comments in the source code and didn't realize that Init() doesn't get called anytime you return a new model. Thanks for letting me know!