Bubble Tea Ruby

repository·main·Indexed 40 days ago

https://github.com/marcoroth/bubbletea-ruby

A Ruby implementation of the Bubble Tea framework for building interactive terminal user interfaces (TUIs) using The Elm Architecture. Version 0.1.4 provides the Bubbletea::Model for state management, Bubbletea::Command for side effects, and a Runner to manage the event loop and terminal rendering.

Tokens
4K
Snippets
16
Records
17
Agent score
100%

What's inside Bubble Tea Ruby

  1. Use Bubbletea::Command to trigger side effects

    main

    In the Bubble Tea architecture, Command objects are used to trigger side effects (like I/O, timers, or quitting) from within your update function. Instead of performing the action directly, you return a Command object, which the runtime then executes.

    You should use the convenience methods provided on the Bubbletea::Command class to instantiate these objects.

    # Example of returning a command in an update loop
    # (Conceptual usage)
    def update(msg)
      case msg
      when KeyDown
        Bubbletea::Command.quit
      end
    end
  2. Implement the Elm Architecture using Bubbletea::Model

    main

    To create a Bubbletea application, include the Bubbletea::Model module in your model class. Your class must implement three core methods that follow the Elm Architecture pattern:

    1. init: Returns an array [model, command]. This defines the initial state and an optional initial command to run.
    2. update(message): Returns an array [model, command]. This handles incoming messages (like key presses), updates the internal state, and optionally returns a command.
    3. view: Returns a String. This represents the current visual state of the application to be rendered in the terminal.

    When implementing update, you typically use a case statement to handle different types of messages, such as Bubbletea::KeyMessage.

    class Counter
      include Bubbletea::Model
    
      attr_reader :count
    
      def initialize
        @count = 0
      end
    
      def init
        [self, nil]
      end
    
      def update(message)
        case message
        when Bubbletea::KeyMessage
          case message.to_s
          when "q", "ctrl+c"
            [self, Bubbletea.quit]
          when "up", "k"
            @count += 1
            [self, nil]
          when "down", "j"
            @count -= 1
            [self, nil]
          else
            [self, nil]
          end
        else
          [self, nil]
        end
      end
    
      def view
        "Count: #{@count}\n\nPress up/down to change, q to quit"
      end
    end
  3. Install and use the Bubbletea gem

    main

    The bubbletea gem provides a Ruby implementation of the Bubble Tea framework (based on The Elm Architecture) for building interactive terminal applications. It automatically selects the appropriate backend based on your Ruby version for optimized performance.

    To use it in your project, require the main module:

    require 'bubbletea'
  4. Initialize and run a Bubbletea application

    main

    To start a TUI application, use Bubbletea.run(model, **options). This method initializes a Runner instance with your model and starts the event loop, managing terminal setup, rendering, and message processing.

    Your model must implement the standard Elm Architecture interface: init, update(message), and view.

    # Assuming your model implements init, update, and view
    Bubbletea.run(MyModel.new)
  5. Configure Runner options

    main

    When calling Bubbletea.run or initializing Bubbletea::Runner.new, you can pass several options to customize the terminal behavior and event loop:

    OptionTypeDefaultDescription
    alt_screenBooleanfalseWhether to use the terminal's alternate screen buffer
    mouse_cell_motionBooleanfalseEnables mouse cell motion
    mouse_all_motionBooleanfalseEnables all mouse motion
    bracketed_pasteBooleanfalseEnables bracketed paste mode
    report_focusBooleanfalseEnables reporting of terminal focus
    fpsInteger60Frames per second for the rendering loop
    input_timeoutInteger10Timeout for polling input events
    without_rendererBooleanfalseIf true, skips creating a renderer (useful for headless/testing)
    Bubbletea.run(MyModel.new, alt_screen: true, fps: 30, mouse_all_motion: true)
  6. Handle KeyMessage for keyboard input

    main

    The KeyMessage class is used to represent keyboard events in the update loop. You can inspect the message to determine which key was pressed using predicate methods like .enter?, .esc?, .up?, etc., or by checking the .runes for character input.

    Common key constants include:

    • KeyMessage::KEY_ENTER
    • KeyMessage::KEY_ESC
    • KeyMessage::KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT
    • KeyMessage::KEY_BACKSPACE
    • KeyMessage::KEY_SPACE
    • KeyMessage::KEY_RUNES (for character input)

    For character input, use the .char method to retrieve the string representation of the pressed runes.

    # Example of handling a KeyMessage in an update loop
    case msg
    when KeyMessage
      if msg.enter?
        # Handle Enter key
      elsif msg.up?
        # Handle Up arrow
      elsif msg.runes?
        puts "Typed: #{msg.char}"
      end
    end
  7. Send messages to the running model

    main

    While the Runner manages the loop, you can manually inject messages into the processing queue using the send(message) method on a Runner instance. This is useful if you have external asynchronous processes that need to trigger an update in your TUI model.

    runner = Bubbletea::Runner.new(MyModel.new)
    # ... in some other context ...
    runner.send(MyCustomMessage.new)
    runner.run
  8. Parse events using Bubbletea.parse_event

    main

    Use Bubbletea.parse_event(hash) to convert a raw event hash (typically from a Go-to-Ruby bridge) into a concrete Message object. The hash must contain a "type" key.

    Supported types:

    • "key": Returns a KeyMessage.
    • "mouse": Returns a MouseMessage.
    • "focus": Returns a FocusMessage.
    • "blur": Returns a BlurMessage.
    # Example parsing a key event hash
    event_hash = {
      "type" => "key",
      "key_type" => 13,
      "alt" => false
    }
    
    msg = Bubbletea.parse_event(event_hash)
    puts msg.class # => KeyMessage
    puts msg.enter? # => true
  9. Execute external tasks with ExecCommand

    main

    Use Bubbletea::Command.exec(callable, message: nil) to run a Ruby callable (such as a Proc or a lambda) as a side effect. This is useful for long-running tasks or I/O operations that shouldn't block the main update loop. If a message is provided, it will be sent back to the update function once the callable finishes execution.

    # Running a proc that returns a message
    Bubbletea::Command.exec(-> { 
      # Perform heavy work here
      "Result of work"
    }, message: :work_completed)
  10. Handle MouseMessage for mouse events

    main

    The MouseMessage class represents mouse interactions. It provides coordinates, button information, and action types.

    Actions:

    • MouseMessage::ACTION_PRESS
    • MouseMessage::ACTION_RELEASE
    • MouseMessage::ACTION_MOTION

    Buttons:

    • MouseMessage::BUTTON_LEFT
    • MouseMessage::BUTTON_MIDDLE
    • MouseMessage::BUTTON_RIGHT
    • MouseMessage::BUTTON_WHEEL_UP
    • MouseMessage::BUTTON_WHEEL_DOWN

    Predicate Methods:

    • .press?, .release?, .motion?
    • .left?, .right?, .middle?
    • .wheel? (returns true if the button is a scroll wheel action)

    Attributes available via attr_reader: x, y, button, action, shift, alt, ctrl.

    case msg
    when MouseMessage
      if msg.press? && msg.left?
        puts "Clicked at #{msg.x}, #{msg.y}"
      elsif msg.wheel?
        puts "Scrolled!"
      end
    end
  11. Common Bubbletea::Command convenience methods

    main

    The Bubbletea::Command class provides several factory methods to create common side-effect commands:

    • Bubbletea::Command.quit: Returns a QuitCommand to terminate the application.
    • Bubbletea::Command.batch(*commands): Returns a BatchCommand to run multiple commands concurrently. It flattens and compacts the input array.
    • Bubbletea::Command.sequence(*commands): Returns a SequenceCommand to run multiple commands in order.
    • Bubbletea::Command.tick(duration, &block): Returns a TickCommand that triggers a callback after the specified duration.
    • Bubbletea::Command.send_message(message, delay: 0): Returns a SendMessage command to inject a message into the update loop after an optional delay.
    • Bubbletea::Command.enter_alt_screen: Returns an EnterAltScreenCommand to switch to the terminal's alternate screen buffer.
    • Bubbletea::Command.exit_alt_screen: Returns an ExitAltScreenCommand to return to the main terminal buffer.
    • Bubbletea::Command.set_window_title(title): Returns a SetWindowTitleCommand to update the terminal window title.
    • Bubbletea::Command.puts(text): Returns a PutsCommand to print text to the standard output.
    • Bubbletea::Command.suspend: Returns a SuspendCommand to pause the application.
    • Bubbletea::Command.exec(callable, message: nil): Returns an ExecCommand to run a callable (e.g., a Proc or object responding to .call), optionally returning a message upon completion.
    • Bubbletea::Command.none: Returns nil, used when no side effect is required.
    # Batching multiple commands
    Bubbletea::Command.batch(
      Bubbletea::Command.set_window_title("My App"),
      Bubbletea::Command.enter_alt_screen
    )
    
    # Running a background task
    Bubbletea::Command.exec(-> { sleep 1; "Done" }, message: :task_finished)