RSType - Rust Typing Trainer
A fast, terminal-based typing trainer written in Rust with multiple typing modes, Wikipedia content, word salad dictionaries, and session history tracking.
Features
- Multiple typing modes — Forward, Stop, Correct, Sudden Death, and Blind modes for varied training
- Wikipedia content — fetch random Wikipedia paragraphs for fresh, varied typing material
- Word salad mode — generate practice text from installable dictionaries (en-US, de-DE, fr, etc.)
- Configurable text length — one line, short paragraph, paragraph, or long paragraph
- Session history — every session is recorded in JSONL format with per-keystroke timing data
- Calendar view — browse training history by month in an interactive calendar
- In-app configuration — switch modes and settings from the Config screen
- TUI interface — clean terminal UI built with Ratatui, with toolbar and status bar
- Shell completions — generate completions for Bash, Zsh, Fish, PowerShell, and Elvish
- Cross-platform — builds for Linux (x86_64, aarch64), macOS, and Windows
Quick Start
rstype wikipedia download # Download Wikipedia paragraphs
rstype train # Launch the typing trainer
rstype train --mode forward # Train in forward mode
rstype train --source word-salad # Train with word salad text
rstype version # Show version and build info
Typing Modes
| Mode | Description |
|---|---|
| Forward | Cursor advances even on wrong key; errors shown in red |
| Stop | Cursor stays on wrong key until the correct one is pressed |
| Correct | Like Forward but must correct all errors before finishing |
| Sudden Death | One mistake resets the entire session immediately |
| Blind | Typed characters are hidden (shown as ·); no visual feedback |
Philosophy
Practice-oriented, zero-friction typing training — launch and start typing with minimal setup. Convention over configuration with sensible defaults.
Author
Mark Veltzer mark.veltzer@gmail.com
Content Sources
rstype supports multiple sources for typing practice. You can configure the
default source in your config file or override it with the --source flag
when running the train command.
Wikipedia
The Wikipedia source fetches random, high-quality paragraphs from a local collection. This provides varied, natural language practice on a wide range of topics.
Commands
-
rstype wikipedia downloadDownloads paragraphs from Wikipedia until your local collection reaches the target size (default: 1000). Use-cor--countto specify a different total count.rstype wikipedia download --count 5000 -
rstype wikipedia statsDisplays statistics about your local collection, including total paragraphs and file size. -
rstype wikipedia clearDeletes your local collection. -
rstype wikipedia showShows the file path where the Wikipedia collection is stored.
Word Salad (Dictionaries)
Word salad mode generates practice text by picking random words from installed dictionaries. This is excellent for drilling common words and improving raw speed without the context of natural sentences.
Commands
-
rstype dict list-remoteLists all language dictionaries available for installation from the wooorm/dictionaries collection. -
rstype dict install <LANG>Installs a specific dictionary (e.g.,en-US,de-DE,fr).rstype dict install en-US -
rstype dict listLists all dictionaries currently installed on your system. -
rstype dict remove <LANG>Removes an installed dictionary. -
rstype dict showShows the directory path where dictionaries are stored.
Usage in Training
To use a specific source during a training session:
# Train with Wikipedia paragraphs
rstype train --source wikipedia
# Train with Word Salad (uses the first available installed dictionary)
rstype train --source word-salad
You can also specify the target length of the text:
# Possible lengths: one-line, short-paragraph, paragraph, long-paragraph
rstype train --source wikipedia --length short-paragraph
Keyboard Shortcuts
Global shortcuts (work on every screen)
| Key | Action |
|---|---|
Ctrl+C | Exit the application (intercepted by app to cleanly restore terminal) |
Ctrl+T | Go to Train screen |
Ctrl+G | Go to Config screen |
Ctrl+H | Go to History (calendar) screen |
Ctrl+E | Exit the application cleanly |
Esc | Go back to Train screen (from Config or History), or exit if already on Train |
Train screen
| Key | Action |
|---|---|
| Any character key | Start session (on first keypress), type character |
Backspace | Delete last character (move cursor back) |
Space / Enter / R | Restart session (only when session is Done) |
Config screen
| Key | Action |
|---|---|
↑ / ↓ | Move selection between modes |
Enter | Save selected mode and return to Train screen |
History (calendar) screen
| Key | Action |
|---|---|
← | Go to previous month |
→ | Go to next month |
Notes
Ctrl+Cis intercepted by the app (raw mode captures it before the OS). It exits cleanly, restoring the terminal. This is preferable to a raw SIGINT which would leave the terminal in raw mode.- Navigating away from the Train screen mid-session silently discards the in-progress session (it is not saved to history).
Status Bar Design
Overview
A single-row status bar is displayed at the bottom of the terminal window on all screens. It is always visible and provides authorship and branding information.
Location
The status bar occupies the last row of the terminal (area.height - 1). The
usable body area (toolbar to status bar) is therefore area.height - 2 rows.
┌─────────────────────────────────────┐ ← row 0: toolbar
│ │
│ body content │
│ │
└─────────────────────────────────────┘ ← last row: status bar
Content
rstype by Mark Veltzer <mark.veltzer@gmail.com>
- Left-aligned
- Padded with spaces to fill the full terminal width
- No dynamic content — it never changes at runtime
Visual style
| Property | Value |
|---|---|
| Background | White |
| Foreground | Black |
| Modifier | none |
Matches the toolbar at the top — both are white bars, framing the black body in between. This gives the UI a consistent, symmetrical appearance.
Rationale
- Branding — identifies the application and its author in any screenshot or recording.
- Symmetry — the toolbar occupies the top row; a status bar at the bottom gives the UI a balanced, framed appearance common in terminal applications (vim, htop, etc.).
- Static content — future versions could use the status bar to show transient messages (e.g. “Config saved”) without disrupting the main body layout, since the row is already reserved.
Minimum Real Estate Requirements
Overview
The app checks terminal dimensions at startup and refuses to run if the terminal
is too small to render correctly. The minimums are stored in ~/.config/rstype.toml
as min_cols and min_rows.
Per-screen analysis
| Screen | Min width | Min height | Notes |
|---|---|---|---|
| Toolbar | any | 1 | Always 1 row |
| Status bar | any | 1 | Always 1 row |
| Train | 47 | 5 | Text (43 chars) + box borders + blank lines |
| Train + progress bar | 47 | 8 | Box + progress + stats rows |
| Results | 52 | 8 | Fixed-size results box |
| Config | 60 | 10 | Fixed-size config box |
| Calendar | 74 | 23 | 7 cells × 10 chars + borders; 6 weeks × 3 rows + headers + borders |
Overall minimum (most demanding screen: Calendar)
min_cols = 76 # 74 (calendar box) + 2 side margin
min_rows = 26 # 23 (calendar box) + 2 (toolbar + statusbar) + 1 top/bottom margin
These are the default values. They can be changed in ~/.config/rstype.toml:
mode = "forward"
min_cols = 76
min_rows = 26
Behaviour when terminal is too small
On startup, the app queries the terminal size. If either dimension is below the configured minimum, it prints an error to stderr and exits with code 1:
Error: terminal too small (current: 60×20, required: 76×26)
Rationale
- Config option rather than hard-coded constant: allows users with small terminals to lower the minimum if they accept degraded rendering, and allows future screens with different requirements to raise it without code changes.
- Startup check rather than per-frame: simpler, no need to handle the app being “paused” mid-session because the window was resized.
Future Mode Ideas
This chapter collects ideas for additional typing modes beyond the five currently implemented (Forward, Stop, Correct, Sudden Death, Blind). These are candidates for future implementation — not promises. They are grouped by what kind of skill or experience they target.
Current Modes (for reference)
| Mode | Concept |
|---|---|
| Forward | Errors are shown but don’t block progress |
| Stop | Cursor blocks until you hit the correct key |
| Correct | Errors advance but you must backspace and fix them before finishing |
| Sudden Death | One mistake resets you to the start |
| Blind | No visual feedback — all typed chars shown as dots |
Accuracy / Penalty Modes
Rewind
A wrong key sends you back N characters (e.g. 3–5) rather than all the way to the start like Sudden Death. A middle ground between Correct and Sudden Death — the penalty hurts but isn’t catastrophic.
Three Strikes
You get N lives. Each mistake costs one. Lose them all and the session restarts. Adds tension without the brutality of Sudden Death, and gives the user a visible “health” indicator in the status bar.
Decay
Each mistake adds a time penalty (e.g. +2 seconds) to your final score. You can keep going, but errors are costly. Encourages a risk/reward calculation: is it faster to backspace-and-fix or to eat the penalty?
Speed / Pressure Modes
Countdown
You have a fixed time limit (e.g. 30s, 60s, 120s). Type as much as you can before time runs out. Measures raw throughput under pressure. This is the standard mode in most web-based typing trainers (monkeytype, 10fastfingers).
Accelerating
You must maintain a minimum WPM that gradually increases over the session. Fall below the threshold and you fail. Tests how fast you can sustain accuracy — a moving target rather than a fixed one.
Sprint
Short bursts (single words or short phrases) back-to-back, with per-word timing rather than whole-text timing. Focuses on burst speed and reaction time rather than sustained typing.
Learning / Training Modes
Mirror
The text is displayed reversed (or the keyboard mapping is flipped). A neuroplasticity challenge — forces conscious processing rather than muscle memory. Probably a novelty, but interesting.
Weighted
After a round, the app identifies your weakest keys (highest error rate or slowest reaction time) and generates the next round’s text emphasizing those characters. Targeted weakness training — makes the tool genuinely adaptive rather than just presenting fixed content.
Rhythm
A metronome or visual pulse sets the pace. You must type one character per beat. Trains consistent cadence rather than raw speed. Useful because inconsistent rhythm (fast bursts followed by pauses) is a common typing flaw even among fast typists.
No Backspace
Like Forward, but Backspace is explicitly disabled and errors are permanent. Forces you to commit and move forward — trains confidence and discourages the habit of second-guessing every keystroke.
Endurance / Challenge Modes
Marathon
An endless stream of text. No defined end — just tracks how long you can sustain a target WPM/accuracy before fatigue sets in. Good for stamina training and for observing how performance degrades over time.
Survival
Combines Countdown + Sudden Death: you have a timer, and each correct word adds time to the clock. One mistake ends the run. How long can you survive? Gamifies the session in a way that rewards both speed and accuracy simultaneously.
Zen
No timer, no error tracking, no score. Just type. For warming up or practicing without pressure. The anti-mode — a deliberate counterweight to Sudden Death.
Priority
If only a few of these are implemented, the highest-impact additions would be:
- Countdown — it’s the expected default in most typing trainers and would make rstype feel familiar to users coming from the web.
- Weighted — turns rstype into an adaptive trainer rather than a fixed content player. Biggest step up in actual pedagogical value.
- Zen — a cheap-to-implement complement to the high-pressure modes, and useful as a warm-up mode before a “real” session.
History Recording Design
Overview
Every completed training session is appended as a single JSON line to a log file. This allows post-hoc analysis of typing speed, accuracy trends, and per-key performance without any in-app reporting UI.
File location
~/.local/share/rstype/history.jsonl
This follows the XDG Base Directory Specification:
~/.config/rstype.toml— configuration (settings the user edits)~/.local/share/rstype/history.jsonl— application data (generated by the app)
History is data accumulated by the app, not something the user configures, so
~/.local/share is the correct location by that convention.
Why ~/.local/share/rstype/?
This follows the XDG Base Directory Specification
for user-specific application data on Linux. It keeps training data separate from
configuration (~/.config/rstype.toml) and out of the home directory root.
File format: JSONL
One JSON object per line, appended on every session completion.
Why JSONL over SQLite?
SQLite would enable richer in-process queries but adds a native dependency and
significantly more complexity. JSONL is append-only, requires no schema migrations,
is readable with any text tool (cat, grep, jq), and can be imported into any
analysis tool (Python, R, DuckDB, etc.) trivially.
Why JSONL over plain CSV?
The keystrokes field is a variable-length array. CSV cannot represent nested
structures cleanly without quoting hacks or splitting into multiple files.
Record structure
{
"timestamp": "2026-04-04T15:08:00Z",
"text": "The quick brown fox jumps over the lazy dog",
"mode": "forward",
"wpm": 65.3,
"errors": 2,
"keystrokes": [
{"typed": "T", "offset_ms": 0},
{"typed": "h", "offset_ms": 312},
{"typed": "Backspace", "offset_ms": 850},
{"typed": "h", "offset_ms": 1103}
]
}
Fields
| Field | Type | Description |
|---|---|---|
timestamp | string | ISO 8601 UTC timestamp of when the session completed |
text | string | The target text that was trained on |
mode | string | Typing mode: "forward" or "stop" |
wpm | number | Words per minute (chars / 5 / elapsed minutes) |
errors | number | Total wrong keypresses during the session |
keystrokes | array | Ordered list of every key pressed, from first to last |
Keystroke fields
| Field | Type | Description |
|---|---|---|
typed | string | The key that was pressed, in W3C format (see below) |
offset_ms | number | Milliseconds since the first keypress of the session |
Why record every keypress (not just correct ones)? Wrong keypresses, backspaces, and hesitations are precisely where skill gaps live. Recording only correct keys would discard the most valuable training signal.
Why no expected field per keystroke?
It is redundant. The target text is stored in the text field, and the position
of each correct keystroke in the sequence can be derived from it. Storing expected
would bloat the log for no additional information.
Key name standard: W3C KeyboardEvent key values
Key names follow the W3C UI Events KeyboardEvent key Values specification.
Examples:
| Key | Stored as |
|---|---|
| Letter a | "a" |
| Letter A (shifted) | "A" |
| Space | " " (literal space character) |
| Backspace | "Backspace" |
| Enter | "Enter" |
| CapsLock | "CapsLock" |
| Left arrow | "ArrowLeft" |
| Escape | "Escape" |
Why W3C key values over USB HID keycodes (numbers)?
USB HID codes are more compact (1–2 digits vs. 9 characters for "Backspace")
but the size difference is negligible (~2 bytes per keystroke over a 43-character
exercise). W3C key strings are self-documenting, require no lookup table to
interpret, and are the closest thing to a universal cross-platform string standard
for key names.
CI/CD & Deployment
This project uses a modern, streamlined GitHub Actions workflow for both documentation deployment and binary releases.
Documentation Deployment
The documentation is built using mdBook and deployed directly to GitHub Pages without the need for a separate gh-pages branch.
Key Features of the Workflow
- Stateless Deployment: The site is deployed from a temporary build artifact using the
actions/deploy-pagesaction. - Root Configuration:
book.tomlresides in the project root for simplicity. - Direct Source Paths: Source files are located in
docs/, and the output is generated in_site/(which is ignored by Git). - Automated Builds: Any push to the
masterbranch triggers a re-build and re-deployment of the documentation.
Workflow File
The configuration is defined in .github/workflows/docs.yml.
Binary Releases
Releases are triggered by pushing a version tag (e.g., v0.2.1).
Release Strategy
- Platform Support: The workflow currently builds for:
- Linux (x86_64, aarch64)
- macOS (x86_64, aarch64)
- Automated Assets: Binaries are automatically renamed with platform suffixes and attached to the GitHub Release.
- Notes Generation: Release notes are automatically generated based on commit history.
Workflow File
The configuration is defined in .github/workflows/release.yml.
Versioning
This project uses cargo-release for version management.
# Example: Bump patch version and push tag
cargo release patch --execute
The release.toml file in the root directory contains the configuration for cargo-release, ensuring consistent tag formats and signing.