Freon Documentation (version 0.5.0)

Writing Projections in TypeScript

The editor is always an implementation of the interface PiProjection. The implementation generated by Freon is located in the file ~/picode/editor/gen/<yourLanguageName>ProjectionDefault.ts. It holds projections for all concepts in the language. When a projection is given in the .edit file this is the one that will be in the implementation, when no projection is defined, a default projection will be generated.

As you can read in the Freon Editor Framework , all projections are based on boxes. In the next few steps we will show you how to build a hierarchy of boxes to project your AST nodes, and how to style these boxes according to your wishes.

The projections in this section are available in the Freon-example.

Customize by implementing getBox()

In order to customize the editor you need to implement the getBox(...) method in the file ~/picode/editor/Custom<yourLanguageName>Projection.ts. For every concept that needs a customized projection, it should return a Box object. For all other concepts it should simply return null. This way Freon will know that you did not define a projection yourself and will use the projection defined in ~/picode/editor/gen/<yourLanguageName>ProjectionDefault.

// tutorial-language/editor/CustomEntityProjection.ts#L40-L43

getBox(element: PiElement): Box {
    // Add any handmade projections of your own before next statement
    return null;
}
Use another filename and/or location
You can rename the file `~/picode/editor/CustomProjection.ts` and/or put it in another location. In that case, you need to adjust the file `config/ProjectItConfiguration`.

How to Create a Box Object

Step 1 - Projecting a Simple Property

We start with building the projection for a simple property of type identifier: the name of the unit in our Entity language. In the metamodel this is represented by the value of the property name of class EntityModelUnit.

// tutorial-language/defs/LanguageDefinition.ast#L56-L61

modelunit EntityModelUnit {
    name: identifier;

    functions: EntityFunction[];
    entities: Entity[];
}

A reasonable choice for the projection of this property is a HorizontalListBox which holds a LabelBox with the name of the class, followed by the value stored in the variable name. The following code shows a method that returns this HorizontalListBox. This method should be called in the overall method getBox(...).

// tutorial-language/editor/CustomEntityProjection.ts#L45-L51

// Most simple model box
private createModelBox(model: EntityModelUnit): Box {
    return new HorizontalListBox(model, "model", [
        new LabelBox(model, "model-label", "Model"),
        new TextBox(model, "model-name", () => model.name, (c: string) => (model.name = c))
    ]);
}

When we start the editor based on this projection, we see the following:

Image 'demomodelname.png' seems to be missing
Figure 1. Simple Projection of a name property

Step 2 - Adding Style and a PlaceHolder

In version 0.5.0 this has been changed such that you can use CSS classes. More info follows…

Step 3 - Projecting a List

Next, we will add the entities property of the EntityModelUnit to the projection. The entities property is a list of Entity.

// tutorial-language/defs/LanguageDefinition.ast#L60-L60

entities: Entity[];

In the projection we add a LabelBox, to be shown before the list, and the list itself using a VerticalListBox to make sure that this list is displayed vertically. Note that the LabelBox is styled as a keyword, like the LabelBox in the previous step.

// tutorial-language/editor/CustomEntityProjection.ts#L67-L86

return new VerticalListBox(model, "model", [
    new HorizontalListBox(model, "model-info", [
        new LabelBox(model, "model-keyword", "Model", {
            style: styleToCSS(projectitStyles.keyword)
        }),
        new TextBox(model, "model-name", () => model.name, (c: string) => (model.name = c), {
            placeHolder: "<name>"
        })
    ]),
    new LabelBox(model, "entity-keyword", "Entities", {
        style: styleToCSS(projectitStyles.keyword)
    }),
    new VerticalListBox(
        model,
        "entity-list",
        model.entities.map(ent => {
            return this.rootProjection.getBox(ent);
        })
    )
]);

The projection of a single Entity is done using this.rootProjection.getBox(ent). This will call a separate function (here called createEntityBox) that also returns a Box, thus building a hierarchy of boxes. The use of this.rootProjection.getBox(ent), instead of directly calling createEntityBox, ensures that the proper projection for entity is used, following the rules laid down in customize projections.

We can track the hierarchy of boxes. First, have a look at the projection for Entity, which is defines as follows in the .ast.

// tutorial-language/defs/LanguageDefinition.ast#L26-L32

concept Entity implements Type {
    isCompany: boolean;
    attributes: AttributeWithLimitedType[];
    entAttributes: AttributeWithEntityType[];
    functions: EntityFunction[];
    reference baseEntity?: Entity;
}

Its projection is very similar to the projection of the EntityModel, showing the keyword Entity followed by its name and below all properties of the entity in a VerticalListBox.

// tutorial-language/editor/CustomEntityProjection.ts#L108-L127

private createEntityBox(entity: Entity): Box {
    return new VerticalListBox(entity,"entity",
        [
            new HorizontalListBox(entity, "entity-info", [
                new LabelBox(entity, "entity-keyword", "Entity", {
                    style: styleToCSS(projectitStyles.keyword)
                }),
                new TextBox(entity, "entity-name",
                    () => entity.name,
                    (c: string) => (entity.name = c),
                    { placeHolder: "<name>" })
            ]),
            new VerticalListBox( entity, "attribute-list",
                entity.attributes.map(att => {
                    return this.rootProjection.getBox(att);
                })
            )
        ]
    );
}

Next in the hierarchy of boxes is the projection of the elements of the attributes list. Once again, this projection is defined in its own function. Have a look at the .ast definition and the projection method. Here, we use a HorizontalListBox to show the property name, followed by a colon, followed by its type.

// tutorial-language/defs/LanguageDefinition.ast#L71-L74

concept AttributeWithLimitedType {
    reference declaredType: AttributeType;
    name: identifier;
}
// tutorial-language/defs/LanguageDefinition.ast#L56-L61

modelunit EntityModelUnit {
    name: identifier;

    functions: EntityFunction[];
    entities: Entity[];
}
// tutorial-language/editor/CustomEntityProjection.ts#L151-L188

public getAttributeBox(attribute: AttributeWithLimitedType): Box {
    return new HorizontalListBox( // tag::AttributeBox[]
      attribute,
      "Attribute",
      [
          new TextBox(
            attribute,"Attribute-name",
            () => attribute.name,
            (c: string) => (attribute.name = c as string),
          ),
          new LabelBox(attribute, "attribute-label", ": "),
          this.getReferenceBox(
            attribute,
            "Attribute-declaredType",
            "<select declaredType>",
            "AttributeType",
            () => {
                if (!!attribute.declaredType) {
                    return { id: attribute.declaredType.name, label: attribute.declaredType.name };
                } else {
                    return null;
                }
            },
            async (option: SelectOption): Promise<BehaviorExecutionResult>  => {
                if (!!option) {
                    attribute.declaredType = PiElementReference.create<AttributeType>(
                      EntityEnvironment.getInstance().scoper.getFromVisibleElements(attribute, option.label, "AttributeType") as AttributeType,
                      "Type"
                    );
                } else {
                    attribute.declaredType = null;
                }
                return BehaviorExecutionResult.EXECUTED;
            }
          )
      ]
    ); // end::AttributeBox[]
}

Step 4 - Adding Behavior

The projection so far is exactly that: a projection. There are no actions defined yet, which we need to enable the user to change the model and add elements to it. However, we do have the built-in default behavior of the editor:

  • Inside a TextBox the text can be edited.
  • Using the arrow keys the user can navigate the projection.
  • Using Ctrl-Arrow the user can navigate up and down the model/AST.
  • When an element is selected, it can be deleted with the DEL key.

The default behavior takes care of changing simple AST nodes and deleting both simple and complex AST nodes. Find out more about adding behavior in Writing Actions.