// lesson: cursor-rules
Cursor Movement and the Goal Column
Cursor movement sounds like row += dr; col += dc. Open vim, move
around for ten seconds, and count the rules that little formula
misses. Movement is policy, refined over fifty years into
conventions your fingers already know โ which means getting them
wrong is instantly, viscerally noticeable.
The clamping rules
Where may the cursor be? On line r, valid columns run from 0
to len(r) โ inclusive. One past the last character is a legal
and essential position: it's where you stand to append to a line.
(Rendering-wise the cursor sits on the space after the text.) But
len + 5 is not legal, and here's the classic case that generates
it: cursor at column 40 of a long line, press Down onto a 10-column
line. Keep the raw column and the cursor floats in the void; every
editor instead snaps the column to the shorter line's length.
Vertical motion clamps the row to [0, nlines-1], then snaps
the column as above.
Horizontal motion wraps. Left at column 0 moves to the end of the previous line; Right at end-of-line moves to column 0 of the next line. (At the very start/end of the buffer: no-op.) This is how arrow keys traverse a file as one continuous string of text instead of trapping you inside a line.
Home/End go to column 0 / column len. Page Up/Down move by
a screenful of rows โ the viewport height, which is why the editor
struct carries screen_rows โ clamping at the ends like any
vertical move.
The goal column โ the rule everyone feels, nobody names
Snap-to-length has a follow-up problem. Cursor at column 40, press Down through these lines:
a very long line of text with the cursor out here somewhere
short
another very long line of text
Snapping alone puts you at column 5 on short โ fine โ but then at
column 5 on the long line below. You feel this bug instantly:
"I was at column 40; passing a short line shouldn't reset my lane."
Editors therefore remember a goal column (Emacs calls it exactly that): the column you want, as distinct from the column you've got.
- Vertical moves never change the goal; they set the actual
column to
min(goal, len(new line)). - Horizontal moves and edits set the goal to wherever they put the cursor.
With the goal at 40: Down onto short shows column 5, Down again
restores column 40. Two fields, three lines of discipline, and
vertical motion feels right.
Where does this logic live?
Not in the key handler. The dispatch layer (a later lesson) will map
KEY_ARROW_UP to editor_move(e, MOVE_UP) โ one call, no logic.
All policy concentrates in one function with one switch, where every
rule is visible next to its siblings and testable without a
keyboard. The alternative โ clamps sprinkled through the
key handler, the mouse handler, the search-jump code โ is how
editors end up with the cursor in the void when you arrive at a line
by a path nobody tested.
โบ Movement Rules
20 ptsImplement editor_move(e, m) for the eight motions, with all of the
lesson's policy: inclusive end-of-line positions, vertical snap,
goal-column memory, horizontal wrap, page moves by screen_rows,
and total clamping (no motion may ever leave the cursor out of
bounds, no matter the sequence).
Contract details the tests pin down:
MOVE_LEFTat (0,0) andMOVE_RIGHTat the last position of the last line: no-ops.MOVE_UPon row 0 andMOVE_DOWNon the last row: no vertical motion, and the column also stays put (vim behavior).- Horizontal motions (LEFT, RIGHT, HOME, END) set
goal_colto the resulting column; vertical motions (UP, DOWN, PAGE_*) leavegoal_colalone and setcursor_col = min(goal_col, line len).
Log in to submit a solution and earn points.