Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Leaf Projectors #1218

Draft
wants to merge 68 commits into
base: dev
Choose a base branch
from
Draft

Leaf Projectors #1218

wants to merge 68 commits into from

Conversation

disconcision
Copy link
Member

@disconcision disconcision commented Feb 20, 2024

First stage of projectors. Implements a simple atomic code projection framework intended to be used for:

  • Term-level code folding (part of this PR)
  • Module collapsing
  • Type Hole inference displays
  • Non-splicing inline livelits
  • Hiding parts of code in live views e.g. function bodies

This PR implements two kind of leaf projector:

  • Fold: Expression-level code folding. Currently fold can be evoked by pressing Cmd-E, which will un/fold the currently indicated term. This currently only supports convex tiles (see details below)
  • Infer: Replaces an expression/hole with the expected type at that position. Currently you can toggle this by clicking on a Fold. Infer otherwise behaves similarly to fold (you can remove it with Cmd-E)

How to add a new projector:

The meat of adding a new projector consists of:

  • Model Type: Choosing a model type to represent that projector's state
  • Implementing a module of type Zipper.ProjectorCore to determine the projector's logic
  • Implementing a module of type ProjectorViewModule to determine the projector's view
  • Adding new actions or update logic to ass/modify/remove the new projector

Roadmap for adding a new projector:

  1. Add a model type for the projector to ZipperBase.re alongside fold and infer
  2. Add that model type to the sum type ZipperBase.projector
  3. Add a new file for the core logic module, based on (eg) FoldProjectorCore.re
  4. Add that module to the switch in Projector.to_module
  5. Add any new actions for that projector to Action.project
  6. Handle those actions in ProjectorPerform.go
  7. Add a new file for the view module, based on (eg) FoldProjectorView.re
  8. Add that module to the switch in ProjectorsView.to_module

Projector model types and persistence of projector state

The model type represents the persistent state of a projector which is serialized alongside the program zipper. If your model type involves heavy state making user of substructural sharing, or stores functions, you'll need to implement custom serialization. The model state is stored in the projector map, which is a map of IDs to zippers. Hazel UUIDs form the link between the syntax and projectors, so any action which disrupts IDs, for example, modifying a token or cutting or copying syntax will not (currently) retain projectors; all projector state will be lost in such cases. Note that it's possible to destroy a projector on an atomic literal/variable by causing that atom to merge with another, e.g. if {} denotes projector then given 1+{2}, backspacing the + results in an unprojected 12. This is somewhat artifactual of the implementation decision to use a new id in such cases, rather than the id of one of the initial tiles. This also applies to projectors on holes.

Projector core logic module

type t;
let projector: projector;
let data: t;
let placeholder_length: unit => int;
let can_project: Piece.t => bool;
let update: projector_info => projector;

The type t should be your model type. The data field is the projector state, and the projector field is an implementation detail; follow the pattern of existing projector modules.

The placeholder_length method must return an integer corresponding to the number of characters your projector's view will occupy in the syntax. Currently only single-line projectors are supported. This must be kept in sync with your projector's view logic.

The can_project method is a predicate to determine what kinds of syntax your projector can apply to. Currently only single pieces can be projected, not segments or slots. This means that projection is currently limited to terms which form convex tiles, like atoms, parentheses, and case expressions. This predicate is not currently automatically enforced; you must include it in your action logic manually.

The update method is automatically called whenever the syntax is edited to supply your projector with semantic information. Currently projector_info contains only static cursor info. If you want to more information here, you'll need to add the relevant type(s) to ZipperBase.projector_info, and make sure this information gets populated in Update.re.

Projector view module

type t;
let data: t;
let normal: (Id.t, FontMetrics.t, UpdateAction.t => Ui_effect.t(unit), Measured.measurement) => Node.t;
let indicated: (Id.t, FontMetrics.t, UpdateAction.t => Ui_effect.t(unit), Measured.measurement) => Node.t;
let key_handler: (Id.t, Key.t) => option(UpdateAction.t);
let ci_string: unit => string;

The type t and data field should be exactly as in the corresponding core logic module.

The normal and indicated methods represent how your projector is rendered when not indicated / indicated by the caret respectively. The currently means precisely when the caret is directly adjacent to a projector. Projector actions are currently globally scoped actions but this will likely be limited before merge to only be able to effect either the projector's own model, or the syntax under the projector.

The key_handler method allows overriding keyboard input precisely when the caret is directly adjacent to a projector. When this function returns None, keyboard input is escaped to the parent editor.

The ci_string method provides a single-character indication on the right side of the cursor inspector bottom bar that a projector is currently indicated. This is a stub for a more general UI for overloading the CI to show more or modified information when a projector is indicated.

Progress

Features:

  • View: Text layer: Render placeholder instead of projected term
  • View: Deco layer: Render projected UI over placeholder
  • Action: Move: Skips over the projected term
  • Action: Select: Selects through and over the projected term
  • Action: Indicated projections override keyboard handler
  • View: Deco layer: Indicated deco for projected UI
  • Action: Update syntax under projector

Bugs:

  • Projector statics update is delayed one action
  • Clicking on projector only seems to register if caret is on right side
  • Action: Can't properly fold more than one term in the same segment
  • Action: Movement into concave side of folded infix/prefix/postfix crashes
  • Action: Under some conditions moving/selecting past a fold in a nested context skips to end of program
  • In school mode, typechecking and eval are using display version of terms... see TODOs

Todo:

  • Cleanup: editor state/meta
  • Deal with indication in a more principled way than the find_p hack
  • Core: Factor out projectors into first class modules
  • Action: Gate folding on tile shape (prevent nonconvex)
  • View: Folded term decoration should be hexagon shaped probably
  • Move positioning code outside specific view fns
  • Internalize actions
  • Add proper internal action type
  • Try to remove statics from action
  • Cleanup/rename syntax_map
  • Cleanup TODOs

@cyrus- cyrus- changed the title Leaf Projectors WIP Leaf Projectors Mar 1, 2024
@cyrus- cyrus- added the in-development for PRs that remain in development label Mar 1, 2024
@cyrus- cyrus- mentioned this pull request Mar 1, 2024
disconcision and others added 30 commits May 16, 2024 23:29
…individual projector cores and views to their own files
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in-development for PRs that remain in development
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants