

My structure editing theories - Hexstream

I was writing a post on the keyboard layout topic and my editing got a bit out of hand so I thought I'd post a new topic... Enjoy.<p>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...<p>I think the current idiom of editing text with what I consider primitive edit assist (autoindent, parameter hinting, completion) is really inefficient.<p>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.<p>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.<p>Let's have a look at an example. Let's say I want to write this:<p><pre><code>  (:html
   (:head (:title "An example"))
   (:body
    (:p :class "benefit" "Putting things in quotes (\") is easier!")))
</code></pre>
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!<p>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.<p>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...<p>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):<p>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<p>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.<p>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.<p>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 ul at the end the editor would have returned me to the place I was when I invoked ia.<p>Also notice that the editor could easily switch views, if someone who hates s-expressions comes to see my code I could just flip a switch and a traditional HTML representation would be shown. And I could edit it <i>exactly</i> like the s-expression representation at full speed. There could be many such views.<p>I also have ideas about relying less on file structure and more on "query-views" but that's for another day.
======
frig
This is old hat, to a point. Names are escaping me but from time to time
there've been structure-editing "text editors" that have you work at the
correct "syntax" layer, instead of the level of raw text.

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 make your program totally
useless.

\- if you don't do that kind of "tainting", your program's going to be pretty
weak in a lot of ways.

The situation above is just one particular example of a general problem you'll
encounter if you go this route: how do you tell "good" errors -- ie, the kind
of temporarily broken syntax or semantics you'll get while editing code --
from "bad" errors, in your syntax-and-semantics-enforcing logic?

Unfortunately for you, I'm afraid, a simple mode-toggle doesn't work too
great, either, and here's why in a nutshell:

\- when you're first writing the program and the editor is forcing you to be
"correct", everything that you wind up writing is going to turn out correct
(just b/c if it was incorrect it'd not have made it through).

\- if you hit the "CHECKING OFF" switch, and then do some mucking about, and
turn the "CHECKING ON" switch, two things could happen: your code could be
correct, or it could have errors. If it's correct, yay! Parse it again and be
done with it. If it's not correct, though, you (the editor writer) have the
very fun task of trying to smartly parse enough of it to be helpful (and/or to
try and reconcile differences between the current and previous versions, re-
using as much as you could from the previous version).

The root of the problem(s) above are this:

\- many "semantic" edits (like any refactoring) change many different parts of
the program; this is mostly independent of whether or not you're looking @ an
abstract syntax tree or the raw text, and mostly independent of how structured
or not your code style is. If you write a function, any place it gets used is
a long-distance-dependency in the a.s.t.

\- your structure editor needs to be smart wrt to such multiple-step editing
transactions, while retaining enough power to provide actual benefits over a
plaintext editor.

\- And: not all languages have structures that are well-defined enough to make
building a structure editor a tractable undertaking. C++ is far and away the
worst offender here: you can approximate the grammar pretty easily, but a
"correct" parsing is pretty nontrivial (see here for some details:
<http://www.nobugs.org/developer/parsingcpp/> ).

A good design for a structure editor would somehow make sure that that
"approximateness" of the grammar didn't handicap you, in the case that what
you were trying to do was running into one of the "approximations".

Lastly: I'm not trying to discourage you, just trying to warn you of the
challenges people who go down this route tend to encounter. I'd spend some
time thinking about the stuff above, but a lot more time thinking about a
sensible user interface for working with your editor; any workable solution
you come up with is going to benefit immensely more from an intuitive-and-
efficient user interface than from getting the parsing-and-enforcement-model
100% correct on day one.

It's football time got to go.

~~~
Hexstream
I acknowledge the validity of the concerns you're bringing up here.

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.

~~~
frig
I'm pretty sure "a text editor that bitches at you and won't let you type
incorrect stuff" isn't the answer and I'm pretty sure "a text editor in which
clippy pops up and is like "you seem to have forgotten a parentheses. Can I
help you figure out where to put it" isn't the answer, either; if I had an
inkling about what a good ui would be I'd tell you or be working on this
myself, but I don't have any real insight beyond what I've shared.

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.

~~~
Hexstream
I didn't say the editor would bitch at you for entering incorrect stuff, it's
that for a lot of things it would be _impossible_ to enter incorrect stuff _by
design_ and it would make editing _easier and faster_ , not harder. If the
only way to enter parentheses into your s-expressions is to invoke the open-
sexp command by pressing the appropriate key, it's impossible that you'll
forget a parenthesis or have them nested improperly (unless the nesting is
syntactically correct). Correspondingly, to delete an element such as an
s-expression you'll invoke the "delete-element" command so it's impossible
that you'll just delete the last parenthesis of an s-expression without
deleting the rest of it.

~~~
frig
Sure, I was just enumerating obviously-wrong implementation ideas, not
suggesting that's what I thought you were doing.

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.

------
DavidSJ
<http://www.emacswiki.org/emacs/ParEdit>

------
tptacek
I'm reminded of <http://cr.yp.to/sarcasm/modest-proposal.txt>.

~~~
Hexstream
I'm not sure exactly what you mean here.

If 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

    
    
      (something (stuff)
        (something2 (stuff2)
          body1
          body2))
    

into:

    
    
      (something2 (stuff2)
        (something (stuff)
          body1
          body2))
    

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.

------
rw
Write a vim plugin for scheme!

~~~
Hexstream
Actually I'm thinking of writing my own (emacs-inspired) IDE. I can't stand
the idea of legacy considerations limiting the novelty of my thinking.

