Skip to content

(v2) Generic model#1298

Closed
aymanbagabas wants to merge 27 commits intov2-expfrom
v2-generics
Closed

(v2) Generic model#1298
aymanbagabas wants to merge 27 commits intov2-expfrom
v2-generics

Conversation

@aymanbagabas
Copy link
Member

@aymanbagabas aymanbagabas commented Jan 27, 2025

This proposal removes all program options and, instead, define them directly on the Program struct. This also changes the API and implement this proposal #1136

type Program[T any] struct {
	Input  io.Reader
	Output io.Writer
	Env    []string
	Init   func() (T, Cmd)
	Filter func(T, Msg) Msg
	Update func(T, Msg) (T, Cmd)
	View   func(T) Frame
	Model T

	// DontCatchPanics is a flag that determines whether or not the program should
	// catch panics.
	DontCatchPanics bool

	// IgnoreSignals is a flag that determines whether or not the program should
	// ignore signals.
	IgnoreSignals bool

	// FPS is the frames per second we should set on the renderer, if
	// applicable,
	FPS int

	// Profile is the color profile of the terminal. Use [Profile] to set it.
	Profile colorprofile.Profile // the terminal color profile
}

@aymanbagabas aymanbagabas force-pushed the v2-generics branch 2 times, most recently from e9aea7c to 95b0498 Compare January 27, 2025 17:48
@aymanbagabas aymanbagabas marked this pull request as ready for review January 27, 2025 20:02
@aymanbagabas aymanbagabas force-pushed the v2-generics branch 3 times, most recently from 1deae97 to a69f856 Compare January 28, 2025 21:47
aymanbagabas added a commit to charmbracelet/bubbles that referenced this pull request Jan 29, 2025
@aymanbagabas
Copy link
Member Author

Here's an example of rewriting the cursor-style example using this API

package main

import (
	"fmt"
	"os"

	tea "github.com/charmbracelet/bubbletea/v2"
)

type model struct {
	shape tea.CursorShape
	blink bool
}

func (m model) describeCursor() string {
	var adj, noun string

	if m.blink {
		adj = "blinking"
	} else {
		adj = "steady"
	}

	switch m.shape {
	case tea.CursorBlock:
		noun = "block"
	case tea.CursorUnderline:
		noun = "underline"
	case tea.CursorBar:
		noun = "bar"
	}

	return fmt.Sprintf("%s %s", adj, noun)
}

func initialModel() (model, tea.Cmd) {
	m := model{blink: true}
	return m, nil
}

func updateModel(m model, msg tea.Msg) (model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyPressMsg:
		switch msg.String() {
		case "ctrl+q", "q":
			return m, tea.Quit
		case "h", "left":
			if m.shape == tea.CursorBlock && m.blink {
				break
			}
			if m.blink {
				m.shape--
			}
			m.blink = !m.blink
		case "l", "right":
			if m.shape == tea.CursorBar && !m.blink {
				break
			}
			if !m.blink {
				m.shape++
			}
			m.blink = !m.blink
		}
	}
	return m, nil
}

func viewModel(m model) fmt.Stringer {
	f := tea.NewFrame("Press left/right to change the cursor style, q or ctrl+c to quit." +
		"\n\n" +
		"  <- This is the cursor (a " + m.describeCursor() + ")")
	f.Cursor = tea.NewCursor(0, 2)
	f.Cursor.Shape = m.shape
	f.Cursor.Blink = m.blink
	return f
}

func main() {
	p := tea.Program[model]{
		Init:   initialModel,
		Update: updateModel,
		View:   viewModel,
	}
	if err := p.Run(); err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v", err)
		os.Exit(1)
	}
}

aymanbagabas added a commit to charmbracelet/bubbles that referenced this pull request Jan 29, 2025
func NewProgram[T any](model Model[T]) *Program[T] {
p := new(Program[T])
p.Init = model.Init
p.Update = func(t T, msg Msg) (T, Cmd) { return any(t).(Model[T]).Update(msg) }
Copy link

@dolmen dolmen Mar 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line would me much simpler (and less runtime cost in every call to Update, View) if Model[T] was a struct instead of an interface. See #1136 (comment)

Copy link

@dolmen dolmen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NewProgram implementation wraps the Update and View functions (which are called frequently in the render loop) in a costly way. See my proposal for an alternate representation of Model[T] in #1136 (comment).

@meowgorithm
Copy link
Member

I still absolutely love this design. For now, however, in the spirit of keeping things tidy, I'm closing this PR.

@meowgorithm meowgorithm deleted the v2-generics branch September 30, 2025 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants