// lesson: modal-editing-modes
Modal Editing โ Modes
Now the vi heart of the course. Everything so far โ raw input, painted
frames, a buffer โ could become a notepad: keys insert themselves,
arrows move. vi's founding idea is stranger and, once learned,
unshakeable: the same key means different things depending on mode.
In normal mode the keyboard is a command console โ x deletes, w
hops a word, nothing inserts. In insert mode it's a typewriter. In
command-line mode (after :) you're typing an instruction to be
executed on Enter.
The lineage explains the shape. Bill Joy grew vi (1976) out of the line
editor ex, itself descended from ed โ on printing terminals, where
"editing" was a command language (ed still answers your typos with
a lone ?). When video terminals arrived, Joy put a live viewport on
top of ex; the command-language soul stayed. The hardware left
fingerprints too: Joy's terminal was a Lear Siegler ADM-3A, whose
keyboard had arrows printed on H, J, K, L โ that's why those keys move
the cursor โ and whose Esc key sat where Tab does today, an easy pinky
reach. Modern keyboards moved Esc; the mode-switch key stayed, and
generations of vi users remap Caps Lock to chase it. :wq, hjkl, the
~ โ none of it is arbitrary; all of it is 1976 preserved in muscle
memory.
Why does modality survive? Because it turns editing into a
composable language. Next lesson: motions as nouns. The one after:
operators as verbs, and d w โ "delete a word" โ as a sentence. None of
that grammar works if every printable key is busy meaning itself, which
is exactly what insert mode is for: a mode where keys mean themselves,
entered deliberately, left with Esc.
The mode machine
Mechanically, modes are a small state machine that sits in front of
everything you've built: each decoded Key is dispatched first on the
current mode. The transitions:
- Normal โ Insert:
i(insert here),I(insert at first non-blank),a(append after cursor),A(append at end of line),o(open a line below),O(open above). Six doors into the same mode, differing only in where they put the cursor first โ the cursor moves are the editor core's job (final challenge); the transition is the machine's. - Insert โ Normal: Esc. The only door out. (vi's deep bet: you spend most of your time in normal mode, so leaving insert must be one keystroke, always the same one.)
- Normal โ Command:
:opens the command line at the bottom of the screen. Keys now build up a string, shown as you type. Enter executes it; Esc abandons it; Backspace erases โ and backspacing past the start cancels back to normal mode, a small authentic vi behavior the tests check.
std::visit earns its keep here: dispatching a Key variant means
handling each alternative, and the overloaded-lambdas idiom is the
standard C++ pattern for it:
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
std::visit(overloaded{
[&](char c) { /* printable key */ },
[&](CtrlKey k) { /* control chord */ },
[&](SpecialKey k) { /* Esc, Enter ... */ },
}, key);
Forget one alternative and it doesn't compile โ the sum-type payoff:
the compiler enforces that every kind of key has a decided meaning in
every mode. An if/else chain over holds_alternative compiles fine
with a case missing; the visit does not.
The machine below returns the executed command string and leaves
interpreting it to a parser โ the second challenge, where
std::variant appears on the output side: a parsed command is
one-of-several shapes, exactly what a variant is for.
โบ The Mode Machine
15 ptsImplement ModeMachine, dispatching keys per the transitions above.
mode()starts asMode::Normal.feed(key)processes one key and returnsstd::optional<std::string>: engaged exactly when a command line was submitted (Enter in command mode), carrying its text (without the:). All other feeds returnstd::nullopt.- Normal:
i I a A o Oโ Insert.:โ Command with empty pending text. Every other key: stays normal (your editor core will interpret them; the machine ignores them). - Insert:
SpecialKey::Escapeโ Normal; everything else stays. - Command: printable
charโ append to the pending text.SpecialKey::Enterโ submit: return the text, clear it, back to Normal.SpecialKey::Escapeโ abandon: clear, back to Normal, return nullopt.SpecialKey::Backspaceโ erase the last pending char; if the pending text is already empty, cancel back to Normal. Other keys: ignored. pending()returns the command line being typed (empty outside command mode) โ your render loop draws it in the message line.
Log in to submit a solution and earn points.
โบ Parse the Command Line
10 ptsThe mode machine hands you "wq" or "w notes.txt" or "42". Now
interpret it. The output is a textbook sum type: a command is one of
write / quit / write-and-quit / go-to-line, each carrying different
data โ std::variant on the return side, wrapped in std::optional
because the input might be gibberish.
Implement parse_ex(input) (input arrives without the leading :):
"w"โWriteCmd{""}(write to the current file)."w <name>"โWriteCmd{name}โ name is everything after the first space (may itself contain spaces; filenames are like that)."q"โQuitCmd{false};"q!"โQuitCmd{true}(force: discard changes)."wq"or"x"โWriteQuitCmd{}.- A string of digits โ
GotoCmd{n}withn โฅ 1(:42jumps to line 42;:0is invalid in our dialect โ reject it). - Anything else โ empty string, unknown words,
"w"with trailing junk like"wfoo", digits with a suffix โ โstd::nullopt; the editor shows "not an editor command".
Log in to submit a solution and earn points.