Skip to content

Info: Editor Structure

Matt Carroll edited this page Apr 6, 2021 · 1 revision

SuperEditor is composed of the following core abstractions:

  1. Document
  2. Document Editor
  3. Document Selection
  4. Document Layout
  5. Document Interactor
  6. Document Composer
  7. Editor

Each of these pieces are discussed below.

Document

The document establishes the logical structure of content.

A document is a series of nodes. Each node has an ID. Clients can query for a node by ID or node index.

Working with content requires an ability to specify a location within a document. A "document position" refers to a singular location within a document, e.g., node "abcde" with node position "text offset of 6". The type of data for the node position is specific to the type of node in question, e.g., paragraph node, image node, etc.

Documents also require the concept of "selection". Even non-editable documents. A selection is a directional span from one document position to another. This selection might go from an earlier node to a later node (top-to-bottom), or from a later node to an earlier node (bottom-to-top). In keeping with Flutter's representation of text selection, the beginning of a document selection is referred to as the "base", while the end of a document selection is referred to as the "extent". When selecting from top to bottom, the base is higher in the document than the extent. When selecting from bottom to top, the extent is higher in the document than the base.

The combination of node structure, document positions, and document selection, collectively represent the logical "document".

Document Editor

The document editor is exclusively responsible for altering the underlying document.

Centralizing the ability to alter a document is critical because that centrality is what facilitates undo/redo functionality. All edits need to be serialized in one place so that actions can be undone. This essentially implies that the document editor implements event sourcing for all document changes.

Document Layout

The document layout is responsible for displaying a document, as well as answering questions about that layout.

Consider a caret sitting in a paragraph. The user presses the "up" key. The user expects the caret to move to the line above. But what is the line above? What's the text position directly above the current caret position? The answer to this question fundamentally requires knowledge about text layout because the answer is tied to where lines are wrapped, and thus the width of the paragraph.

Consider a user dragging across the screen. The user expects a portion of the document to be selected. But which portion? How do we know what the user dragged over? The answer to this question fundamentally requires knowledge of visual sizes and positions.

The document layout answers questions about how (x,y) positions map to document positions.

Document Interactor

The document interactor interprets user input for the purpose of document selection and editing.

Tap and drag selections, as well as all keyboard input flows through the document interactor.

It might be tempting to merge the concept of a document interactor with the document layout or some other component, however, every editor is likely to want slightly different rules for user interaction. Therefore, this behavior is encapsulated in the document interactor to reduce the work needed to replace the behavior. If the interactor were merged with the document layout then the entire document layout would need to be rewritten just to implement different rules for what double-tap and triple-tap mean for selection.

Document Composer

The document composer holds the document selection that changes over time.

TODO: does the document composer hold the text style mode? should that capability be abstracted to include things like alignment? or should those properties move to some other component?

Editor

The editor brings all of the above pieces together to present a visual editor.

The Editor widget is given a DocumentEditor, which internally references a Document. The Editor internally creates a DocumentComposer and builds a widget tree with a DocumentLayout and a DocumentInteractor.

The Editor is a widget, and therefore it can be composed within a broader app.