How Carets Move
When I show up at networking events and tell people "I’m spending a month reimplementing a
<textarea> from scratch," they usually give me a blank look and shuffle away slowly. At first glance, textareas are so ubiquitous that they seem like they’d be trivial to implement. They’re not. As an example, let’s imagine we have a caret in the very middle of a three-lined textarea:
If I press the up arrow on my keyboard, what happens? This is still the easy part, so hopefully you said “it jumps up to the first line.”
But now, if I press up again, what happens? Many people get hung up here, and say it stays in place. In fact, it moves to the beginning of the line.
If you’ve gotten it right up to this point, you probably think you understand it, and this caret movement thing isn’t too complicated. Just have a special case to jump to the beginning of the line when we go off the top, right?
Well, consider this — what happens when you now press the down arrow on the keyboard?
The beginning of the second line (shown above) is a pretty reasonable solution. After all, if we started at the top left corner and pressed down, it would obviously go to this point. However, it’s wrong. The caret jumps to the middle of the second line, back to the original position at the very beginning of this post.
Hopefully by now you’re starting to see that something pretty fishy is going on. This is similar to the case that you’re perhaps more familiar with, where the caret is at the end of a long line:
If you press up and then down in the case above, the caret will appear back at the end of the second line. We don't want the caret to drift leftward as we pass over short lines.
We can handle both of these cases (and some other problems) with a simple solution: the caret must remember its x position, in pixels, and only updated this stored position when moving left/right or when typing. Then, whenever moving up/down, we jump to the closest character to that stored pixel x position. For non fixed-width typefaces, this being a pixel measurement and not a character measurement is crucial, since we want the caret to go up to the visually equivalent character, not the char-num equivalent character.