Skip to main content

Crate bubbletea

Crate bubbletea 

Source
Expand description

§Bubbletea

A powerful TUI (Terminal User Interface) framework based on The Elm Architecture.

Bubbletea provides a functional approach to building terminal applications with:

  • A simple Model-Update-View architecture
  • Command-based side effects
  • Type-safe messages with downcasting
  • Full keyboard and mouse support
  • Frame-rate limited rendering (60 FPS default)

§Role in charmed_rust

Bubbletea is the core runtime and event loop for the entire ecosystem:

  • bubbles builds reusable widgets on top of the Model/Msg/Cmd pattern.
  • huh composes form flows using bubbletea models.
  • wish serves bubbletea programs over SSH.
  • glow uses bubbletea for pager-style Markdown viewing.
  • demo_showcase is the flagship multi-page bubbletea app.

§The Elm Architecture

Bubbletea follows the Elm Architecture pattern:

  • Model: Your application state
  • Update: A pure function that processes messages and returns commands
  • View: A pure function that renders state to a string
  • Cmd: Lazy IO operations that produce messages

§Quick Start

use bubbletea::{Program, Model, Message, Cmd, KeyMsg, KeyType};

struct Counter {
    count: i32,
}

struct IncrementMsg;
struct DecrementMsg;

impl Model for Counter {
    fn init(&self) -> Option<Cmd> {
        None
    }

    fn update(&mut self, msg: Message) -> Option<Cmd> {
        if msg.is::<IncrementMsg>() {
            self.count += 1;
        } else if msg.is::<DecrementMsg>() {
            self.count -= 1;
        } else if let Some(key) = msg.downcast_ref::<KeyMsg>() {
            match key.key_type {
                KeyType::CtrlC | KeyType::Esc => return Some(bubbletea::quit()),
                KeyType::Runes if key.runes == vec!['q'] => return Some(bubbletea::quit()),
                _ => {}
            }
        }
        None
    }

    fn view(&self) -> String {
        format!(
            "Count: {}\n\nPress +/- to change, q to quit",
            self.count
        )
    }
}

fn main() -> Result<(), bubbletea::Error> {
    let model = Counter { count: 0 };
    let final_model = Program::new(model)
        .with_alt_screen()
        .run()?;
    println!("Final count: {}", final_model.count);
    Ok(())
}

§Messages

Messages are type-erased using Message. You can create custom message types and downcast them in your update function:

use bubbletea::Message;

struct MyCustomMsg { value: i32 }

let msg = Message::new(MyCustomMsg { value: 42 });

// Check type
if msg.is::<MyCustomMsg>() {
    // Downcast to access
    if let Some(custom) = msg.downcast::<MyCustomMsg>() {
        assert_eq!(custom.value, 42);
    }
}

§Commands

Commands are lazy IO operations that produce messages:

use bubbletea::{Cmd, Message, batch, sequence};
use std::time::Duration;

// Simple command
let cmd = Cmd::new(|| Message::new("done"));

// Batch commands (run concurrently)
let cmds = batch(vec![
    Some(Cmd::new(|| Message::new(1))),
    Some(Cmd::new(|| Message::new(2))),
]);

// Sequence commands (run in order)
let cmds = sequence(vec![
    Some(Cmd::new(|| Message::new(1))),
    Some(Cmd::new(|| Message::new(2))),
]);

§Keyboard Input

Keyboard events are delivered as KeyMsg:

use bubbletea::{KeyMsg, KeyType, Message};

fn handle_key(msg: Message) {
    if let Some(key) = msg.downcast_ref::<KeyMsg>() {
        match key.key_type {
            KeyType::Enter => println!("Enter pressed"),
            KeyType::CtrlC => println!("Ctrl+C pressed"),
            KeyType::Runes => println!("Typed: {:?}", key.runes),
            _ => {}
        }
    }
}

§Mouse Input

Enable mouse tracking with with_mouse_cell_motion() or with_mouse_all_motion():

use bubbletea::{Program, MouseMsg, MouseButton, MouseAction};

let program = Program::new(model)
    .with_mouse_cell_motion()  // Track clicks and drags
    .run()?;

// In update:
if let Some(mouse) = msg.downcast_ref::<MouseMsg>() {
    if mouse.button == MouseButton::Left && mouse.action == MouseAction::Press {
        println!("Click at ({}, {})", mouse.x, mouse.y);
    }
}

§Screen Control

Control terminal features with screen commands:

use bubbletea::screen;

// In update, return a command:
let cmd = screen::enter_alt_screen();
let cmd = screen::hide_cursor();
let cmd = screen::enable_mouse_cell_motion();

Re-exports§

pub use command::Cmd;
pub use command::batch;
pub use command::every;
pub use command::printf;
pub use command::println;
pub use command::quit;
pub use command::sequence;
pub use command::set_window_title;
pub use command::tick;
pub use command::window_size;
pub use key::KeyMsg;
pub use key::KeyType;
pub use key::parse_sequence;
pub use key::parse_sequence_prefix;
pub use message::BlurMsg;
pub use message::FocusMsg;
pub use message::InterruptMsg;
pub use message::Message;
pub use message::QuitMsg;
pub use message::ResumeMsg;
pub use message::SuspendMsg;
pub use message::WindowSizeMsg;
pub use mouse::MouseAction;
pub use mouse::MouseButton;
pub use mouse::MouseMsg;
pub use mouse::parse_mouse_event_sequence;
pub use program::Error;
pub use program::Model;
pub use program::Program;
pub use program::ProgramHandle;
pub use program::ProgramOptions;
pub use program::Result;

Modules§

command
Commands for side effects.
key
Keyboard input handling.
message
Message types for the Elm Architecture.
mouse
Mouse input handling.
prelude
Prelude module for convenient imports.
program
Program lifecycle and event loop.
screen
Screen control commands.
simulator
Program simulator for testing lifecycle without a real terminal.