Each Proxima layer is structured in the same way. It consists of two components: a presentation module (with a name ending in
) that defines a function
, and an interpretation module (name ending in
) that declares a function
. As described in the architecture overview
, Proxima has 6 data levels with 5 layers in between:
Evaluator / Reducer
Presenter / Parser
Layouter / Scanner
Arranger / Unarranger
Renderer / Gesture intepreter
Each level type is defined in a Haskell module:
Because the Document and Enriched document type depend on the editor instance, the datatypes in
have type parameters
, which denote the actual document and enriched document types. The actual types are declared in the instance-specific module
, which is generated from the
The following Haskell modules implement the various layer components:
Starting with the edit gesture (which is regarded as an edit operation on the rendering for symmetry), the interpretation component of each layer maps the lower-level edit operation onto a higher-level edit operation. At the top the edit operation is sent downward, and each presentation component maps the higher-level edit operation onto a lower-level one. At the bottom left, this yields an edit operation on the HTML rendering.
In reality, a list of edit operations is passed up and down the architecture, and each component maps the head of the list onto a list of edit operations on the adjacent level. This way, each component can insert extra edit operations in the list. The reason for this is that sometimes a component cannot process an edit operation before some other operation is performed. For example, a navigation operation that moves out of the currently viewed area, may cause a previously unarranged part of the presentation to be arranged. After each cycle, the head of list (which is an update on the HTML rendering) is removed and another cycle is started if the list is not empty.
Edit operations in the presentation direction are in a datatype
), in the interpretation direction, the datatype names get a prime:
Layer data flow
The data flow in a layer can be seen from the types of
presentIO :: LayerState -> UpperLevel -> LowerLevel -> [EditUpperLevel] ->
IO ([EditLowerLevel], LayerState, UpperLevel)
interpretIO :: LayerState -> LowerLevel -> UpperLevel -> [EditLowerLevel] ->
IO ([EditUpperLevel], LayerState, LowerLevel)
In the sources, these functions sometimes have extra parameters, but when given these extra parameters, the types are as above. As mentioned above, only the head of that list is processed. Although it may seem that the previous value of the lower level is not needed, it is passed as a parameter, since the lower level may contain extra state that cannot be computed from the upper level and the edit operation on it. Extra state is discussed in detail in Chapter 4
of Martijn Schrage's PhD thesis.
The return values of
are the list of edit operations on the lower level (only the head was actually mapped by present, the rest is mapped onto a lower level edit operation by the process of wrapping, explained below) together with the updated layer state and the updated upper level.
As was mentioned above, some layers have extra parameters at the front. The reason for this is that the mappings at several layers are specific to the editor instance and need to be passed to the generic system as parameters. Furthermore, some layers require the settings record which contains instance-specific settings. This record is also passed as a parameter. When the presentation or interpretation functions are applied to their extra parameters, they have the types shown above.
As an example, we can look at the presentation component. If we omit the type parameters, for brevity, the type of
presentIO :: PresentationSheet ->
LayerStatePres -> EnrichedDocLevel -> PresentationLevel -> [EditEnrichedDoc'] ->
IO ([EditPresentation'], LayerStatePres, EnrichedDocLevel)
Which, when the
parameter is applied, corresponds to the type above.
Each level has a datatype for edit operations in the interpretation direction (
) and the presentation direction (
stands for the abbreviated level name). The
module for each level declares two types
(with underscores), which are used to declare
in the module
. The reason for this is that the edit operation types need an extra parameter in order to support wrapping edit operations, which is explained below.
datatypes for the edit operations vary on each level, but a number of constructors is always present:
data Edit<Level> = Set<Lvl>_ (<Level>Level ...)
| Skip<Lvl> Int
| Wrap<Lvl> wrapped
At the layout level, for example, we have:
data EditLayout_ wrapped doc enr node clip token
= SetLay (LayoutLevel doc enr node clip token)
| SkipLay Int
| WrapLay wrapped
operation causes the source level (upper level for presentation, lower level for interpretation) to be replaced by the argument of set operation.
constructor is used in the interpretation direction to skip all layers above the current one and resume the presentation at the same layer. For example, when typing in an unparsed document, all layers above the layout layer can be skipped until the document is parsed.
In order to skip the layers above, an interpretation component can return a
as the upper level edit operation. The interpretation component above does not compute any mapping but simply returns a skip operation with the number increased by one. At the top, the skip operation is sent down through the presentation components, which each decrease the number until 0 is reached. At that point (which is in the same layer that yielded the skip 0), normal presentation is resumed.
constructor is used for casting edit operations between layers. This allows a component to yield an edit operation not on the level immediately above it (on interpretation) or below it, but somewhere else in the cycle. A wrapped edit operation is passed around to the next layer until it arrives at the destination layer.
The argument of
in the datatype
is the type parameter
, but its actual type in an edit operation is
(Wrapped doc enr node clip token)
. The reason for the type variable is that because
(Wrapped doc enr node clip token)
depends on all edit operation datatypes, it cannot be used directly without introducing import cycles. Therefore, each edit operation datatype gets an underscore in its name and a type parameter
, and in the module
, the actual edit operation types are declared. For example, for
has the type declaration:
type EditDocument doc enr node clip token =
EditDocument_ (Wrapped doc enr node clip token) doc enr node clip token
Wrapping is implemented with a type class that has functions
. In order to wrap an edit operation to an edit operation of another type, the function
cast = unwrap . wrap
can be used.