# vimhjkl grades your keystrokes against a par, not the buffer you leave behind

> A terminal trainer that drills 66 Vim skills and 230 challenges inside real vim or neovim, capturing keystrokes and scoring them on correctness and efficiency. Command drills reject hand-editing, drills run on a clean configuration, and the curriculum is regenerated from sources that refuse to emit a broken challenge.

**S-Sigdel/vimhjkl** — learn vim from your terminal with spaced repetition

- Repository: https://github.com/S-Sigdel/vimhjkl
- Stars: 544 · Forks: 13
- Language: Python
- License: MIT
- Published: 2026-09-17 · Updated: 2026-09-17 · Language: en
- Canonical page: https://hysenlabs.com/projects/s-sigdel-vimhjkl

## Keystrokes are recorded and scored against a par

The measurement decision is what separates this from a checklist or a keybinding quiz.

You edit in real vim or neovim, not in an emulator the tool wrote. The goal for the current challenge sits in a read-only split beside your buffer, and your keystrokes are captured with the editor's own script recording flag.

Those keystrokes are then scored on two axes: whether they were correct, and how efficient they were compared against a verified par.

The efficiency axis is where the par matters. A drill passes when the attempt is both correct and at most twice the par. That is a deliberately loose bound rather than an exact match, so there is room for a slightly different but equally idiomatic route to the same result. What it rules out is doing it the long way round on purpose.

Command drills add a further constraint. Where the technique involves an ex command, a global command or a normal-mode command applied to a range, you have to actually issue that command. Hand-editing the buffer until it matches the goal is rejected even if the final text is identical.

That rule exists because the command is the thing being taught. A drill that accepted any route to the text would let you pass every substitution exercise by selecting and typing the result.

## Every drill runs on a clean editor configuration

Grading that depends on keystrokes is only meaningful if the editor behaves predictably, and the way this is guaranteed is to throw the configuration away.

Drills start vim with no configuration file at all. Plugins and autocommands therefore cannot interfere with what you type or with how a motion resolves, which removes the most common source of a grading discrepancy: someone else's plugin turning a key into something else.

That default comes with an escape hatch, and the mechanism is interesting. The settings menu offers Vim extras, which are your own display commands run at drill startup. Relative line numbers, or a particular colour scheme, can be added so the buffer looks the way you are used to working.

So the split is between behaviour and appearance. Anything that changes what the editor does is excluded; anything that changes how it looks is yours to supply.

The distinction is the whole design in one sentence, and it is why the extras are described as display commands rather than general configuration. If you tried to put a mapping or an option that alters motion behaviour in there, grading would stop meaning anything.

## Mastery is a Leitner box plus a rep count toward twenty-five

The scheduling model is two mechanisms rather than one, and they track different things.

The first is a Leitner box, numbered one to five, which drives both when a skill comes back and what unlocks next. A skill sitting in a low box resurfaces sooner; one promoted upward resurfaces later.

The second is a repetition count on the way to twenty-five. Once a skill reaches that many reps it moves off the learning schedule entirely and onto a maintenance schedule.

That threshold is doing real work. It is the difference between a skill you are still acquiring and a skill you are keeping alive, and a tool that only had the first mechanism would either keep drilling things you have learned or quietly drop them.

Unlocking is tiered rather than linear: harder skills become available as the tier below them is mastered, so the curriculum reveals itself in order.

All five modes write to this same model. The mode you pick changes only how much help you are shown before you start editing, not what gets recorded. Two modes do differ in what they log: practice mode keeps a single outcome per skill, recording your best retry, while grind mode records every repetition. So repeated drilling inflates your history in one mode and not the other.

## Quitting without saving is an abstain and costs nothing

One rule in the grading section is worth singling out because it is the kind of detail that decides whether a drill tool feels fair.

Quitting without saving counts as an abstain, and an abstain does not count against you.

That is a small design decision with a large effect on behaviour. Without it, a student who is stuck has an incentive to force something through rather than stop, which trains exactly the wrong instinct in an editor where aborting a half-finished change is a normal and healthy move.

It also means the rep count toward twenty-five is a count of attempts you actually committed to. You cannot inflate it by starting and backing out.

Combined with the pass rule of correct and within twice par, the grading model ends up reasonably kind: it demands the technique, it tolerates a different route, and it does not punish a retreat. Those three properties together are what make repetition drilling tolerable over the twenty-five repetitions a skill needs.

The five modes themselves are thin wrappers over that core. Learn shows the technique and the idiomatic move before you edit. Blind shows only the before and after states, so you have to recall the move rather than recognise it. Practice takes your weakest skills and retries until they pass. Grind repeats one skill a set number of times back to back. Review is flashcards with self-rating and no editor at all.

## The curriculum is generated, and generation fails loudly

The data behind the drills is not hand-maintained, which is unusual and is the most interesting engineering decision in the repository.

The skills file is generated. Lessons live in separate pass modules under the build directory, and a pool file holds extra verified instances. A generator step verifies every challenge in real vim and writes the output.

The property that matters most is what happens when verification fails: the generator refuses to write. There is no partial output and no best-effort fallback, so a broken challenge cannot reach the curriculum.

That is the correct failure mode for this kind of content, because a drill that does not work is worse than a missing drill. Someone learning a substitution technique from a challenge whose expected keystrokes are wrong would learn the wrong thing and believe it.

Regeneration and testing are separate commands, and the test suite is split by concern rather than being one file:

```sh
uv run python -m build.generate          # verify every challenge in real vim, write skills.json
uv run python -m tests.test_grader       # grading tests (replays keys through vim)
uv run python -m tests.test_engine       # scheduling/scoring tests
uv run python -m tests.test_i18n         # locale overlay tests
```

Replaying recorded keys through a real editor is what makes the grader itself testable.

The consequence for contributors is stated plainly: adding a technique is a data change, not an engine change. You write or extend a lesson and regenerate, and the verification gate decides whether it ships.

## Any key can be remapped, and remaps are graded as the original

Most Vim users have muscle memory that differs from the defaults, and a trainer that ignores that measures the wrong thing.

Any key can be remapped in any mode from the settings menu. The examples given are conventional: a two-stroke escape replacement, and a colon-operator key.

The detail that makes this safe is that remapped keys are graded as the original. If you always press a different sequence to escape, the drill records the technique you intended rather than marking you wrong for a personal preference.

That is a small amount of logic with a disproportionate effect on whether the tool is usable. Without it, a remap would fix the ergonomics and break the grading simultaneously.

Language selection is handled in the same menu, and there is a command-line flag for it as well. The repository carries contribution guides in two languages, and one of the four test targets exists specifically to check the locale overlay, which means translation is treated as a tested surface rather than as an afterthought.

Other flags cap and pace the session: a repetition count, a difficulty gate that limits how far into the curriculum new drills can reach, and an option to hide the move hints. The gate in particular is what lets a session stay inside what you have already unlocked instead of surfacing something new every time.

## Pure standard library, and the only real dependency is an editor

The packaging is minimal in a way that matters for a tool you are expected to run inside your editor.

The runtime dependency list is empty, and the reason is given in the manifest: the package is pure standard library. The single external requirement is a vim or neovim binary on your path.

The build backend is a modern one with a pinned narrow range, the module lives under a src root, and Python 3.11 or newer is required.

So installing this adds nothing to your environment except a command. There is no plugin to keep in sync with your editor, no native extension to rebuild, and nothing that can conflict with a distribution's vim.

That constraint also explains a good deal about how the tool has to work. Capturing keystrokes, running a real editor as a subprocess, verifying challenges against it, and replaying keys in tests are all things you would normally reach for a library to do. Doing them in the standard library is what keeps the dependency list at zero, at the cost of more code to maintain.

Installation is offered three ways: a Homebrew tap for macOS and Linux, an Arch package repository entry, and a source install that needs the Python package manager and an editor binary:

```sh
git clone https://github.com/S-Sigdel/vimhjkl && cd vimhjkl
uv sync && uv run vimhjkl
```

The sessions themselves are chosen by flag, and the two drill modes differ only in how much help you see first:

```sh
vimhjkl --drill                             # Learn mode
vimhjkl --drill --mode blind                # Blind mode
vimhjkl --practice                          # weakest skills, retry until pass
vimhjkl --reps 6                            # Grind one skill N times
vimhjkl --review                            # flashcards, no editor
```

The last mode is worth knowing about because it needs no editor either: review mode is self-rated flashcards that never launch vim, which makes it the one part of the curriculum usable on a machine with no editor at all.

## Conclusion

vimhjkl suits someone who already moves in Vim comfortably and stalled on the techniques the built-in tutor skips, since that gap is precisely what the curriculum names and the grader measures. It suits you less if you are starting from zero, because none of the material assumes you need basic motions taught. Before your first session, add your own display commands as Vim extras so the buffer looks the way you are used to, and know that drilling deliberately discards your configuration, which is the correct behaviour for grading and will still surprise you the first time.

## FAQ

### How does vimhjkl grade a Vim drill?

It records your keystrokes with the editor's script recording flag and scores them on correctness and on efficiency against a verified par. A drill passes when the attempt is correct and at most twice the par, and command drills must actually issue the ex command rather than hand-editing the buffer to match.

### Why does vimhjkl ignore my Vim configuration during drills?

Drills start vim with no configuration file so plugins and autocommands cannot skew grading. You can add your own display commands as Vim extras so the buffer looks familiar, but anything altering editor behaviour stays out.

### What happens if I quit a drill without saving?

It counts as an abstain and does not count against you. You cannot inflate your repetition count by starting and backing out of a challenge.

### How does vimhjkl decide what to drill next?

Each skill has a Leitner box from one to five for scheduling and unlocks, plus a repetition count toward twenty-five after which it moves to a maintenance schedule. Harder skills unlock as the tier below is mastered, and a difficulty gate can cap how far new drills reach.

### Can I use different keys in vimhjkl than the Vim defaults?

Yes. Any key can be remapped in any mode from the settings menu, and remapped keys are graded as the original, so a personal sequence does not count as an error.

### What does vimhjkl need installed to run?

A vim or neovim binary on your path, and Python 3.11 or newer. The runtime dependency list is empty because the package is pure standard library. Review mode is the exception, since it is self-rated flashcards that never launch an editor.

## Sources

- [Issues](https://github.com/S-Sigdel/vimhjkl/issues)
- [License: MIT](https://github.com/S-Sigdel/vimhjkl/blob/main/LICENSE)
- [README](https://github.com/S-Sigdel/vimhjkl/blob/main/README.md)
- [Releases](https://github.com/S-Sigdel/vimhjkl/releases)
- [S-Sigdel/vimhjkl on GitHub](https://github.com/S-Sigdel/vimhjkl)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/s-sigdel-vimhjkl
