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

> Discover how Rustlings deftly handles terminal resizing. Learn about its event system, watch loop, and real-time UI updates powered by crossterm.

- Repository: [The Rust Programming Language/rustlings](https://github.com/rust-lang/rustlings)
- Tags: deep-dive
- Published: 2026-03-05

---

**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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/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:

```rust
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:

```rust
// 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`](https://github.com/rust-lang/rustlings/blob/main/src/term.rs) utilizes the current terminal width to calculate its display size:

```rust
// 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`](https://github.com/rust-lang/rustlings/blob/main/src/watch/terminal_event.rs) | Defines `terminal_event_handler` that captures crossterm resize events and emits `WatchEvent::TerminalResize` |
| [`src/watch.rs`](https://github.com/rust-lang/rustlings/blob/main/src/watch.rs) | Contains the `run_watch` event loop that dispatches resize events to `WatchState` |
| [`src/watch/state.rs`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/src/watch.rs) acts as the central dispatcher, forwarding resize events to the state manager.
- **`WatchState::update_term_width`** in [`src/watch/state.rs`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/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`](https://github.com/rust-lang/rustlings/blob/main/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.