Treefrog: a tool for writing code
Gus Hogg-Blake
22 December 2021
The problem with text & AST editors
I started researching and prototyping AST editors in 2017 after becoming frustrated with my editor's lack of understanding of code and the consequent need for me to perform repetitive text updates to carry out conceptually simple code changes. The idea of having an editor that fundamentally understood the structure of code – specifically its AST representation – seemed like a promising avenue to begin creating a better editor.
As you might guess though, AST editors haven't taken off, and there's a good reason: it's hard to design a user-friendly interface around them. There are cases where AST manipulations happen to overlap nicely with conceptual edits and thereby provide a much more rational and ergonomic interface than do traditional editors, but there are many more cases where they don't. It turns out that having to think in terms of AST manipulations all the time can be just as much of a chore as having to maintain the code's text representation.
I realised this fairly early on and, in recognition of it, my first prototype supported two modes: normal mode and AST mode. You could switch between them seamlessly and (the idea was) the visual representation remained pretty much the same – so you got the benefits of both without too much cost in switching. That project didn't take off for various reasons, and after coming back to the problem with a fresh mind recently I've come to believe that text and AST editors actually make the same fundamental mistake at some level, and that is designing around a data structure in the first place.
It seems logical to use the basic data structure as a starting point for the design of the commands that will ultimately edit the data structure, but I don't think it's possible to take this approach without the particular constraints of the data structure having an undue influence over how the UI ends up being laid out. Text editors feel very much like tools for navigating and manipulating rows and columns of text, and AST editors feel very much like tools for navigating and manipulating syntax trees.
Treefrog is based on the realisation that what we need is a tool for writing code. This sounds too obvious to be worth stating at first, but it's taken me a while to fully appreciate it and integrate it into the design process. When you edit code you're changing the function and organisation of a program. The thought processes are about solving a problem and expressing the solution in terms of the features and vocabulary of the language, and a well-designed editor should make it as easy as possible to express these thought processes as changes to the code. Text and ASTs are both useful representations for achieving this, but the data structures are ultimately irrelevant – all that matters is how easy and enjoyable it is to get the job done.
Designing for thought processes, not data structures
What is the fundamental purpose of a code editor? I think it's something like: mapping the thought processes involved in programming and then creating an intuitive and ergonomic interface for expressing them. Using this as a guiding principle, Treefrog's interactions are explicitly designed around the following types of thought process:
- Linguistic/logical (initial drafting): prose-like thought processes where the code is written and thought about mostly as a sequence of logical and declarative statements. This is well accommodated by traditional code editors – and Treefrog's normal mode – with features such as snippets and auto-complete.
- Linguistic/logical (editing): as above, but involves selecting a range(s) of text before modifying it, and usually only small pieces of code are modified at a time. This is handled reasonably well by traditional editing, but having a dedicated mode for syntax-aware navigation and selection opens up new possibilities for making the selection process faster and more intuitive. For example, with an if statement selected, the command to change the condition could be simply cc ("change condition").
- Spatial (moving things): drag-and-drop is the obvious choice for implementing these.
- Rephrasing & reorganising: these are updates to the display or organisation of the code that don't affect the logic of the program, and so the thought processes are focused on the mechanics of the language and the details of the text. Most editors add these features on top of a standard text editing interface, behind context menus or less-ergonomic key combinations.
Tree mode
To enable concise and ergonomic expression of these thought processes, Treefrog has a new mode, called Tree mode, that works on the structure and meaning of the code and carries out the corresponding text edits automatically. Some of the main concepts and features implemented so far are:
- vi-like navigation and selections that work on the hierarchical structure (something I've referred to as the "z axis" based on the idea that traditional editors support the two row/col dimensions, but code also has a third dimension describing the nested structure of code elements). This is based on whole lines – I found that if you need to specify particular nodes within a line it quickly becomes easier just to select with the mouse or standard keyboard navigation. Currently s selects the containing block and d selects the current block's first child; and j and k go up and down as in vi.
- The mode switch key (Escape) can be pressed to toggle between modes, or used like a modifier key to "peek" into tree mode and issue quick tree edit commands or mouse actions from normal mode.
- Moving something (e.g. a
div
into anotherdiv
) is a single drag-and-drop – no need to draw a selection around it first. Working on whole lines makes it easy to think about and non-fiddly, and formatting is of course handled automatically, no need to think about exactly where an element starts and ends or having to clean up indentation/newlines afterwards. - Drag-and-drop actions have optional "pick options" and "drop targets" which augment the behaviour of the action, e.g. "move selection to new else statement [drop target] after if statement".
- Tree mode and normal mode work together via a simple convention around snippets: if a tree edit needs placeholders to be typed in, it inserts a snippet, and completing the snippet puts you back in tree mode (unless you were only "peeking" tree mode, i.e. using the mode switch key as a modifier key).
- Refactoring and reformatting cases are handled by either generic or language-specific keyboard or mouse commands, or in language- and node-specific context menus (accessible via either keyboard or mouse).
For example, to wrap the current selection in an if statement you would do: Esc-w (for "wrap"), if
followed by Tab (to insert the if
snippet), fill in the condition, tab to the body, then press Alt-i to insert the original selection.
Try it out in the web editor or pre-order to get the Electron version.