Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(v2) Bubble Tea v2 #1118

Draft
wants to merge 330 commits into
base: main
Choose a base branch
from
Draft

(v2) Bubble Tea v2 #1118

wants to merge 330 commits into from

Conversation

aymanbagabas
Copy link
Member

@aymanbagabas aymanbagabas commented Aug 28, 2024

This PR will keep track of the changes upcoming in Bubble Tea v2

  • Input sequence parser
  • Kitty keyboard
  • Xterm modifyOtherKeys
  • Mode 2027 (grapheme clustering)
  • Setting/getting terminal background/foreground/cursor colors
  • Setting/getting the clipboard using OSC52
  • Make Init() return the model
  • New Key/Mouse API (v2) Use KeyMsg/MouseMsg interfaces #1111

aymanbagabas and others added 13 commits August 21, 2024 16:03
This adds the ability to use a custom renderer using a modified version
of the existing `renderer` interface. This also keeps track of enabled
capabilities and ensure they are turned off on program exit. It only
turns off enabled capabilities rather.

The new API looks like:

```go
// Renderer is the interface for Bubble Tea renderers.
type Renderer interface {
	// Close closes the renderer and flushes any remaining data.
	Close() error

	// Render renders a frame to the output.
	Render(string) error

	// SetOutput sets the output for the renderer.
	SetOutput(io.Writer)

	// Flush flushes the renderer's buffer to the output.
	Flush() error

	// InsertAbove inserts lines above the current frame. This only works in
	// inline mode.
	InsertAbove(string) error

	// Resize sets the size of the terminal.
	Resize(w int, h int)

	// Request a full re-render. Note that this will not trigger a render
	// immediately. Rather, this method causes the next render to be a full
	// Repaint. Because of this, it's safe to call this method multiple times
	// in succession.
	Repaint()

	// ClearScreen clear the terminal screen. This should always have the same
	// behavior as the "clear" command which is equivalent to `CSI 2 J` and
	// `CSI H`.
	ClearScreen()

	// SetMode sets a terminal mode on/off. The mode argument is an int
	// consisting of the mode identifier.
	// For example, to set alt-screen mode, you would call SetMode(1049, true).
	SetMode(mode int, on bool)

	// Mode returns whether the render has a mode enabled.
	// For example, to check if alt-screen mode is enabled, you would call
	// Mode(1049).
	Mode(mode int) bool
}
```

--- 

This also introduces some performance gains by not writing unnecessary
sequences on enable/disable terminal modes. For example, we always hide
the cursor when the program runs, it doesn't make sense to write the
sequence again when the model sends a `HideCursor` command, we already
know it's hidden. Don't _always_ disable mouse if it wasn't enabled in
the first place.
…#1099)

Bumps [github.com/charmbracelet/x/ansi](https://github.com/charmbracelet/x) from 0.2.1 to 0.2.2.
- [Release notes](https://github.com/charmbracelet/x/releases)
- [Commits](charmbracelet/x@ansi/v0.2.1...ansi/v0.2.2)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/x/ansi
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Hack!

This forces a bg/fg query on the default os.Stdout and os.Stdin before
starting any bubbletea program. Otherwise, programs might hang before
the query finishes. This is because at that point, the bubbletea already
acquired the terminal os.Stdin and termenv cannot read from it. The
termenv query timeout can be adjusted using `termenv.OSCTimeout`.

Note that this will only work for the default IO i.e. os.Stdout and
os.Stdin.

docs: copyedits in Lip Gloss/Termenv workaround for clarity

Co-authored-by: Christian Rocha <[email protected]>
* feat: add mode 2027 grapheme clustering stubs

This adds the necessary stubs to make mode 2027 (grapheme clustering)
work on Bubble Tea with renderers that support it.

* fix: screen grapheme clustering tests
@aymanbagabas aymanbagabas changed the title V2 exp (v2) Bubble Tea API Aug 28, 2024
@aymanbagabas aymanbagabas added this to the v2.0.0 milestone Aug 29, 2024
Use key and mouse interfaces to catch their respective types as an
interface.
This replaces KeyMsg and MouseMsg with interfaces that catch their
respective types. For example, both KeyPressMsg and KeyReleaseMsg
implement KeyMsg. Same goes for MouseClickMsg, MouseReleaseMsg, etc,
they all implement MouseMsg. This makes it possible to switch on the
interface type to catch all events of the same input message type.

New API:

```go

// KeyMsg represents a key event. This can be either a key press or a key
// release event.
type KeyMsg interface {
	fmt.Stringer

	// Key returns the underlying key event.
	Key() Key
}


// Key represents a Key press or release event. It contains information about
// the Key pressed, like the runes, the type of Key, and the modifiers pressed.
// There are a couple general patterns you could use to check for key presses
// or releases:
//
//	// Switch on the string representation of the key (shorter)
//	switch msg := msg.(type) {
//	case KeyPressMsg:
//	    switch msg.String() {
//	    case "enter":
//	        fmt.Println("you pressed enter!")
//	    case "a":
//	        fmt.Println("you pressed a!")
//	    }
//	}
//
//	// Switch on the key type (more foolproof)
//	switch msg := msg.(type) {
//	case KeyMsg:
//	    // catch both KeyPressMsg and KeyReleaseMsg
//	    switch key := msg.Key(); key.Code {
//	    case KeyEnter:
//	        fmt.Println("you pressed enter!")
//	    default:
//	        switch key.Text {
//	        case "a":
//	            fmt.Println("you pressed a!")
//	        }
//	    }
//	}
//
// Note that [Key.Text] will be empty for special keys like [KeyEnter],
// [KeyTab], and for keys that don't represent printable characters like key
// combos with modifier keys. In other words, [Key.Text] is populated only for
// keys that represent printable characters shifted or unshifted (like 'a',
// 'A', '1', '!', etc.).
type Key struct {
	// Text contains the actual characters received. This usually the same as
	// [Key.Code]. When [Key.Text] is non-empty, it indicates that the key
	// pressed represents printable character(s).
	Text string

	// Mod represents modifier keys, like [ModCtrl], [ModAlt], and so on.
	Mod KeyMod

	// Code represents the key pressed. This is usually a special key like
	// [KeyTab], [KeyEnter], [KeyF1], or a printable character like 'a'.
	Code rune

	// ShiftedCode is the actual, shifted key pressed by the user. For example,
	// if the user presses shift+a, or caps lock is on, [Key.ShiftedCode] will
	// be 'A' and [Key.Code] will be 'a'.
	//
	// In the case of non-latin keyboards, like Arabic, [Key.ShiftedCode] is the
	// unshifted key on the keyboard.
	//
	// This is only available with the Kitty Keyboard Protocol or the Windows
	// Console API.
	ShiftedCode rune

	// BaseCode is the key pressed according to the standard PC-101 key layout.
	// On international keyboards, this is the key that would be pressed if the
	// keyboard was set to US PC-101 layout.
	//
	// For example, if the user presses 'q' on a French AZERTY keyboard,
	// [Key.BaseCode] will be 'q'.
	//
	// This is only available with the Kitty Keyboard Protocol or the Windows
	// Console API.
	BaseCode rune

	// IsRepeat indicates whether the key is being held down and sending events
	// repeatedly.
	//
	// This is only available with the Kitty Keyboard Protocol or the Windows
	// Console API.
	IsRepeat bool
}


// MouseMsg represents a mouse message. This is a generic mouse message that
// can represent any kind of mouse event.
type MouseMsg interface {
	fmt.Stringer

	// Mouse returns the underlying mouse event.
	Mouse() Mouse
}

// Mouse represents a Mouse message. Use [MouseMsg] to represent all mouse
// messages.
//
// The X and Y coordinates are zero-based, with (0,0) being the upper left
// corner of the terminal.
//
//	// Catch all mouse events
//	switch msg := msg.(type) {
//	case MouseMsg:
//	    m := msg.Mouse()
//	    fmt.Println("Mouse event:", m.X, m.Y, m)
//	}
//
//	// Only catch mouse click events
//	switch msg := msg.(type) {
//	case MouseClickMsg:
//	    fmt.Println("Mouse click event:", msg.X, msg.Y, msg)
//	}
type Mouse struct {
	X, Y   int
	Button MouseButton
	Mod    KeyMod
}

```

TODO: update examples (in a separate PR)
This exports the following modes:
- Kitty keyboard commands and options
- Xterm modifyOtherKeys commands and options
- Win32Input commands and options
aymanbagabas and others added 27 commits January 13, 2025 16:47
Signed-off-by: Carlos Alexandro Becker <[email protected]>
This implements the new cursed ferocious renderer. This is based on the
new `cellbuf.Screen` renderer. This renderer is more efficient and
flexible than the previous renderer.

The renderer controls the altscreen buffer and cursor visibility which
means we don't need to do so in Bubble Tea. Instead, Bubble Tea can
focus on the event loop and the application state.

This also removes the experimental flags and changes the renderer
interface to be more explicit about the methods that are available.
Signed-off-by: Carlos Alexandro Becker <[email protected]>
…sted and active enhancements (#1286)

refactor: keyboard: expose type and track requested and active
enhancements

This change exposes the `KeyboardEnhancements` type and tracks requested
and active enhancements in the `Program` struct. This change also
renames the `KeyboardEnhancement` type to `KeyboardEnhancementOption`
as well as`EnableKeyboardEnhancements` to `RequestKeyboardEnhancements`.



Fixes: #1283
Send the environment variables used by the program to the program when it
starts. This is done by sending an `EnvMsg` message to the program.
Programs that need to access the environment variables can use this
message type to get the values of the environment variables.

This is useful when programs targeting remote sessions need to access
environment variables that are set by the user or the shell.
…#1288)

This adds a new message type, `RawMsg`, and a new command, `Raw`, that
allows printing raw strings to the terminal without any intermediate
processing.
This is ported from Textualize splash.py example.
* feat(examples): add space example to measure fps and performance

This is a simple example that uses Lip Gloss and Bubble Tea to render a
moving color gradient.

* docs(example): add attribution to Orhun Parmaksız in space example

* chore: remove fps stuff
* feat(examples): add doom-fire example

* feat: add elapsed time to doom-fire example
This commit introduces a new `Frame` type that represents a single frame of
the program's output. The `Frame` type contains the frame's content and cursor
settings. The cursor settings include the cursor's position, style, blink, and
visibility.

It also removes the cursor commands from the `tea` package. The cursor
commands are now part of the `Frame` type. The cursor commands were removed
because they were not idiomatic and were not flexible enough to support
additional cursor settings.

Supersedes: #1293
This commit introduces a new `Frame` type that represents a single frame
of the program's output. The `Frame` type contains the frame's content
and cursor settings. The cursor settings include the cursor's position,
style, blink, and visibility.

It also removes the cursor commands from the `tea` package. The cursor
commands are now part of the `Frame` type. The cursor commands were
removed because they were not idiomatic and were not flexible enough to
support additional cursor settings.

API:
```go
// Model contains the program's state as well as its core functions.
type Model interface {
	// Init is the first function that will be called. It returns an optional
	// initial command. To not perform an initial command return nil.
	Init() (Model, Cmd)

	// Update is called when a message is received. Use it to inspect messages
	// and, in response, update the model and/or send a command.
	Update(Msg) (Model, Cmd)

	// View renders the program's UI, which is just a [fmt.Stringer]. The view
	// is rendered after every Update.
	// The main model can return a [Frame] to set the cursor position and
	// style.
	View() fmt.Stringer
}

// Cursor represents a cursor on the terminal screen.
type Cursor struct {
	// Position is a [Position] that determines the cursor's position on the
	// screen relative to the top left corner of the frame.
	Position Position

	// Color is a [color.Color] that determines the cursor's color.
	Color color.Color

	// Shape is a [CursorShape] that determines the cursor's style.
	Shape CursorShape

	// Blink is a boolean that determines whether the cursor should blink.
	Blink bool
}

// NewCursor returns a new cursor with the default settings and the given
// position.
func NewCursor(x, y int) *Cursor {
	return &Cursor{
		Position: Position{X: x, Y: y},
		Color:    nil,
		Shape:    CursorBlock,
		Blink:    true,
	}
}

// Frame represents a single frame of the program's output.
type Frame struct {
	// Content contains the frame's content. This is the only required field.
	// It should be a string of text and ANSI escape codes.
	Content string

	// Cursor contains cursor settings for the frame. If nil, the cursor will
	// be hidden.
	Cursor *Cursor
}

// NewFrame creates a new frame with the given content.
func NewFrame(content string) Frame {
	return Frame{Content: content}
}

// String implements the fmt.Stringer interface for [Frame].
func (f Frame) String() string {
	return f.Content
}

```

Supersedes: #1293
@aymanbagabas aymanbagabas changed the title (v2) Bubble Tea API (v2) Bubble Tea v2 Jan 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants