How Does Rustlings Handle Terminal Resizing? A Deep Dive into the Watch Mode UI

Rustlings handles terminal resizing through a three-component event system that captures resize events via crossterm, dispatches them through a channel-based watch loop, and re-renders the UI with updated dimensions in real-time.

When running in watch mode, the Rustlings CLI tool must maintain a responsive terminal interface that adapts instantly to window resizing. According to the rust-lang/rustlings source code, this is achieved through a coordinated architecture involving low-level event capture, state management, and immediate UI re-rendering.

The Three-Component Architecture for Terminal Resize Handling

Rustlings implements terminal resize handling through three specialized components that work together to ensure the UI remains consistent regardless of terminal dimensions.

terminal_event_handler: Capturing Low-Level Events

Located in src/watch/terminal_event.rs, the terminal_event_handler function serves as the low-level event listener. It utilizes the crossterm crate to poll for terminal events in a dedicated loop. When crossterm generates an Event::Resize(width, _) event, the handler immediately packages this width value into a WatchEvent::TerminalResize enum variant and transmits it through an asynchronous channel to the main watch loop.

The Watch Loop: Dispatching Resize Events

The central dispatcher resides in src/watch.rs within the run_watch function. This main event loop continuously receives WatchEvent variants from the channel. Upon matching WatchEvent::TerminalResize { width }, the loop immediately forwards the new width value to the state management component. This architecture ensures that resize events never block the UI thread and are processed with minimal latency.

WatchState: Updating Dimensions and Re-rendering

The state management logic lives in src/watch/state.rs within the WatchState struct. The update_term_width method receives the new width parameter and performs three critical operations:

  1. Validation: Compares the incoming width against the current self.term_width value
  2. State Update: If changed, updates the stored term_width field with the new value
  3. Re-rendering: Immediately calls self.render(stdout) to redraw the entire UI using the updated dimensions

This ensures that all layout calculations—including the dynamic progress bar rendered by term::progress_bar in src/term.rs—utilize the current terminal width.

The Terminal Resize Handling Flow in Rustlings

The complete resize handling process follows this precise sequence:

  1. User resizes terminal → The operating system generates a SIGWINCH signal
  2. crossterm captures event → Event::Resize(width, height) is generated and polled by terminal_event_handler
  3. Event packaging → The handler creates WatchEvent::TerminalResize { width } and sends it through the channel
  4. Main loop dispatch → run_watch receives the event and calls watch_state.update_term_width(width, stdout)
  5. State validation and update → WatchState compares and updates self.term_width if necessary
  6. UI re-rendering → render() redraws the progress bar and exercise list using the new width constraints

Because rendering occurs immediately upon resize detection, the UI adapts instantly without requiring a program restart.

Code Implementation Examples

Listening for Resize Events

This minimal example demonstrates how Rustlings captures terminal resize events using crossterm:

use crossterm::event::{self, Event};
use std::sync::mpsc::Sender;

fn terminal_event_handler(sender: Sender<u16>) {
    loop {
        if let Ok(Event::Resize(width, _)) = event::read() {
            // Forward the new width to the watch loop
            let _ = sender.send(width);
        }
    }
}

Updating State and Re-rendering

The WatchState implementation handles width updates and triggers immediate re-rendering:

// src/watch/state.rs
impl<'a> WatchState<'a> {
    fn update_term_width(
        &mut self, 
        width: u16, 
        stdout: &mut std::io::StdoutLock
    ) -> std::io::Result<()> {
        if self.term_width != width {
            self.term_width = width;  // Store the fresh size
            self.render(stdout)?;     // Redraw UI with new width
        }
        Ok(())
    }
}

Rendering Width-Aware UI Components

The progress bar in src/term.rs utilizes the current terminal width to calculate its display size:

// src/term.rs
pub fn render_progress_bar(
    stdout: &mut std::io::StdoutLock,
    progress: usize,
    total: usize,
    term_width: u16,
) -> std::io::Result<()> {
    // Layout calculations use term_width for proper sizing
    progress_bar(stdout, progress, total, term_width)
}

Key Source Files for Terminal Resize Handling

File Contribution to Resize Handling
src/watch/terminal_event.rs Defines terminal_event_handler that captures crossterm resize events and emits WatchEvent::TerminalResize
src/watch.rs Contains the run_watch event loop that dispatches resize events to WatchState
src/watch/state.rs Implements WatchState with update_term_width method and stores the term_width field used by all UI components
src/term.rs Provides progress_bar and other terminal UI functions that accept term_width parameters for responsive layout

Summary

  • Rustlings handles terminal resizing through a three-stage pipeline: event capture, dispatch, and state update with re-rendering.
  • The terminal_event_handler in src/watch/terminal_event.rs uses crossterm to detect Event::Resize and converts it to internal WatchEvent::TerminalResize messages.
  • The run_watch loop in src/watch.rs acts as the central dispatcher, forwarding resize events to the state manager.
  • WatchState::update_term_width in src/watch/state.rs validates changes, updates the stored term_width, and triggers immediate UI re-rendering.
  • All responsive UI elements, including the progress bar in src/term.rs, read the current term_width during each render cycle to ensure proper layout.

Frequently Asked Questions

How does Rustlings detect terminal resizing?

Rustlings detects terminal resizing through the crossterm crate's event system. The terminal_event_handler function in src/watch/terminal_event.rs continuously polls for Event::Resize(width, _) events, which the operating system generates whenever the terminal window dimensions change.

What happens to the progress bar when the terminal is resized?

The progress bar automatically adapts to the new width. When a resize event occurs, WatchState::update_term_width updates the stored term_width value and triggers a re-render. The progress_bar function in src/term.rs receives this updated width parameter and recalculates the bar's length to fit the available space.

Does Rustlings require a restart after terminal resizing?

No, Rustlings does not require a restart. The resize handling system updates the UI in real-time through the WatchState::render method, which redraws the entire interface—including exercise lists and progress indicators—immediately after detecting a width change.

Which crate does Rustlings use for terminal event handling?

Rustlings uses the crossterm crate for cross-platform terminal event handling. This crate provides the event::read() function and Event::Resize variant that power the resize detection system across different operating systems including Linux, macOS, and Windows.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →