/puredit

A projectional editor that uses textual code as its source of truth.

Primary LanguageTypeScriptMIT LicenseMIT

Puredit

Purism, referring to the arts, was a movement (...) where objects are represented as elementary forms devoid of detail. (Wikipedia)

The Purist editor is a projectional editor that uses textual code as its source of truth. Unlike other projectional editors, Puredit is based on the assumption that parsers are fast enough to continuously react to changes to a document and update the projections accordingly. Projections are derived from patterns on the abstract syntax tree. To make the definition of patterns easy, they are parsed from actual code snippets in the target language.

Project structure

  • apps/: executable applications and demos
    • example/: an example of a projectional data manipulation DSL based on the TypeScript programming language, which is also available at https://thesis.korz.tech/
    • generate/: an application for generating code pattern templates and projection components from samples
    • jupyter/: an example of a projectional DSL for data analysis of Excel sheets, based on Python and integrated into JupyterLab
    • parser-playground/: an experimentation playground used for debugging our pattern matching algorithm
    • python-dsl/: an example of a projectional data manipulation DSL based on the Python programming language
  • packages/: reusable packages that together are used to create a projectional editor
    • codemirror-typescript/: a client-side language server for the TypeScript programming language integrated into CodeMirror 6, based on prisma/text-editors
    • parser/: a library for defining syntax tree patterns through code templates and finding nodes matching these patterns in a program's syntax tree
    • projections/: an extension for the CodeMirror 6 editor for detecting syntax tree patterns in a document and replacing them with interactive projection widgets implemented as Svelte components
    • simple-projection/: an alternative for the Svelte-based implementation components, limited to simple linear texts widgets with string fields

Installation and development

This projects requires Node 16 LTS and npm 8. Node 18 may work but has not been tested. Then, install the dependencies using npm install.

Develop

To develop all apps and packages, run the following command:

npm run dev

This will start development servers for apps/example, apps/parser-playground and apps/python-dsl. For the JupyterLab example, see the next section.

Build

To build all apps and packages, run the following command:

npm run build

Linting and type checking

To lint and typecheck all apps and packages, run the following command:

npm run lint

TypeScript DSL example

To build and run the TypeScript DSL example, run the following commands:

npm install
npm -w apps/example run dev

Alternatively, the TypeScript DSL example can be built and run using Docker:

docker build -t puredit-example -f apps/example/Dockerfile .
docker run -p 3000:80 puredit-example

Afterwards, the example can be accessed in a browser on http://localhost:3000.

JupyterLab example

To build and run the JupyterLab DSL example, run the following commands:

Virtual environment

# Create and activate Python virtual environment
python3 -m venv .venv
source .venv/bin/activate

# Install dependencies and our Jupyter extension
npm install
pip install -r apps/jupyter/requirements.txt
npm -w apps/jupyter run build:prod
pip install apps/jupyter

# Start JupyterLab
cd apps/jupyter/example
jupyter lab

Docker

Alternatively, the JupyterLab example can be built and run using Docker:

docker build -t puredit-jupyter -f apps/jupyter/Dockerfile .
docker run -p 8888:8888 puredit-jupyter

Afterwards, JupyterLab can be accessed in a browser on http://localhost:8888.

Creating new projections

To create new projection components and their code pattern templates, you can either start from scratch or use the output of our sample-based generator as foundation.

From scratch

To create a new projection from scratch, you need to implement two things: an entrypoint for your projection that defines the pattern and the projection, as well as a Svelte component providing the implementation for your projection widget. A good starting point is to copy and adapt one of the projections from apps/example/src/projections. Use the arg, block, and contextVariable template helpers from the @puredit/parser inside your code templates to create dynamic patterns.

Using the generator

The sample-based generator takes a text files containing both code and projection samples as input. All samples are divided by an empty line. The code samples are divided from the projection samples by a line containg only --- as content. You can look at the datasets in apps/generate/examples as reference. Execute the generator as follows, replacing the argument placeholders with your according values:

npm start -w apps/generate -- \
    --language <language> \
    --parser-name <parser-name> \
    --parser-module <parser-module> \
    --projection-name <projection-name> \
    --samples <samples> \
    --target-dir <target-dir>

Note that the path of parser-module must be relative to the path of target-dir. For example, to generate the pattern and projections files removeProjection.ts and RemoveProjection.svelte for apps/generate/examples/ts-remove.txt with target app apps/examples:

npm start -w apps/generate -- \
    --language ts \
    --parser-name tsParser \
    --parser-module './parser' \
    --projection-name remove \
    --samples apps/generate/examples/ts-remove.txt \
    --target-dir apps/example/src/projections

On Windows Powershell, \ does not work for multiline commands, so write everything in one line instead:

npm start -w apps/generate -- --language ts --parser-name tsParser --parser-module "./parser" --projection-name remove --samples apps/generate/examples/ts-remove.txt --target-dir apps/example/src/projections

Similarly, to generate the pattern and projections files takeProjection.ts and TakeProjection.svelte for apps/generate/examples/py-take.txt with target app apps/jupyter:

npm start -w apps/generate -- \
    --language py \
    --parser-name pythonParser \
    --parser-module './parser' \
    --projection-name take \
    --samples apps/generate/examples/py-take.txt \
    --target-dir apps/jupyter/src/projections

Afterwards, register the new projection by adding them to the lists in apps/example/src/projections/index.ts or respectively apps/jupyter/src/projections/index.ts.

Integrating the projectional editor

To integrate the projectional editor into your own project, see apps/example, in particular apps/example/src/Editor.svelte. There, a new CodeMirror editor is created and mounted that is making use of our projectional editing extension. For a more specialized example, see apps/jupyter. In apps/jupyter/src/editor.ts, a wrapper for CodeMirror 6 is provided that makes it compatible to JupyterLab's own editor. On top of this, apps/jupyter/src/index.ts provides a Jupyter frontend extension that replaces Jupyter's own editor with our projectional editor.