ttyper - a Terminal-Based Typing Test
Tags: showdev, rust
Published: 2021-03-15 20:32:48

_Originally published [here](https://maxniederman.com/ttyper) on my blog._

TL;DR: [here](https://github.com/max-niederman/ttyper)'s the project on GitHub and it's installable as `ttyper` from Crates.io.

## Motivation

I was already pretty happy with the typing test I used to use, [monkeytype](https://monkeytype.com); however, I'm a big fan of CLI tools, so I wanted to be able to practice my typing without reaching for a browser or even a native application. I searched around for terminal-based typing tests, but none of them were quite what I had in mind.

Since there weren't any pre-existing tools which meant my needs, I thought it would be a good opportunity to learning about TUIs (terminal user interfaces) to make one myself. I decided to use Rust with [tui-rs](https://github.com/fdehau/tui-rs), after being inspired by tools built with it such as [gitui](https://github.com/extrawurst/gitui), [bandwhich](https://github.com/imsnif/bandwhich), and [diskonaut](https://github.com/imsnif/diskonaut).

## A Minimal Typing Test

I decided to go with a fairly simple, tried-and-true design: a list of words and an input box where you type each word one-by-one. I wanted an experience similar to [typings.gg](https://typings.gg/) or [10fastfingers](https://10fastfingers.com), but with the ability to return to a previous word after submission.

Here's what the final interface for the test input looks like:
![Test TUI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c2995s6j9n5v70w9bvwi.png)

## Finding WPM and Accuracy

Calculating the results of the test was far harder than I had originally expected, althouhg this may be due in part to the fact that I wanted more insights into my typing performance, such as keywise accuracy and WPM data, as well as a WPM graph.

First of all, I represented the test, more or less, as such:

```rust
struct Event {
    time: std::time::Instant,
    key: termion::Event::Key,
    correct: Option<bool>,
}

struct Word {
    text: String,
    events: Vec<Event>,
}

struct Test {
    words: Vec<Word>,
}
```
_You can read the real code [here](https://github.com/max-niederman/ttyper/blob/main/src/test/mod.rs)._

In this way, each "event" (keypress) can have a correctness value. Then, to find the accuracy, we can divide the number of correct events by the total number of events with correctness.

We can also find the WPM per each event from its time and its predecessor's time.

There's still a lot of things I want to change with ttyper's results calculations and UI, like adding a WPM over time graph and fixing weirdness with the overall WPM calculations, but it's working well enough for someone like me, who doesn't care too much about the exact WPM as long as I'm improving.

## Test Contents

For the test contents, I added an optional argument to specify a file to read, which is then split at whitespace and carriage returns. Then, I added functionality for automatically generating test content. It works by reading a language file and populating a list with a certain number of random lines from that file. Currently, `english100`, `english200`, and `english1000` are installed automatically. In the future, I'd like to add weighted language files based on n-gram analysis to make the test contents more realistic.

## Try It Out

If you want to try ttyper our for yourself, you can install it with Cargo:

```
cargo install ttyper
```