Editor Cursor Positioning

:: text-editing vim emacs composiphrase

The biggest mistake of vi-style editors is that it positions the cursor on characters instead of between characters.

(Note, this is in a series about my “new composable text editor”, Composiphrase. This is part 1, nitpicking a detail that is mostly irrelevant to most of the rest. You can safely skip it if you just want to get to juicier details about composable text editing. See part 2 here. Though if you just want to read one, part 4 is the key piece.)

I started using Vim as my main editor in about 2011 or 2012. A few years later I switched to Emacs as my main editor, but using the evil-mode vim emulation package for emacs, meaning I still used Vim-style key bindings and editing concepts. I also continued to use Vim in some situations. I do not claim to be the foremost Vim (or vi) expert, but I explored its features pretty exhaustively at some point, tried to use most of them, and am pretty familiar with it😮. All-in-all, I think vi and Vim are excellent text editors with great ideas. But I think that the on-character cursor positioning in vi’s normal and visual modes is probably the biggest mistake in vi and its descendants👊.

😮: There are still surprises, and things that I didn’t fit into my workflow and have long forgotten, though!

👊: Or at least the biggest “mistake of commission”, the worst intentional feature. There are more important things that they are simply missing, which is why I quickly moved from vim to emacs as my main editor. But that’s a topic for another time.

I’ve finally made time to move away from using evil-mode in emacs, to using my own new modal editing system. Since I’ve moved my system away from on-character positioning, I thought I would give some background for the decision. It may seem like a small thing, but it’s been grating on me for a long time. It’s maybe silly, but it’s something that I’m really excited to be leaving behind with evil-mode.

1  On-Character Positioning: The Good🔗

Let’s start on a positive note – the feature isn’t all bad. Positioning the cursor on characters gives a sort of symmetry with other situations where the cursor is always (or at least almost always) on something instead of between things. The strongest case of this is probably lines. The cursor is always on/in a line. There is universally always only a single character\r\n that delimits lines, so as long as the cursor is not on the newline character, the cursor is always on a line. Since Vim doesn’t allow the cursor to be on a newline character, it’s a pretty strong similarity. Thus the operations of insert or append, to go into insert mode before or after the current character, mirror the operations of opening a new line above or below the current line. There are other “text objects” that we could try this argument with, but few are so consistently analagous, and, let’s be honest, vi and most descendants are heavily line-oriented, so it’s also maybe the most important. The insert/append dichotomy also fits nicely with the insert-at-start-of-line and append-at-end-of-line I/A commands, which are maybe among the most used and useful commands.

\r\n: Please don’t use multi-character line ending encodings. Let’s be civilized.

2  Ok, I think it’s mostly bad🔗

For starters, how much benefit do you really get out of having separate insert and append commands (i and a, not I and A, vs the complexity of learning to use both and get used to where you want each one? I think it is small. That story will keep repeating – the feature adds complexity in several places, and just doesn’t add commensurate value.

There are several inconsistencies with on-character positioning.
  • Empty lines – vi and friends care more about the cursor being on a given line than on a character, so the cursor can be on an empty line where there is no character to be on.

  • Empty buffer – well, this is just a special case of an empty line.

  • Because the cursor is always on a character, and the cursor is included in the visual region, you can’t have an empty visual region. Maybe an empty visual region is not very useful, but I feel like it is important for the sake of completeness. The number zero is important.

  • Oops, you can have an empty region – but only if you go into visual mode in an empty buffer or on an empty last line! If you do visual mode on an empty line that is not the last line, a copy (yank y) will copy a newline character instead of an empty string.

  • The cursor can never be on a newline character. It can be on every other character. Except in that case where the line is empty, but it’s not the last line...

  • Since insert mode uses between-character positioning, a conversion has to happen each time you go between insert and normal mode. Going from normal to insert is explicit – you use i or a to choose, or some other operation that also includes some larger movement or operation. But going from insert mode to normal mode has an implicit conversion. Because there are more between-character positions on a line than on-character positions (except in empty lines!), the conversion can’t be consistent for all positions. In particular, the conversion for the first between-character position (before the first character) and the last between-character position (after the last character) can’t be the same. In Vim you always end on the character that was before the cursor, unless the cursor was at the start of a line, in which case it ends on the character after the cursor.

3  Wherefore do ye spend [...] your labour for that which satisfieth not?📜🔗

📜: I’m not certain that Isaiah was really thinking about text editors or software complexity, but I think it’s applicable.

Insert mode essentially needs to have between-character positioning. I don’t think anything else really makes sense, and it’s why every text editor I’ve ever heard of uses it at least in insert mode, and typically in every mode1. So if you use on-character addressing, you are using it in addition to between-character addressing. So you have two positioning schemes to deal with. Extra complexity. Questonable benefit.

1: Or, well, typically in the only mode, since most editors aren’t modal like vi.

Personally this was driven home to me after switching to emacs and wanting to use more movement and selection commands. Most of these in emacs are defined assuming between-character addressing, so using them with evil-mode is typically frustrating or requires writing wrapper code to work nicely with on-character positioning. The more you want to share code with people who don’t use modal editing, the more of a constant minor frustration on-character positioning becomes.

Also, don’t you sometimes get the urge to just do a little movement while in insert mode, just occasionally? Maybe use those emacs-style key bindings you learned because they are the default in bash and many other repl-like command-line programs👉? Wouldn’t it be nice to have one movement function that works for both of those situations?

👉: Don’t do it unless you are using a custom keyboard and/or keyboard layout, though. Emacs pinky (more commonly known as RSI) is real, and can be career ending! Not to mention hobby ending.

As an aside, not related to cursor positioning, but one more thing about vi-family editors: they have some other inconsistencies that are typically convenient, but just... bothersome... when you want to think bigger. Movements in vim move differently depending on what operator they are used with. Eg. w when used by itself or with delete (dw) moves a different distance than with the change operator (cw). These little quirks aren’t all bad – if you want to change the word, you probably want the space after it, whereas if you want to delete it then you probably don’t. But... let’s say you want to have many more movements, many more “text objects”, many more operations... Having quirks where things behave contextually slightly differently is extra complexity. The possibility for these quirks also scales multiplicatively with the number of composable operations and objects/motions. Maybe this is much ado about nothing, but the value of the quirks is at odds with the value of a simple and consistent system. These are all little pieces of why I’ve wanted to move away from vim and evil-mode.

And now I have. I’m keeping most of the good ideas from vi, vim, and evil-mode. But I’m leaning harder into ideas like composability and text objects. And I’m shedding a few bad ideas, like on-character cursor positioning.

Tune in next time for more ramblings related to my “new modal text editor” implemented in Emacs.