My structure editing theories
I have a theory that using a keyboard layout and structure editor perfectly tailored to the specific editing task, you could achieve really insane productivity. Imagine editing mathematical formulae or s-expressions (or mathematical formulae represented as s-expressions ;P) at 60 significant operations per second...
I think the current idiom of editing text with what I consider primitive edit assist (autoindent, parameter hinting, completion) is really inefficient.
First, there's the mental overhead of converting each significant operation into a stream of mostly raw-text editing operations... It's a bit like compiling a high-level language in your head, then writing assembler instructions. Not only does it unduly take mental processing power and keyboard operations on your part, but a lot of the semantics of what you meant gets lost in the process.
Which brings me to the next point: by editing text that then gets parsed in complex manner for edit assist purposes (syntax highlighting et al.), it makes the parser much, much more complicated because you have to make an incremental parser that will account for all the semi-incorrect states that you go through, and it must degrade features in a "smart" manner when it can't completely understand the editing context you're in. With a structure editor that will only let you go from one consistent state to the next, the editor would always know exactly what the editing context is and could easily provide great edit assist efficiently.
Let's have a look at an example. Let's say I want to write this:
(:html
(:head (:title "An example"))
(:body
(:p :class "benefit" "Putting things in quotes (\") is easier!")))
With an s-expression structure editor, the operations would be something like (ol = open-list, ie = insert-element, os = open-string, ul = up-one-level, the rest are raw keystrokes):
ol, ie, :html, ie, ol, ie, :head, ie, ol, :title, ie, os, An example, ul, ul, ul, ie, ol, ie, :body, ie, ol, ie, :p, ie, :class, ie, os, benefit, ul, ie, os, Putting things in quotes (") is easier!So that would be 24 significant operations (= 24 keystrokes) to handle the structure only, and we could reduce more by saying for example that ol implies ie if we didn't just create a new element.
Notice that I didn't need to escape the double quote in the string since the editor was completely aware that I was in a string-editing context. We could have many such embedded helpers. Let's say I'm writing Lisp code: (format nil "It's ~A today!" today). The editor could easily detect when I'm editing a format string and help me with ~ parameter hinting and such. You know how complicated those expressions can get sometimes...
And of course, my example was actually a representation of HTML so we could have a more specialized structure editor for that (based on the s-expression one), so the operations would be something like (on = open-node, ic = insert-child, ia = insert-attribute):
on, html, ic, on, head, ic, on, title, ic, os, An example, ul, ul, ic, on, body, ic, on, p, ic, os, Putting things in quotes (") is easier!, ia, class, next, benefit
The operation names are different from the s-expression example, but similar operations between the two editing modes would of course map onto the same key.
Here we have 17 significant operations (= 17 keystrokes) to handle the structure only, and again I'm sure we could reduce further. Notice how I have less to type here, for example when I open an HTML node the editor knows I'll necessarily insert a keyword as the first element.
Also notice how I (hypothetically) thought about inserting the class = benefit attribute after writing the child, but I simply needed to invoke the insert-attribute operation (= 1 keystroke; By the way by keystroke I mean just one key and not CTRL + something) and the editor knew I'd necessarily insert a keyword and then a string. If I had invoked...
10 comments
[ 3.0 ms ] story [ 36.1 ms ] threadIf you're talking about my usage of mnemonics such as ol, understand that those mnemonics wouldn't find their way into neither the implementation nor the operation of the editor, I just used them in my explanations because I thought the operation streams would take too much space otherwise. But in general I'm a big fan of using long-and-meaningful-identifiers.
If you're saying that it's not worth it to make a structure editor just to have more CPU efficiency, I wholeheartedly agree. It's true that a structure editor would probably be more efficient on that level, but it's only a very minor bonus compared to the real advantages of the computer always being fully aware of the editing context you're in (thus helping in ways it normally couldn't), and without the need to write such a complex piece of software as a reliable incremental parser.
If you're saying that the keystroke savings are not worth it, I'll have to admit the very simple example I used doesn't make it seem very worthwhile, but the benefits of a structure editor would become much more obvious the more you looked at complex examples of the kind of thing it could do.
I didn't even talk about the modification of structure yet. I can't tell you how many times I had to edit
into: and how slow and painful it is. With a structure editor I could do it with perhaps 3 key presses... And there are lots of use cases like this one. If you say that I could simply write an emacs-lisp function to do it and bind it to a key, you'd be right and you'd be missing the point.What I'm thinking of are various "editors" that are not only syntax-aware (like in the syntax highlighting sense) but that literally enforce the program syntax, and make it difficult-to-impossible to input a "bad" program (bad as in syntax, or bas as in correct-syntax-but-wrong-argument-types).
If that's not what you're getting at you can probably ignore this.
Generally these structure editors haven't been that well-received and most die on the vine as academic projects. I wish I remembered the names but "structure editor" should help you find stuff.
Here's the annoyances I remember:
- You're right, if you already have a mostly-correct program in your head a properly-functioning structure editor should help you get it onto the screen a lot faster, and with fewer stupid typo-level errors creeping in. The more the language has stupid boilerplate like html's close-tags, the more keystrokes you look to save.
- But: the trickier thing to get right is how to handle editing existing code.
If you're just writing html-in-lisp in an ordinary text editor, you don't have much to worry about here.
If you're writing html-in-lisp in a structure editor that makes it hard to get into an invalid (meaning: contrary-to-language-grammar) state, even something as simple as re-ordering lines can become a royal pain; this gets even worse the more you do cutesy things like write macros to auto-generate some types of output, or try to be smart about how you parse the program being written.
Here's an example: I define a format-product-row function that takes an item sku, price, thumbnail url, and product name argument and generates some html output (python notation below):
def FormatProductRow(sku, price, thumbnailURL, productName): BLAH
Your super-helpful structure editor sees this definition and memorizes the function name and required arguments.
I then proceed to write a bunch of code that makes use of FormatProductRow, and each time I use that function in my code your helpful structure editor reminds me of the arguments it takes and doesn't let me make mistakes by having the wrong # of arguments (too many or too few).
Now, later, I am like "oh snap I needed to include the marketingOneLiner argument in that function", and I need to go back through all my source and do the following:
(1) change the definition of FormatProductListing to take that argument (and handle it)
(2) go to every place the function is used, and change the # of arguments that are fed in, while making sure to actually pass in an appropriate value in each spot this guy gets used
(3) where necessary, change a lot of the functions that are calling FormatProductListing (so that are actually in possession of an appropriate value to feed into that argument slot), possibly altering their arguments, etc., until everything is done
When I get done with (1) (2) and (3), the program should be expressible in 100% correct syntax (and whatever semantics you're enforcing like type-checking).
Before I get done, the program's in a "broken" syntactic state:
- if I change the occurrences before the definition, then I've got what'll look like a lot of "misuses" to your syntax-checking logic (b/c the remembered definition != the way i'm using it in the new, fixed usages)
- if I change the definition before the occurrences, then I've got what'll look like a lot of mis-uses, b/c the old usages are incorrect according to the new, updated definition
Regardless of the ordering, you have more problems:
- if you "taint" any part of the parse tree that has errors inside it (eg: FormatProductTables is now "bad" b/c it calls "FormatProductRow", which is "bad" while I'm fixing stuff up), then potentially large parts of the program are broken during these edits. Depending on how rigid your structure editor is wrt allowing "bad" states to exist, this could...
I've just started thinking about this a few months ago and I haven't spent any time trying to actually implement my ideas yet, so there are indeed quite a few issues I haven't explored very deep or indeed not at all.
I had thought a bit about the potential rigidity tradeoffs of correctness VS usability. I hadn't really thought about going so far as enforcing function signatures, though it's the logical next step after enforcing s-expression validity.
There are lots of different types of "structure errors" and I think some should be strictly enforced while others shouldn't. In the s-expression case, one example of something that should be enforced strictly is the nesting of parentheses that delimit s-expressions. Fortunately that one is pretty easy. Wereas in a traditional editor you'd do:
as 3 different steps, in a structure editor you'll simply have one operation that will do all this atomically so you go from one correct state to the next. In the function arity case, I think a much more lax enforcement is de rigueur. There will always be some editing tasks that will require less rigid rules but I think we should value correctness by default and then introduce laxity in controlled ways where it makes sense. It's a tradeoff similar to how limiting side-effects by default is a restriction that can make a program more powerful, yet sometimes you really want to have some mutable state.edit: I'd add that my end goal for a structure editor is that it may better assist the user in his editing tasks by having a better understanding of the context. The validity enforcement is only a means to facilitate this and should be relaxed wherever it impedes the user rather than helping.
You might want to start by getting a list together of the various refactorings / simple structure transforms you encounter a lot in eg the lisp you write, and then figuring out what the best keyboard interface would be just for manually inputting a sequence of those transformations.
What I was getting at is thinking of a structure editor as a text editor + extras might be a dead end, but I don't have any bright ideas for you as to what user-interface approach wouldn't be a dead end.
Actually hold up, I have a possibility for you before I get back to the game. I could see maybe a case for a split-screen (split vertically into 1+N panes), where your currently-under-edit block is always in the (user-set fixed-size) pane, and the remaining options are like this (quasi-lisp):
+----active pane----+ (define (exponentiate x y) |) +----first suggestion pane----+ [open-sexp] (define (exponentiate x y) (|)) +----second suggestion pane----+ [delete-sexp] (define |) +----third suggestion pane----+ [swap-args] (define (exponentiate y x) |)
etc., with the basic metaphor being:
The top pane has the current version of what you're working on.
Each pane underneath it shows the result of applying one of the higher-level operations (like open-sexp or delete-sexp).
For small syntax edits (like adding a matched pair) this mode is overkill; for fancier operations (invert nesting over some complicated structure) this makes it easy to visualize what you're going to get from each command.
That's it, got to get back to the game. Good luck with your endeavors on this project; a better editor is a worthy cause.