Diary 2019-05-01
One thing I ended up working on briefly over the weekend was an attempt to come up with an alternative to Sublime Text's default "reindent" command that is sometimes triggered by pressing tab.
The general problem of "I don't like what Sublime Text does here by default" is something that I've had cause to think about for years. To recap that question, I asked "Hey, how do I make it only indent one tabstop?", got some discussion that I feel was... not helpful, figured out for myself how to get the behavior I wanted, and was happy with it, then blew back in a few days ago, years later, with a link to a Gist that does something completely different than what I was asking for.
Let's break this down:
- The "auto_indent" setting in Sublime Text has five associated key-bindings. Four of them are kind of two groups of two, and have to do with the enter key, but I don't think they're all of the functionality associated with the enter key when "auto_indent" is true? Doesn't matter. The last keybinding (first in the file) is as follows: when the cursor is on an empty line, with nothing selected, treat pressing "tab" as the "reindent" command.
- The baseline functionality of the "reindent" command, applied to an empty line, is that it will find the previous non-indented line, and if it's indented, match that indentation, otherwise indent by one tab.
- (It also detects characters that begin blocks, and will indent one tab more than an indented line that ends by beginning a block.)
- This behavior, in a language that uses explicit delimiters, is fine. Perfectly acceptable.
- This behavior, in Python, is agonizing.
I'm going to try to systematize the issues I'm having here. Let's suppose, given a blank line in front of a given user at a given moment, that there exists a "user-desired indentation" (UDI), a quantity of whitespace that that user at that moment, desires to be inserted, should they press the tab key. What can we conclude?
The UDI must be non-zero; if they desired no tabs be inserted; they would not move their cursor there and press the key.
It might not be possible for code analyzing the text buffer to determine the UDI; it must rely on fallible heuristics. In particular, there are text buffers where multiple UDIs are possible for the same arrangement of text, depending on user intent.
Heuristics must be evaluated based on the consequences of failure. If a heuristic is wrong, there are two possibilities:
- The heuristic over-estimated the UDI: the text is too indented and some of the whitespace must be deleted. Call this an excess.
- The heuristic under-estimated the UDI: the text is insufficiently indented, and further tabs must be added. Call this a deficit.
These two outcomes present a different workflow to the user to resolve them:
- An excess is resolved by deleting text. Deleting too much text results in a blank line, and the user has expended effort to achieve nothing. Furthermore, the user must switch from using the left hand, to the right.
- A deficit is resolved by adding text. Adding too much text results in nothing in particular. The user does not need to use any additional keys to resolve the deficit.
Given a line that admits multiple possible UDIs, the quality of the behavior depends on the ease of resolution in the case that the behavior is incorrect. Because the workflow for resolving excess is more onerous, I feel that excess must be avoided, and therefore, the correct behavior is to choose the lowest indent among all possible UDIs.
This, then, is what motivated my original question: if we assume that "one tab" is always a possible UDI, then "one tab" is the best choice of what to insert. The resulting behavior was good enough for me for over two years. So what changed?
Well, I'm actually sort of A/B testing myself, with the command I wrote active on one machine I use, but not the other. That said, the idea came to me when I was following the roguelike tutorial I love to complain about, and realized that it would be nice—not essential, but nice—if pressing tab on a blank line between two blocks at the same indentation, brought it to that same level. And then I considered the motivating case for the StackOverflow question: "Given a method that ends in a highly indented block, how do I write a new method after it without hitting the delete key a bunch?" and I thought "What if the class is in an indented block, itself? Supposing I'm inserting a method between two methods, I know I don't want to use any less indentation." After years of being fine with the behavior I'd written, I realize that it was possible to get a higher lower bound on the desired indentation: to reduce deficits without risk of excess.
So anyway, that's what I was doing Sunday night instead of sleeping. Whoops!