Coding TypeGym: A Rust Terminal Typing Trainer | Part 3: Typing Logic

Published on

In Part 3 we finally cross the line from “nice UI prototype” to an actual working typing trainer.

We implement the core typing behavior (including sane whitespace handling), wire up session timing, compute WPM and accuracy, and display results right inside the UI once you complete the text.


What We Covered


Design Insights

Whitespace Is the Hard Part (In Terminal Typing)

If you treat whitespace like any other character, terminal typing apps quickly feel annoying:

So we handle whitespace intentionally:

This keeps the experience consistent and prevents the input from drifting out of alignment.

“Hits” Are About Staying Error-Free

For accuracy we track two counters:

So a “hit” is basically: after this keypress, are we still error-free? That’s a simple, intuitive baseline metric for early versions of the trainer.


Implementation Highlights

1) Typing input with smart whitespace

apply_char() is now real. It:

pub fn apply_char(&mut self, c: char) {
    let target_count = self.target.chars().count();
    let input_count = self.input.chars().count();

    if input_count >= target_count {
        return;
    }

    let to_append = if c.is_whitespace() {
        let ws: String = self
            .target
            .chars()
            .skip(input_count)
            .take_while(|c| c.is_whitespace())
            .collect();
        if ws.is_empty() { " ".to_string() } else { ws }
    } else {
        c.to_string()
    };

    self.input.push_str(&to_append);

    self.strokes += 1;
    if self.is_error_free() {
        self.hits += 1;
    }
}

2) Backspace that respects whitespace groups

Normal backspace removes at least one “character”, but if both the input and the target end with whitespace at the current position, we remove the whole matching whitespace run. That makes “undoing” spaces/newlines feel natural.

3) Ctrl+W word deletion

apply_backspace_word() behaves like a terminal-friendly “delete previous word”:

  1. remove trailing whitespace
  2. remove the word (non-whitespace)
  3. remove a single whitespace before the word (then stop)

This is one of those tiny UX details that instantly makes the tool feel more serious.

4) Session timing: start, stop, and elapsed time

We record the start time on the first meaningful input and stop when the session completes. Then we derive elapsed seconds for stats:

fn elapsed_seconds(&self) -> f64 {
    match (self.session_start, self.session_end) {
        (Some(start), Some(end)) => end.duration_since(start).as_secs_f64(),
        _ => 0.0
    }
}

5) WPM + accuracy

WPM is computed with the standard “5 chars = 1 word” approximation:

We also introduce chars_count() that treats consecutive whitespace as a single unit. The goal is to avoid weird stats spikes from runs of spaces/newlines in the target text.

Accuracy is the simple ratio:

6) Results UI + new session loop

When the text is complete, we render a results line (WPM + accuracy) and show a hint:

We also return should_loop from run_ui() so the outer application loop can restart cleanly without a full program restart.


What’s Next?

Now that TypeGym is interactive and can complete a run, we can start improving the “trainer” experience:


Project Code

You will find the complete source code here: typegym