diff --git a/argocd/base/statefulset.yaml b/argocd/base/statefulset.yaml index 040334c7..d5f3dce2 100644 --- a/argocd/base/statefulset.yaml +++ b/argocd/base/statefulset.yaml @@ -26,7 +26,7 @@ spec: # Note: use the *dev* version of the package here, so that # PRs can deploy `primer-service` container images that have # not yet been merged to `primer` `main`. - image: ghcr.io/hackworthltd/primer-service-dev:git-1145eb89eb85354f67ec3cc5e25f59ef092a6c39 + image: ghcr.io/hackworthltd/primer-service-dev:git-f35380015a14db87c5f330a84a5830adc51e1cbb ports: - containerPort: 8081 env: diff --git a/flake.lock b/flake.lock index 81edc1c0..19971d1c 100644 --- a/flake.lock +++ b/flake.lock @@ -1678,17 +1678,17 @@ "pre-commit-hooks-nix": "pre-commit-hooks-nix_4" }, "locked": { - "lastModified": 1685183219, - "narHash": "sha256-kYzYZW/WTGzKz47GctjNTLOawhwC1jSVowaWKIPuoa8=", + "lastModified": 1685528982, + "narHash": "sha256-nxMQSO+SbRXxe/kpRZYfKVeiG5+ok2+wSulUBT35BjM=", "owner": "hackworthltd", "repo": "primer", - "rev": "1145eb89eb85354f67ec3cc5e25f59ef092a6c39", + "rev": "f35380015a14db87c5f330a84a5830adc51e1cbb", "type": "github" }, "original": { "owner": "hackworthltd", "repo": "primer", - "rev": "1145eb89eb85354f67ec3cc5e25f59ef092a6c39", + "rev": "f35380015a14db87c5f330a84a5830adc51e1cbb", "type": "github" } }, diff --git a/flake.nix b/flake.nix index d8b857b5..87099e1f 100644 --- a/flake.nix +++ b/flake.nix @@ -16,7 +16,7 @@ # Note: don't override any of primer's Nix flake inputs, or else # we won't hit its binary cache. - primer.url = github:hackworthltd/primer/1145eb89eb85354f67ec3cc5e25f59ef092a6c39; + primer.url = github:hackworthltd/primer/f35380015a14db87c5f330a84a5830adc51e1cbb; flake-parts.url = "github:hercules-ci/flake-parts"; }; diff --git a/src/Actions.tsx b/src/Actions.tsx index aef8033a..fd7c35d8 100644 --- a/src/Actions.tsx +++ b/src/Actions.tsx @@ -82,6 +82,16 @@ export const actionName = ( return prose("r"); case "RenameDef": return prose("r"); + case "RenameType": + return prose("r"); + case "AddCon": + return prose("+"); + case "RenameCon": + return prose("r"); + case "RenameTypeParam": + return prose("r"); + case "AddConField": + return prose("+"); } }; @@ -171,6 +181,16 @@ export const actionDescription = ( return "Rename this type variable"; case "RenameDef": return "Rename this definition"; + case "RenameType": + return "Rename this type"; + case "AddCon": + return "Add a constructor to this type"; + case "RenameCon": + return "Rename this constructor"; + case "RenameTypeParam": + return "Rename this parameter"; + case "AddConField": + return "Add a parameter to this constructor"; } }; @@ -246,5 +266,15 @@ export const actionType = (action: NoInputAction | InputAction): ActionType => { return "Primary"; case "RenameDef": return "Primary"; + case "RenameType": + return "Primary"; + case "AddCon": + return "Primary"; + case "RenameCon": + return "Primary"; + case "RenameTypeParam": + return "Primary"; + case "AddConField": + return "Primary"; } }; diff --git a/src/App.tsx b/src/App.tsx index 802f43ce..3f773acc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -215,16 +215,16 @@ const AppNoError = ({ .sort((a, b) => cmpName(a.name, b.name)) .map((d) => d.name.baseName), types: p.module.types - .sort((a, b) => cmpName(a, b)) - .map((t) => t.baseName), + .sort((a, b) => cmpName(a.name, b.name)) + .map((t) => t.name.baseName), importedDefs: p.imports .flatMap((m) => m.defs) .sort((a, b) => cmpName(a.name, b.name)) .map((d) => d.name.baseName), importedTypes: p.imports .flatMap((m) => m.types) - .sort((a, b) => cmpName(a, b)) - .map((t) => t.baseName), + .sort((a, b) => cmpName(a.name, b.name)) + .map((t) => t.name.baseName), }} onClickDef={(defName, _event) => { if (scrollToDefRef.current != undefined) { @@ -304,22 +304,79 @@ const AppNoError = ({ {...defaultTreeReactFlowProps} {...(selection && { selection })} onNodeClick={(_e, node) => { - if (!("nodeType" in node.data)) { + // TODO move selection conversion in to tree component? + // we could have `onClick` take it as an arg + if (node.type == "primer-typedef-name") { + setSelection({ + tag: "SelectionTypeDef", + contents: { def: node.data.def }, + }); + // console.log("clicked type name"); + } else if (node.type == "primer-def-name") { setSelection({ - def: node.data.def, + tag: "SelectionDef", + contents: { def: node.data.def }, + }); + } else if (node.type == "primer-typedef-param") { + setSelection({ + tag: "SelectionTypeDef", + contents: { + def: node.data.def, + node: { + tag: "TypeDefParamNodeSelection", + contents: node.data.name, + }, + }, + }); + } else if (node.type == "primer-typedef-cons") { + setSelection({ + tag: "SelectionTypeDef", + contents: { + def: node.data.def, + node: { + tag: "TypeDefConsNodeSelection", + contents: { con: node.data.name }, + }, + }, }); } else { + console.log(1, node.id); const id = Number(node.id); // Non-numeric IDs correspond to non-selectable nodes (those with no ID in backend) e.g. pattern constructors. if (!isNaN(id)) { - setSelection({ - def: node.data.def, - node: { id, nodeType: node.data.nodeType }, - }); + // if (node.data.nodeType != "typedefFieldNode") { + // if (!("typedefFieldNode" in node.data.nodeType)) { + if (typeof node.data.nodeType == "string") { + setSelection({ + tag: "SelectionDef", + contents: { + def: node.data.def, + node: { meta: id, nodeType: node.data.nodeType }, + }, + }); + } else { + setSelection({ + tag: "SelectionTypeDef", + contents: { + def: node.data.def, + node: { + tag: "TypeDefConsNodeSelection", + contents: { + con: node.data.nodeType.typedefFieldNode.con, + field: { + index: node.data.nodeType.typedefFieldNode.nChild, + meta: id, + }, + }, + }, + }, + }); + } } } }} defs={p.module.defs} + typeDefs={p.module.types} level={level} /> @@ -380,7 +437,9 @@ const AppNoError = ({ ) : null} {showCreateTypeDefModal ? ( t.baseName))} + moduleTypeDefNames={ + new Set(p.module.types.map((t) => t.name.baseName)) + } open={showCreateTypeDefModal} onClose={() => setShowCreateTypeDefModal(false)} onCancel={() => setShowCreateTypeDefModal(false)} diff --git a/src/components/TreeReactFlow/TreeReactFlow.stories.tsx b/src/components/TreeReactFlow/TreeReactFlow.stories.tsx index bdacce8d..6705f13e 100644 --- a/src/components/TreeReactFlow/TreeReactFlow.stories.tsx +++ b/src/components/TreeReactFlow/TreeReactFlow.stories.tsx @@ -30,6 +30,7 @@ const props: Omit = { ...defaultTreeReactFlowProps, scrollToDefRef: { current: undefined }, forestLayout: "Vertical", + typeDefs: [], }; const emptyTypeTree = (nodeId: string): Tree => { @@ -90,7 +91,10 @@ export const Tree1: ComponentStory = ( treeSized({ ...args, defs: [def1], - selection: { def: def1.name, node: { nodeType: "BodyNode", id: 100 } }, + selection: { + tag: "SelectionDef", + contents: { def: def1.name, node: { nodeType: "BodyNode", meta: 100 } }, + }, }); export const Tree2: ComponentStory = ( args: TreeReactFlowProps @@ -105,7 +109,10 @@ export const Tree3: ComponentStory = ( treeSized({ ...args, defs: [def3], - selection: { def: def3.name, node: { nodeType: "BodyNode", id: 301 } }, + selection: { + tag: "SelectionDef", + contents: { def: def3.name, node: { nodeType: "BodyNode", meta: 301 } }, + }, }); export const Tree4: ComponentStory = ( args: TreeReactFlowProps @@ -113,7 +120,10 @@ export const Tree4: ComponentStory = ( treeSized({ ...args, defs: [def4], - selection: { def: def4.name, node: { nodeType: "BodyNode", id: 409 } }, + selection: { + tag: "SelectionDef", + contents: { def: def4.name, node: { nodeType: "BodyNode", meta: 409 } }, + }, }); export const Tree5: ComponentStory = ( args: TreeReactFlowProps @@ -121,7 +131,10 @@ export const Tree5: ComponentStory = ( treeSized({ ...args, defs: [def5], - selection: { def: def5.name, node: { nodeType: "BodyNode", id: 503 } }, + selection: { + tag: "SelectionDef", + contents: { def: def5.name, node: { nodeType: "BodyNode", meta: 503 } }, + }, }); export const AllTrees: ComponentStory = ( args: TreeReactFlowProps @@ -129,7 +142,10 @@ export const AllTrees: ComponentStory = ( treeSized({ ...args, defs: [def1, def2, def3, def4, def5], - selection: { def: def3.name, node: { nodeType: "BodyNode", id: 301 } }, + selection: { + tag: "SelectionDef", + contents: { def: def3.name, node: { nodeType: "BodyNode", meta: 301 } }, + }, }); export const OddAndEven: ComponentStory = ( args: TreeReactFlowProps @@ -137,7 +153,10 @@ export const OddAndEven: ComponentStory = ( treeSized({ ...args, defs: oddEvenDefs, - selection: { def: def5.name, node: { nodeType: "BodyNode", id: 5 } }, + selection: { + tag: "SelectionDef", + contents: { def: def5.name, node: { nodeType: "BodyNode", meta: 5 } }, + }, }); export const OddAndEvenMiscStyles: ComponentStory = ( args: TreeReactFlowProps @@ -145,5 +164,8 @@ export const OddAndEvenMiscStyles: ComponentStory = ( treeSized({ ...args, defs: oddEvenDefsMiscStyles, - selection: { def: def5.name, node: { nodeType: "BodyNode", id: 5 } }, + selection: { + tag: "SelectionDef", + contents: { def: def5.name, node: { nodeType: "BodyNode", meta: 5 } }, + }, }); diff --git a/src/components/TreeReactFlow/Types.ts b/src/components/TreeReactFlow/Types.ts index 9c2bd884..60f2c820 100644 --- a/src/components/TreeReactFlow/Types.ts +++ b/src/components/TreeReactFlow/Types.ts @@ -7,6 +7,7 @@ import { NodeType, } from "@/primer-api"; import { unzip } from "fp-ts/lib/Array"; +import { Position } from "reactflow"; import { NodeFlavor } from "./Flavor"; /** A generic graph. */ @@ -120,6 +121,10 @@ export type PrimerNode = { | { type: "primer-simple"; data: PrimerSimpleNodeProps } | { type: "primer-box"; data: PrimerBoxNodeProps } | { type: "primer-def-name"; data: PrimerDefNameNodeProps } + // TODO check all of these are used - some were created while experimenting (if not, there's more to remove below) + | { type: "primer-typedef-name"; data: PrimerTypeDefNameNodeProps } + | { type: "primer-typedef-param"; data: PrimerTypeDefParamNodeProps } + | { type: "primer-typedef-cons"; data: PrimerTypeDefConsNodeProps } ); export const primerNodeWith = (n: PrimerNode, x: T): PrimerNode => @@ -130,9 +135,16 @@ export const primerNodeWith = (n: PrimerNode, x: T): PrimerNode => data: { ...n1.data, ...x }, }))(n); +export type NodeType1 = + | NodeType + | { + // tag: "typedefFieldNode"; + typedefFieldNode: { con: GlobalName; nChild: number }; + }; + /** Node properties. */ export type PrimerNodeProps = { - nodeType: NodeType; + nodeType: NodeType1; syntax: boolean; flavor: NodeFlavorTextBody | NodeFlavorPrimBody | NodeFlavorNoBody; contents: string; @@ -140,13 +152,13 @@ export type PrimerNodeProps = { /** Properties for a simple node. */ export type PrimerSimpleNodeProps = { - nodeType: NodeType; + nodeType: NodeType1; flavor: NodeFlavorNoBody; }; /** Properties for a box node. */ export type PrimerBoxNodeProps = { - nodeType: NodeType; + nodeType: NodeType1; flavor: NodeFlavorBoxBody; }; @@ -155,6 +167,18 @@ export type PrimerDefNameNodeProps = { def: GlobalName; }; +export type PrimerTypeDefNameNodeProps = { + name: GlobalName; +}; + +export type PrimerTypeDefParamNodeProps = { + name: string; +}; + +export type PrimerTypeDefConsNodeProps = { + name: GlobalName; +}; + /** Properties common to every type of node. */ export type PrimerCommonNodeProps = { width: number; @@ -168,8 +192,8 @@ export type PrimerEdge = { id: string; source: string; target: string; - sourceHandle: string; - targetHandle: string; + sourceHandle: Position; + targetHandle: Position; zIndex: number; } & ({ type: "primer"; data: PrimerEdgeProps } | { type: "primer-def-name" }); diff --git a/src/components/TreeReactFlow/index.tsx b/src/components/TreeReactFlow/index.tsx index 933b338a..64cd7c41 100644 --- a/src/components/TreeReactFlow/index.tsx +++ b/src/components/TreeReactFlow/index.tsx @@ -6,6 +6,7 @@ import { GlobalName, NodeType, Level, + TypeDef, } from "@/primer-api"; import { ReactFlow, @@ -23,7 +24,7 @@ import { useReactFlow, } from "reactflow"; import "./reactflow.css"; -import { MutableRefObject, useId } from "react"; +import { MutableRefObject, PropsWithChildren, useId } from "react"; import classNames from "classnames"; import { combineGraphs, @@ -44,6 +45,10 @@ import { PrimerCommonNodeProps, treeNodes, PrimerEdgeProps, + PrimerTypeDefConsNodeProps, + PrimerTypeDefParamNodeProps, + PrimerTypeDefNameNodeProps, + NodeType1, } from "./Types"; import { LayoutParams, layoutTree } from "./layoutTree"; import deepEqual from "deep-equal"; @@ -60,6 +65,7 @@ import { import { ZoomBar } from "./ZoomBar"; import { WasmLayoutType } from "@zxch3n/tidy/wasm_dist"; import { usePromise } from "@/util"; +import { mapSnd } from "fp-ts/lib/Tuple"; export type ScrollToDef = (defName: string) => void; @@ -76,6 +82,7 @@ type DefParams = { }; export type TreeReactFlowProps = { defs: Def[]; + typeDefs: TypeDef[]; onNodeClick?: ( event: React.MouseEvent, node: Positioned> @@ -111,6 +118,9 @@ const handle = (type: HandleType, position: Position) => ( ); +// TODO this has all become pretty repetitive +// factor out common parts? +// also move to separate file? const nodeTypes = { primer: ({ data }: { data: PrimerNodeProps & PrimerCommonNodeProps }) => ( <> @@ -258,6 +268,113 @@ const nodeTypes = { {handle("source", Position.Bottom)} ), + + "primer-typedef-name": ({ + data, + }: { + data: PrimerCommonNodeProps & PrimerTypeDefNameNodeProps; + }) => ( + <> + {handle("target", Position.Top)} + {handle("target", Position.Left)} +
+ {data.name.baseName} +
+ {handle("source", Position.Bottom)} + {handle("source", Position.Right)} + + ), + "primer-typedef-param": ({ + data, + }: { + data: PrimerCommonNodeProps & PrimerTypeDefParamNodeProps; + }) => ( + <> + {handle("target", Position.Top)} + {handle("target", Position.Left)} +
+ { +
+ {data.name} +
+ } +
+ {handle("source", Position.Bottom)} + {handle("source", Position.Right)} + + ), + "primer-typedef-cons": ({ + data, + }: { + data: PrimerCommonNodeProps & PrimerTypeDefConsNodeProps; + }) => ( + <> + {handle("target", Position.Top)} + {handle("target", Position.Left)} +
+ { +
+ {data.name.baseName} +
+ } +
+ {handle("source", Position.Bottom)} + {handle("source", Position.Right)} + + ), }; const edgeTypes = { @@ -354,7 +471,7 @@ const makePrimerNode = async ( p: NodeParams, layout: LayoutParams, zIndex: number, - nodeType: NodeType + nodeType: NodeType1 ): Promise< [ PrimerNode, @@ -364,7 +481,9 @@ const makePrimerNode = async ( PrimerGraph[] ] > => { - const selected = p.selection?.node?.id?.toString() == node.nodeId; + const selected = + p.selection?.tag == "SelectionDef" && + p.selection.contents.node?.meta?.toString() == node.nodeId; const id = node.nodeId; const common = { width: p.nodeWidth, @@ -550,7 +669,9 @@ const defToTree = async ( width: p.nodes.nodeWidth * p.nameNodeMultipliers.width, height: p.nodes.nodeHeight * p.nameNodeMultipliers.height, selected: - deepEqual(p.nodes.selection?.def, def.name) && !p.nodes.selection?.node, + p.nodes.selection?.tag == "SelectionDef" && + deepEqual(p.nodes.selection?.contents.def, def.name) && + !p.nodes.selection?.contents.node, }, type: "primer-def-name", zIndex: 0, @@ -605,64 +726,307 @@ const defToTree = async ( * It ensures that these are clearly displayed as "one atomic thing", * i.e. to avoid confused readings that group the type of 'foo' with the body of 'bar' (etc). */ -export const TreeReactFlow = (p: TreeReactFlowProps) => ( - - defToTree(def, { - ...p.defParams, - layout: p.layout, - nodes: p, - }).then((t) => layoutTree(t, p.layout)) - ) - ).then( - // Space out the forest. - (sizedTrees) => - sizedTrees.reduce< - [Tree, PrimerEdge>[], number] - >( - ([trees, offset], { tree, width, height }) => { - const { increment, offsetVector } = (() => { - switch (p.forestLayout) { - case "Horizontal": - return { - increment: width, - offsetVector: { x: offset, y: 0 }, - }; - case "Vertical": - return { - increment: height, - offsetVector: { x: 0, y: offset }, - }; - } - })(); - return [ - trees.concat( - treeMap(tree, (n) => ({ - ...n, - position: { - x: n.position.x + p.layout.margins.sibling + offsetVector.x, - y: n.position.y + p.layout.margins.child + offsetVector.y, +export const TreeReactFlow = (p: TreeReactFlowProps) => { + // TODO inline these type defs? + type N = PrimerNode<{ def: GlobalName }>; + type E = PrimerEdge; + type T = Tree; + const tdTrees0: Promise[] = p.typeDefs.map(async (def) => { + // TODO don't hardcode + const rootId = "hardcoded ".concat(def.name.baseName); + // TODO instead of this, define a properly-typed `deepEqual` in `util` + const expectedSelection: Selection = { + tag: "SelectionTypeDef", + contents: { def: def.name }, + }; + const node: N = { + id: rootId, + type: "primer-typedef-name", + data: { + def: def.name, + name: def.name, + height: p.nodeHeight * p.defParams.nameNodeMultipliers.height, + width: p.nodeWidth * p.defParams.nameNodeMultipliers.width, + selected: + p.selection?.tag == "SelectionTypeDef" && + deepEqual(p.selection, expectedSelection), + }, + zIndex: 0, + }; + const rightChild0 = def.params.reduceRight< + [T, (parentId: string) => E] | undefined + >((c, name) => { + // TODO IDs + const id = JSON.stringify([def.name, name]); + const expectedSelection: Selection = { + tag: "SelectionTypeDef", + contents: { + def: def.name, + node: { + tag: "TypeDefParamNodeSelection", + contents: name, + }, + }, + }; + const node: PrimerNode<{ def: GlobalName }> = { + id, + type: "primer-typedef-param", + data: { + def: def.name, + width: p.nodeWidth, + height: p.nodeHeight, + name, + selected: deepEqual(p.selection, expectedSelection), + }, + zIndex: 0, + }; + return [ + { + node, + childTrees: [], + ...(c ? { rightChild: [c[0], c[1](id)] } : {}), + }, + (parentId) => ({ + id: JSON.stringify([parentId, id]), + source: parentId, + target: id, + type: "primer-def-name", + zIndex: 0, + sourceHandle: Position.Right, + targetHandle: Position.Left, + }), + ]; + }, undefined); + // const rightChild: [T, E] | undefined = rightChild0 + // ? [rightChild0[0], rightChild0[1](rootId)] + // : undefined; + const rightChild: [T, E] | undefined = rightChild0 + ? mapSnd((f: (parentId: string) => E) => f(rootId))(rightChild0) + : undefined; + // TODO render primitives differntly i.e. empty list differently to undefined + const constructors = def.constructors ?? []; + const childTrees: [T, E][] = await Promise.all( + constructors.map(async (cons) => { + // TODO + const consId = JSON.stringify(cons.name); + const cs0 = cons.fields.map>((t, nChild) => + augmentTree(t, (n0) => + // TODO DRY this with `defEdge` + // the passing of `makePrimerNode` to `augmentTree` is simple enough, and we do that in a few places + // it's actually adding defs, including to the nested trees, that's big and boilerplate-y + // oh, and adding the edge, but we might want to abstract that out somewhere else + // TODO this isn't really a "BodyNode" or "SigNode"... (NB: this predates "typedefFieldNode" etc.) + // do we need a third constructor? + // or should we not be using `makePrimerNode`? + // actually, I think that could be right, or at least it needs to be generalised + // `makePrimerNode` also has the wrong `selected` for us here + // we override that but it's a bit ugly and we have to turn off TC + // we also know that this won't actually contain nested subtrees, since type-level things just don't + // makePrimerNode(n0, p, p.layout, 0, "typedefFieldNode").then( + makePrimerNode(n0, p, p.layout, 0, { + typedefFieldNode: { con: cons.name, nChild }, + }).then(([n, e, nested]) => { + const expectedSelection1 = ( + id0: string + ): Selection | undefined => { + const id = Number(id0); + const s: Selection = { + tag: "SelectionTypeDef", + contents: { + def: def.name, + node: { + tag: "TypeDefConsNodeSelection", + contents: { + con: cons.name, + field: { index: nChild, meta: id }, + }, + }, }, - })) - ), - offset + increment + p.treePadding, - ]; + }; + return !isNaN(id) ? s : undefined; + }; + return [ + primerNodeWith(n, { + def: def.name, + nested: nested.map((g) => + graphMap(g, ({ position, ...n }) => ({ + ...primerNodeWith(n, { + def: def.name, + selected: + p.selection && + deepEqual(p.selection, expectedSelection1(n.id)), + }), + position, + })) + ), + // TODO we have to do it this verbose way since we don't consider it selected when both are undefined + selected: + p.selection && + deepEqual(p.selection, expectedSelection1(n.id)), + }), + e, + ]; + }) + ).then((t) => [ + t, + { + id: JSON.stringify([consId, t.node.id]), + source: consId, + target: t.node.id, + type: "primer-def-name", + zIndex: 0, + sourceHandle: Position.Bottom, + targetHandle: Position.Top, + }, + ]) + ); + const cs = await Promise.all(cs0); + const expectedSelection: Selection = { + tag: "SelectionTypeDef", + contents: { + def: def.name, + node: { + tag: "TypeDefConsNodeSelection", + contents: { + con: cons.name, + }, + }, }, - [[], 0] - )[0] - )} - {...(p.onNodeClick && { onNodeClick: p.onNodeClick })} - scrollToDefRef={p.scrollToDefRef} - > -); + }; + return [ + { + node: { + id: consId, + type: "primer-typedef-cons", + data: { + def: def.name, + name: cons.name, + width: p.nodeWidth, + height: p.nodeHeight, + selected: deepEqual(p.selection, expectedSelection), + }, + zIndex: 0, + }, + childTrees: cs, + }, + { + id: JSON.stringify([rootId, consId]), + type: "primer-def-name", + source: rootId, + target: consId, + sourceHandle: Position.Bottom, + targetHandle: Position.Top, + zIndex: 0, + }, + ]; + }) + ); + return { + node, + childTrees, + ...(rightChild ? { rightChild } : {}), + }; + }); + const tdTrees: Promise<{ + tree: Tree, E>; + width: number; + height: number; + // }>[] = p.typeDefs.map((d) => d); + }>[] = tdTrees0.map((x) => x.then((y) => layoutTree(y, p.layout))); + const spaceForest = + (extra: { x: number; y: number }) => + ( + sizedTrees: { + tree: Tree, PrimerEdge>; + width: number; + height: number; + }[] + ) => + sizedTrees.reduce< + [Tree, PrimerEdge>[], number] + >( + ([trees, offset], { tree, width, height }) => { + const { increment, offsetVector } = (() => { + switch (p.forestLayout) { + case "Horizontal": + return { + increment: width, + offsetVector: { x: offset, y: 0 }, + }; + case "Vertical": + return { + increment: height, + offsetVector: { x: 0, y: offset }, + }; + } + })(); + return [ + trees.concat( + treeMap(tree, (n) => ({ + ...n, + position: { + x: + n.position.x + + p.layout.margins.sibling + + offsetVector.x + + extra.x, + y: + n.position.y + + p.layout.margins.child + + offsetVector.y + + extra.y, + }, + })) + ), + offset + increment + p.treePadding, + ]; + }, + [[], 0] + )[0]; + return ( + , PrimerEdge>[] + > => { + const tdTrees1 = await Promise.all(tdTrees); + const typeRowHeight = + tdTrees1.length > 0 + ? Math.max(...tdTrees1.map((x) => x.height)) + p.treePadding + : 0; + const typeTrees = tdTrees1.map(({ tree, ...b }) => ({ + // TODO this is some horrible boilerplate... + ...b, + tree: treeMap(tree, ({ position, ...c }) => ({ + position, + ...primerNodeWith(c, { nested: [], ...c.data }), + })), + })); + const defs = await Promise.all(p.defs); + const defTrees = await Promise.all( + defs.map((def) => + defToTree(def, { + ...p.defParams, + layout: p.layout, + nodes: p, + }).then((t) => layoutTree(t, p.layout)) + ) + ); + return spaceForest({ x: 0, y: 0 })(typeTrees).concat( + spaceForest({ x: 0, y: typeRowHeight })(defTrees) + ); + })()} + {...(p.onNodeClick && { onNodeClick: p.onNodeClick })} + > + + + ); +}; export default TreeReactFlow; export type TreeReactFlowOneProps = { tree?: APITree; onNodeClick?: (event: React.MouseEvent, node: Positioned) => void; layout: LayoutParams; - scrollToDefRef: MutableRefObject; } & NodeParams; /** Renders one `APITree` (e.g. one type or one term) on its own individual canvas. @@ -682,20 +1046,20 @@ export const TreeReactFlowOne = (p: TreeReactFlowOneProps) => ( : new Promise(() => []) } {...(p.onNodeClick && { onNodeClick: p.onNodeClick })} - scrollToDefRef={p.scrollToDefRef} > ); // The core of our interaction with ReactFlow: take some abstract trees, and render them. // This is not exported, but various wrappers around it are. -const Trees = (p: { - makeTrees: Promise>, PrimerEdge>[]>; - onNodeClick?: ( - event: React.MouseEvent, - node: Positioned> - ) => void; - scrollToDefRef: MutableRefObject; -}): JSX.Element => { +const Trees = ( + p: PropsWithChildren<{ + makeTrees: Promise>, PrimerEdge>[]>; + onNodeClick?: ( + event: React.MouseEvent, + node: Positioned> + ) => void; + }> +): ReturnType => { const trees = usePromise([], p.makeTrees); const { nodes, edges } = combineGraphs([ ...trees.map(treeToGraph), @@ -719,7 +1083,7 @@ const Trees = (p: { > - + {p.children} ); }; diff --git a/src/primer-api/model/defSelection.ts b/src/primer-api/model/defSelection.ts new file mode 100644 index 00000000..a5c38817 --- /dev/null +++ b/src/primer-api/model/defSelection.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v6.16.0 🍺 + * Do not edit manually. + * Primer backend API + * A backend service implementing a pedagogic functional programming language. + * OpenAPI spec version: 0.7 + */ +import type { GlobalName } from './globalName'; +import type { NodeSelection } from './nodeSelection'; + +export interface DefSelection { + def: GlobalName; + node?: NodeSelection; +} diff --git a/src/primer-api/model/getActionOptionsAction.ts b/src/primer-api/model/getActionOptionsAction.ts index 680b9fbe..66ce979f 100644 --- a/src/primer-api/model/getActionOptionsAction.ts +++ b/src/primer-api/model/getActionOptionsAction.ts @@ -29,4 +29,8 @@ export const GetActionOptionsAction = { MakeForall: 'MakeForall', RenameForall: 'RenameForall', RenameDef: 'RenameDef', + RenameType: 'RenameType', + RenameCon: 'RenameCon', + RenameTypeParam: 'RenameTypeParam', + AddCon: 'AddCon', } as const; diff --git a/src/primer-api/model/index.ts b/src/primer-api/model/index.ts index d37387eb..a1d349c5 100644 --- a/src/primer-api/model/index.ts +++ b/src/primer-api/model/index.ts @@ -17,6 +17,7 @@ export * from './applyActionWithInputParams'; export * from './createDefinitionParams'; export * from './createTypeDefBody'; export * from './def'; +export * from './defSelection'; export * from './evalFullParams'; export * from './evalFullResp'; export * from './evalFullRespOneOf'; @@ -65,7 +66,21 @@ export * from './recordPairNodeFlavorBoxBodyTree'; export * from './recordPairNodeFlavorPrimBodyPrimCon'; export * from './recordPairNodeFlavorTextBodyName'; export * from './selection'; +export * from './selectionOneOf'; +export * from './selectionOneOfTag'; +export * from './selectionOneOfThree'; +export * from './selectionOneOfThreeTag'; export * from './session'; export * from './sessionName'; export * from './tree'; -export * from './uuid'; \ No newline at end of file +export * from './typeDef'; +export * from './typeDefConsFieldSelection'; +export * from './typeDefConsSelection'; +export * from './typeDefNodeSelection'; +export * from './typeDefNodeSelectionOneOf'; +export * from './typeDefNodeSelectionOneOfTag'; +export * from './typeDefNodeSelectionOneOfThree'; +export * from './typeDefNodeSelectionOneOfThreeTag'; +export * from './typeDefSelection'; +export * from './uuid'; +export * from './valCon'; \ No newline at end of file diff --git a/src/primer-api/model/inputAction.ts b/src/primer-api/model/inputAction.ts index 03a3d946..8bf98cd6 100644 --- a/src/primer-api/model/inputAction.ts +++ b/src/primer-api/model/inputAction.ts @@ -29,4 +29,8 @@ export const InputAction = { MakeForall: 'MakeForall', RenameForall: 'RenameForall', RenameDef: 'RenameDef', + RenameType: 'RenameType', + RenameCon: 'RenameCon', + RenameTypeParam: 'RenameTypeParam', + AddCon: 'AddCon', } as const; diff --git a/src/primer-api/model/module.ts b/src/primer-api/model/module.ts index 8a8356ac..9a906d65 100644 --- a/src/primer-api/model/module.ts +++ b/src/primer-api/model/module.ts @@ -6,11 +6,11 @@ * OpenAPI spec version: 0.7 */ import type { Def } from './def'; -import type { GlobalName } from './globalName'; +import type { TypeDef } from './typeDef'; export interface Module { defs: Def[]; editable: boolean; modname: string[]; - types: GlobalName[]; + types: TypeDef[]; } diff --git a/src/primer-api/model/noInputAction.ts b/src/primer-api/model/noInputAction.ts index a7109162..f526082f 100644 --- a/src/primer-api/model/noInputAction.ts +++ b/src/primer-api/model/noInputAction.ts @@ -28,4 +28,5 @@ export const NoInputAction = { DeleteType: 'DeleteType', DuplicateDef: 'DuplicateDef', DeleteDef: 'DeleteDef', + AddConField: 'AddConField', } as const; diff --git a/src/primer-api/model/nodeSelection.ts b/src/primer-api/model/nodeSelection.ts index 8a9a7bc6..c157ac89 100644 --- a/src/primer-api/model/nodeSelection.ts +++ b/src/primer-api/model/nodeSelection.ts @@ -8,6 +8,6 @@ import type { NodeType } from './nodeType'; export interface NodeSelection { - id: number; + meta: number; nodeType: NodeType; } diff --git a/src/primer-api/model/selection.ts b/src/primer-api/model/selection.ts index 43b33ce4..c8683606 100644 --- a/src/primer-api/model/selection.ts +++ b/src/primer-api/model/selection.ts @@ -5,10 +5,7 @@ * A backend service implementing a pedagogic functional programming language. * OpenAPI spec version: 0.7 */ -import type { GlobalName } from './globalName'; -import type { NodeSelection } from './nodeSelection'; +import type { SelectionOneOf } from './selectionOneOf'; +import type { SelectionOneOfThree } from './selectionOneOfThree'; -export interface Selection { - def: GlobalName; - node?: NodeSelection; -} +export type Selection = SelectionOneOf | SelectionOneOfThree; diff --git a/src/primer-api/model/selectionOneOf.ts b/src/primer-api/model/selectionOneOf.ts new file mode 100644 index 00000000..bb9b2f2a --- /dev/null +++ b/src/primer-api/model/selectionOneOf.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v6.16.0 🍺 + * Do not edit manually. + * Primer backend API + * A backend service implementing a pedagogic functional programming language. + * OpenAPI spec version: 0.7 + */ +import type { DefSelection } from './defSelection'; +import type { SelectionOneOfTag } from './selectionOneOfTag'; + +export type SelectionOneOf = { + contents: DefSelection; + tag: SelectionOneOfTag; +}; diff --git a/src/primer-api/model/selectionOneOfTag.ts b/src/primer-api/model/selectionOneOfTag.ts new file mode 100644 index 00000000..a7baa249 --- /dev/null +++ b/src/primer-api/model/selectionOneOfTag.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v6.16.0 🍺 + * Do not edit manually. + * Primer backend API + * A backend service implementing a pedagogic functional programming language. + * OpenAPI spec version: 0.7 + */ + +export type SelectionOneOfTag = typeof SelectionOneOfTag[keyof typeof SelectionOneOfTag]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const SelectionOneOfTag = { + SelectionDef: 'SelectionDef', +} as const; diff --git a/src/primer-api/model/selectionOneOfThree.ts b/src/primer-api/model/selectionOneOfThree.ts new file mode 100644 index 00000000..98ba0b92 --- /dev/null +++ b/src/primer-api/model/selectionOneOfThree.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v6.16.0 🍺 + * Do not edit manually. + * Primer backend API + * A backend service implementing a pedagogic functional programming language. + * OpenAPI spec version: 0.7 + */ +import type { TypeDefSelection } from './typeDefSelection'; +import type { SelectionOneOfThreeTag } from './selectionOneOfThreeTag'; + +export type SelectionOneOfThree = { + contents: TypeDefSelection; + tag: SelectionOneOfThreeTag; +}; diff --git a/src/primer-api/model/selectionOneOfThreeTag.ts b/src/primer-api/model/selectionOneOfThreeTag.ts new file mode 100644 index 00000000..71c11c7c --- /dev/null +++ b/src/primer-api/model/selectionOneOfThreeTag.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v6.16.0 🍺 + * Do not edit manually. + * Primer backend API + * A backend service implementing a pedagogic functional programming language. + * OpenAPI spec version: 0.7 + */ + +export type SelectionOneOfThreeTag = typeof SelectionOneOfThreeTag[keyof typeof SelectionOneOfThreeTag]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const SelectionOneOfThreeTag = { + SelectionTypeDef: 'SelectionTypeDef', +} as const; diff --git a/src/primer-api/model/typeDef.ts b/src/primer-api/model/typeDef.ts new file mode 100644 index 00000000..b2dcec17 --- /dev/null +++ b/src/primer-api/model/typeDef.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v6.16.0 🍺 + * Do not edit manually. + * Primer backend API + * A backend service implementing a pedagogic functional programming language. + * OpenAPI spec version: 0.7 + */ +import type { ValCon } from './valCon'; +import type { GlobalName } from './globalName'; + +export interface TypeDef { + constructors?: ValCon[]; + name: GlobalName; + nameHints: string[]; + params: string[]; +} diff --git a/src/primer-api/model/typeDefConsFieldSelection.ts b/src/primer-api/model/typeDefConsFieldSelection.ts new file mode 100644 index 00000000..2a677c79 --- /dev/null +++ b/src/primer-api/model/typeDefConsFieldSelection.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v6.16.0 🍺 + * Do not edit manually. + * Primer backend API + * A backend service implementing a pedagogic functional programming language. + * OpenAPI spec version: 0.7 + */ + +export interface TypeDefConsFieldSelection { + index: number; + meta: number; +} diff --git a/src/primer-api/model/typeDefConsSelection.ts b/src/primer-api/model/typeDefConsSelection.ts new file mode 100644 index 00000000..f8de3873 --- /dev/null +++ b/src/primer-api/model/typeDefConsSelection.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v6.16.0 🍺 + * Do not edit manually. + * Primer backend API + * A backend service implementing a pedagogic functional programming language. + * OpenAPI spec version: 0.7 + */ +import type { GlobalName } from './globalName'; +import type { TypeDefConsFieldSelection } from './typeDefConsFieldSelection'; + +export interface TypeDefConsSelection { + con: GlobalName; + field?: TypeDefConsFieldSelection; +} diff --git a/src/primer-api/model/typeDefNodeSelection.ts b/src/primer-api/model/typeDefNodeSelection.ts new file mode 100644 index 00000000..7c7f743a --- /dev/null +++ b/src/primer-api/model/typeDefNodeSelection.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v6.16.0 🍺 + * Do not edit manually. + * Primer backend API + * A backend service implementing a pedagogic functional programming language. + * OpenAPI spec version: 0.7 + */ +import type { TypeDefNodeSelectionOneOf } from './typeDefNodeSelectionOneOf'; +import type { TypeDefNodeSelectionOneOfThree } from './typeDefNodeSelectionOneOfThree'; + +export type TypeDefNodeSelection = TypeDefNodeSelectionOneOf | TypeDefNodeSelectionOneOfThree; diff --git a/src/primer-api/model/typeDefNodeSelectionOneOf.ts b/src/primer-api/model/typeDefNodeSelectionOneOf.ts new file mode 100644 index 00000000..b7d13007 --- /dev/null +++ b/src/primer-api/model/typeDefNodeSelectionOneOf.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v6.16.0 🍺 + * Do not edit manually. + * Primer backend API + * A backend service implementing a pedagogic functional programming language. + * OpenAPI spec version: 0.7 + */ +import type { TypeDefNodeSelectionOneOfTag } from './typeDefNodeSelectionOneOfTag'; + +export type TypeDefNodeSelectionOneOf = { + contents: string; + tag: TypeDefNodeSelectionOneOfTag; +}; diff --git a/src/primer-api/model/typeDefNodeSelectionOneOfTag.ts b/src/primer-api/model/typeDefNodeSelectionOneOfTag.ts new file mode 100644 index 00000000..cf912ffb --- /dev/null +++ b/src/primer-api/model/typeDefNodeSelectionOneOfTag.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v6.16.0 🍺 + * Do not edit manually. + * Primer backend API + * A backend service implementing a pedagogic functional programming language. + * OpenAPI spec version: 0.7 + */ + +export type TypeDefNodeSelectionOneOfTag = typeof TypeDefNodeSelectionOneOfTag[keyof typeof TypeDefNodeSelectionOneOfTag]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const TypeDefNodeSelectionOneOfTag = { + TypeDefParamNodeSelection: 'TypeDefParamNodeSelection', +} as const; diff --git a/src/primer-api/model/typeDefNodeSelectionOneOfThree.ts b/src/primer-api/model/typeDefNodeSelectionOneOfThree.ts new file mode 100644 index 00000000..a1551f1a --- /dev/null +++ b/src/primer-api/model/typeDefNodeSelectionOneOfThree.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v6.16.0 🍺 + * Do not edit manually. + * Primer backend API + * A backend service implementing a pedagogic functional programming language. + * OpenAPI spec version: 0.7 + */ +import type { TypeDefConsSelection } from './typeDefConsSelection'; +import type { TypeDefNodeSelectionOneOfThreeTag } from './typeDefNodeSelectionOneOfThreeTag'; + +export type TypeDefNodeSelectionOneOfThree = { + contents: TypeDefConsSelection; + tag: TypeDefNodeSelectionOneOfThreeTag; +}; diff --git a/src/primer-api/model/typeDefNodeSelectionOneOfThreeTag.ts b/src/primer-api/model/typeDefNodeSelectionOneOfThreeTag.ts new file mode 100644 index 00000000..e6aa1e38 --- /dev/null +++ b/src/primer-api/model/typeDefNodeSelectionOneOfThreeTag.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v6.16.0 🍺 + * Do not edit manually. + * Primer backend API + * A backend service implementing a pedagogic functional programming language. + * OpenAPI spec version: 0.7 + */ + +export type TypeDefNodeSelectionOneOfThreeTag = typeof TypeDefNodeSelectionOneOfThreeTag[keyof typeof TypeDefNodeSelectionOneOfThreeTag]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const TypeDefNodeSelectionOneOfThreeTag = { + TypeDefConsNodeSelection: 'TypeDefConsNodeSelection', +} as const; diff --git a/src/primer-api/model/typeDefSelection.ts b/src/primer-api/model/typeDefSelection.ts new file mode 100644 index 00000000..43e2ab3b --- /dev/null +++ b/src/primer-api/model/typeDefSelection.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v6.16.0 🍺 + * Do not edit manually. + * Primer backend API + * A backend service implementing a pedagogic functional programming language. + * OpenAPI spec version: 0.7 + */ +import type { GlobalName } from './globalName'; +import type { TypeDefNodeSelection } from './typeDefNodeSelection'; + +export interface TypeDefSelection { + def: GlobalName; + node?: TypeDefNodeSelection; +} diff --git a/src/primer-api/model/valCon.ts b/src/primer-api/model/valCon.ts new file mode 100644 index 00000000..15de0b61 --- /dev/null +++ b/src/primer-api/model/valCon.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v6.16.0 🍺 + * Do not edit manually. + * Primer backend API + * A backend service implementing a pedagogic functional programming language. + * OpenAPI spec version: 0.7 + */ +import type { Tree } from './tree'; +import type { GlobalName } from './globalName'; + +export interface ValCon { + fields: Tree[]; + name: GlobalName; +}