CodeAssist can show any Java file as a Scratch-style tree of interlocking blocks, edit it there, and write the changes straight back into the source — leaving every untouched line, comment, and bit of formatting byte-for-byte intact. The block view is a live projection of the shared DOM, not a parallel model.
This document explains how the projection and the edit round-trip work. For the SPI these pieces implement, see extension-points.md.
There is a single shared document and AST. The block tree is derived from it on demand and discarded;
it never becomes a second source of truth. That is what keeps "edit as code" and "edit as blocks"
perfectly in sync — both views are the same ParsedFile.
The projection engine turns a ParsedFile into a block tree by gap carving:
- Walk a node's children in source order.
- The literal source between child ranges is kept as read-only chrome.
- Each child is projected into a typed slot.
Because the chrome is the real text between children, the default serialization round-trips byte-for-byte — the engine never synthesizes separator text or guesses formatting.
A per-language block mapping decides which node kinds explode into structured blocks and which collapse to an editable opaque text slot. The Java mapping decomposes statements and the key expressions (control flow, calls, member/name references, infix operators, literals); containers become a single foldable list slot; everything else stays editable text. Two notable cases:
- Call collapse. A pure-name receiver (e.g.
System.out) becomes an editablequalifierfield in the call block's header. - Fluent chains. A chain such as
sb.append(x).append(y)flattens into one call block withname/name1/… fields and one argument slot per argument, with the real source between them as chrome.
Each slot carries a ValueKind (boolean / number / string / type / object / unknown) describing what the
position expects, and each block carries the kind it produces. These drive Scratch-style socket shapes
(hexagon = boolean, pill = number, and so on) applied to both empty sockets and the blocks filling them.
Kinds are inferred syntactically (literals, operators, casts, typed initializers, conditions), and the
engine consults a ValueKindOracle hook first so a semantic resolver can refine them later.
A block edit (set a field, replace with text, delete, insert a template, move, wrap) is compiled to a
minimal DocumentEdit — the smallest change to the source that realizes it. Untouched code is never
rewritten. Projection ids are deterministic per text: the edit pipeline re-projects the same buffer to
resolve references and holds no state, so the host passes the exact text the displayed tree was
projected from.
The on-screen editor is a Blockly/Sketchware-style canvas: solid, category-colored puzzle blocks with
notch/bump connectors, C-shaped control blocks wrapping a colored body rail, the typed value sockets
above, segmented rendering of fluent chains, inline completion while editing a socket or field (reusing
the code editor's completion engine, re-ranked for the socket type), index-backed palette search for
draggable templates, and drag-and-drop (long-press to move or delete, drag a palette template to
insert). Every gesture maps to a BlockEdit, so it all flows back through the same surgical
projection → document-edit pipeline.