better-looking parameters

This commit is contained in:
Joeri Exelmans 2025-05-12 23:40:58 +02:00
parent 9afaa41fbb
commit 95eb8aef84
10 changed files with 306 additions and 229 deletions

View file

@ -1,94 +1,16 @@
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import './App.css'
import { Editor, initialEditorState, type EditorState } from './Editor'
import { trie, apply, Int } from "dope2";
const listPush = trie.get(initialEditorState.env.name2dyn)("list.push");
const listEmptyList = trie.get(initialEditorState.env.name2dyn)("list.emptyList");
const fourtyTwo = {i: 42n, t: Int};
const nonEmptyEditorState: EditorState = {
kind: "call",
env: initialEditorState.env,
fn: {
kind: "call",
env: initialEditorState.env,
fn: {
kind: "input",
env: initialEditorState.env,
text: "list.push",
resolved: listPush,
},
input: {
kind: "input",
env: initialEditorState.env,
text: "list.emptyList",
resolved: listEmptyList,
},
resolved: apply(listEmptyList)(listPush),
},
input: {
kind: "input",
env: initialEditorState.env,
text: "42",
resolved: fourtyTwo,
},
resolved: apply(fourtyTwo)(apply(listEmptyList)(listPush)),
};
const functionWith3Params = trie.get(initialEditorState.env.name2dyn)("functionWith3Params");
const fourtyThree = {i: 43n, t: Int};
const fourtyFour = {i: 44n, t: Int};
const tripleFunctionCallEditorState: EditorState = {
kind: "call",
env: initialEditorState.env,
fn: {
kind: "call",
env: initialEditorState.env,
fn: {
kind: "call",
env: initialEditorState.env,
fn: {
kind: "input",
env: initialEditorState.env,
text: "functionWith3Params",
resolved: functionWith3Params,
},
input: {
kind: "input",
env: initialEditorState.env,
text: "42",
resolved: fourtyTwo,
},
resolved: apply(fourtyTwo)(functionWith3Params),
},
input: {
kind: "input",
env: initialEditorState.env,
text: "43",
resolved: fourtyThree,
},
resolved: apply(fourtyThree)(apply(fourtyTwo)(functionWith3Params)),
},
input: {
kind: "input",
env: initialEditorState.env,
text: "44",
resolved: fourtyFour,
},
resolved: apply(fourtyFour)(apply(fourtyThree)(apply(fourtyTwo)(functionWith3Params))),
}
import { Editor, type EditorState } from './Editor'
import { initialEditorState, nonEmptyEditorState, tripleFunctionCallEditorState } from "./configurations";
export function App() {
// const [history, setHistory] = useState([initialEditorState]);
const [history, setHistory] = useState([initialEditorState]);
// const [history, setHistory] = useState([nonEmptyEditorState]);
const [history, setHistory] = useState([tripleFunctionCallEditorState]);
// const [history, setHistory] = useState([tripleFunctionCallEditorState]);
const [future, setFuture] = useState<EditorState[]>([]);
const pushHistory = (s: EditorState) => {
console.log('pushHistory');
setHistory(history.concat([s]));
setFuture([]);
};
@ -118,10 +40,8 @@ export function App() {
}
};
useEffect(() => {
window['APP_STATE'] = history;
// console.log("EDITOR STATE:", state);
window['APP_STATE'] = history; // useful for debugging
}, [history]);
useEffect(() => {
@ -139,8 +59,10 @@ export function App() {
<Editor
state={history.at(-1)!}
setState={pushHistory}
onResolve={() => {console.log("toplevel resolved")}}
onCancel={() => {console.log("toplevel canceled")}}
onResolve={() => {}}
onCancel={() => {}}
filter={() => true}
focus={true}
/>
</main>

View file

@ -1,7 +1,8 @@
.functionBlock {
border: solid 1px darkgray;
display: inline-block;
margin: 1px;
margin: 4px;
color: black;
}
.functionBlock.unifyError {
@ -18,37 +19,43 @@
/* background-color: pink; */
}
.inputParam:after {
content: "";
position: absolute;
border: solid 10px transparent;
border-left-color: rgb(242, 253, 146);
margin-left: 0px;
/* z-index: 1; */
border: solid transparent;
border-width: 10px;
right: -19px;
top: 0;
bottom:0;
}
.inputParam {
/* height: 20px; */
margin-right: 20px;
display: inline-block;
display: inline-flex;
vertical-align: middle;
position: relative; /* to ensure the :after (which is absolute) is relative to ourselves */
flex-grow: 1;
}
/* Count nested level AFTER .outputParam (resets the depth) */
.outputParam > .inputParam:after {
border-left-color: rgb(242, 253, 146);
}
.outputParam > .inputParam {
background-color: rgb(242, 253, 146);
}
.inputParam .inputParam {
.outputParam > .inputParam > .inputParam {
background-color: rgb(180, 248, 214);
}
.inputParam .inputParam:after {
.outputParam > .inputParam > .inputParam:after {
border-left-color: rgb(180, 248, 214);
}
.inputParam .inputParam .inputParam {
.outputParam > .inputParam > .inputParam > .inputParam {
background-color: rgb(153, 212, 214);
}
.inputParam .inputParam .inputParam:after {
.outputParam > .inputParam > .inputParam > .inputParam:after {
border-left-color: rgb(153, 212, 214);
}
.typeAnnot {
display: inline-block;
vertical-align: top;
@ -67,14 +74,21 @@
width: 100%;
}
.functionBlock.unifyError .inputParam {
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam {
background-color: darkred;
color: white;
}
.functionBlock.unifyError .inputParam:after {
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam:after {
border-left-color: darkred;
}
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam {
background-color: rgb(95, 4, 4);
color: white;
}
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam:after {
border-left-color: rgb(95, 4, 4);
}
.functionBlock.unifyError .outputParam {
.functionBlock.unifyError > .functionParams > .outputParam {
background-color: pink;
}

View file

@ -1,6 +1,6 @@
import { useState } from "react";
import { apply, UnifyError } from "dope2";
import { apply, UnifyError, assignFn, getType } from "dope2";
import { Editor, type EditorState } from "./Editor";
import { Value } from "./Value";
@ -17,6 +17,7 @@ export interface CallBlockState<
fn: FnState;
input: InputState;
resolved: undefined | Dynamic;
// focus: boolean;
}
interface CallBlockProps<
@ -26,25 +27,24 @@ interface CallBlockProps<
onResolve: (resolved: EditorState) => void;
}
function headlessCallBlock({state: {kind, env, fn, input, resolved }, setState, onResolve}: CallBlockProps) {
function headlessCallBlock({state, setState, onResolve}: CallBlockProps) {
const [unifyError, setUnifyError] = useState<UnifyError | undefined>(undefined);
const {fn, input } = state;
const setFn = (fn: EditorState) => {
setState({kind, env, fn, input, resolved});
setState({...state, fn});
}
const setInput = (input: EditorState) => {
setState({kind, env, fn, input, resolved});
setState({...state, input});
}
const setResolved = (resolved?: Dynamic) => {
setState({kind, env, fn, input, resolved});
setState({...state, resolved});
}
const makeTheCall = (input, fn) => {
console.log('makeTheCall...')
try {
const outputResolved = apply(input.resolved)(fn.resolved);
setResolved(outputResolved);
console.log("onResolve callblock..")
onResolve({
kind, env, fn, input, resolved: outputResolved
...state, resolved: outputResolved
});
setUnifyError(undefined);
}
@ -54,12 +54,11 @@ function headlessCallBlock({state: {kind, env, fn, input, resolved }, setState,
}
setUnifyError(e);
onResolve({
kind, env, fn, input, resolved: undefined
...state, resolved: undefined
})
}
};
const onFnResolve = (fnState) => {
console.log('my fn resolved')
if (input.resolved) {
makeTheCall(input, fnState);
}
@ -67,20 +66,18 @@ function headlessCallBlock({state: {kind, env, fn, input, resolved }, setState,
// setFn(fnState);
setResolved(undefined);
onResolve({
kind, env, fn: fnState, input, resolved: undefined
...state, resolved: undefined
});
}
};
const onInputResolve = (inputState) => {
console.log('my input resolved')
if (fn.resolved) {
makeTheCall(inputState, fn);
}
else {
// setInput(inputState);
setResolved(undefined);
onResolve({
kind, env, fn, input: inputState, resolved: undefined
...state, resolved: undefined
});
}
};
@ -90,24 +87,11 @@ function headlessCallBlock({state: {kind, env, fn, input, resolved }, setState,
const onInputCancel = () => {
setState(fn);
}
// const filterCompatibleInputs = ([_name, dynamic]: [string, Dynamic]) => {
// if (fn.resolved) {
// try {
// assignFn(getType(fn.resolved), getType(dynamic));
// } catch (e) {
// if (!(e instanceof UnifyError)) {
// throw e;
// }
// return false;
// }
// }
// return true;
// }
return {unifyError, setFn, setInput, onFnResolve, onInputResolve, onFnCancel, onInputCancel};
}
export function CallBlock({ state, setState, onResolve }: CallBlockProps) {
const {unifyError, setFn, setInput, onFnResolve, onInputResolve, onFnCancel, onInputCancel}
const {unifyError, setFn, setInput, onFnResolve, onInputResolve, onInputCancel}
= headlessCallBlock({ state, setState, onResolve });
return <span className={"functionBlock" + (unifyError ? " unifyError" : "")}>
<FunctionHeader
@ -120,6 +104,7 @@ export function CallBlock({ state, setState, onResolve }: CallBlockProps) {
<InputParams
fn={state.fn} setFn={setFn}
input={state.input} setInput={setInput}
focus={true}
onFnResolve={onFnResolve}
onInputResolve={onInputResolve} onInputCancel={onInputCancel} />
@ -153,38 +138,59 @@ function FunctionHeader({ fn, setFn, onFnResolve }) {
<Editor
state={fn}
setState={setFn}
focus={false}
onResolve={onFnResolve}
onCancel={() => {/*todo*/}}/>
onCancel={() => {/*todo*/}}
filter={() => true} />
</div>;
}
}
function InputParams({ fn, setFn, input, setInput, onFnResolve, onInputResolve, onInputCancel }) {
const {
onInputResolve: onFnInputResolve,
onFnResolve : onFnFnResolve
} = headlessCallBlock({state: fn, setState: setFn, onResolve: onFnResolve});
function InputParams({ fn, setFn, input, setInput, onFnResolve, onInputResolve, onInputCancel, focus }) {
const filterCompatibleInputs = ([_name, dynamic]: [string, Dynamic]) => {
if (fn.resolved) {
try {
assignFn(getType(fn.resolved), getType(dynamic));
} catch (e) {
if (!(e instanceof UnifyError)) {
throw e;
}
return false;
}
}
return true;
}
return <div className="inputParam">
{(fn.kind === "call") &&
// if the function we're calling is itself the result of a function call,
// then we render its input parameter nested in our own input parameter box, which is way more readable
// Input(s) of the function we're calling:
<InputParams
<NestedInputParams fn={fn} setFn={setFn} onFnResolve={onFnResolve} />
}
{/* Our own input */}
<Editor
state={input}
setState={setInput}
onResolve={onInputResolve}
onCancel={onInputCancel}
filter={filterCompatibleInputs}
focus={focus} />
</div>;
}
function NestedInputParams({fn, setFn, onFnResolve}) {
const {
onInputResolve: onFnInputResolve,
onFnResolve : onFnFnResolve,
} = headlessCallBlock({state: fn, setState: setFn, onResolve: onFnResolve});
return <InputParams
fn={fn.fn}
setFn={fnFn => setFn({...fn, fn: fnFn})}
input={fn.input}
setInput={fnInput => setFn({...fn, input: fnInput})}
onFnResolve={onFnFnResolve}
onInputResolve={onFnInputResolve}
onInputCancel={() => {/*todo*/}}/>
}
{/* Our own input */}
<Editor
state={input}
setState={setInput}
onResolve={onInputResolve}
onCancel={onInputCancel} />
</div>;
};
onInputCancel={() => {/*todo*/}}
focus={false}/>;
}

View file

@ -1,6 +1,5 @@
.typeSignature {
display: inline-block;
/* vertical-align:; */
}
.command {

View file

@ -1,22 +1,16 @@
import { getSymbol, getType, module2Env, ModuleStd, symbolFunction, getDefaultTypeParser } from "dope2";
import { getSymbol, getType, symbolFunction } from "dope2";
import { InputBlock, type InputBlockState } from "./InputBlock";
import { type Dynamic, type State2Props } from "./util/extra";
import { useEffect, useReducer, useRef, useState } from "react";
import { CallBlock, type CallBlockState } from "./CallBlock";
import { useEffect, useState } from "react";
import { InputBlock, type InputBlockState } from "./InputBlock";
import { Type } from "./Type";
import { type Dynamic, type State2Props } from "./util/extra";
import "./Editor.css"
import { focusNextElement, focusPrevElement } from "./util/dom_trickery";
import "./Editor.css";
import type { LetInBlockState } from "./LetInBlock";
interface LambdaBlockState {
kind: "lambda";
env: any;
paramName: string;
expr: EditorState;
resolved: undefined | Dynamic;
}
import { focusNextElement, focusPrevElement } from "./util/dom_trickery";
import type { LambdaBlockState } from "./LambdaBlock";
import { initialEditorState } from "./configurations";
export type EditorState =
InputBlockState
@ -24,33 +18,42 @@ export type EditorState =
| LetInBlockState
| LambdaBlockState;
const mkType = getDefaultTypeParser();
export const initialEditorState: EditorState = {
kind: "input",
env: module2Env(ModuleStd.concat([
["functionWith3Params", {i: i=>j=>k=>i+j+k, t: mkType("Int->Int->Int->Int")}],
])),
text: "",
resolved: undefined,
};
interface EditorProps extends State2Props<EditorState> {
focus: boolean;
filter: (suggestion: [string, Dynamic]) => boolean;
onResolve: (state: EditorState) => void;
onCancel: () => void;
}
const dontFilter = () => true;
function getCommands(type) {
const commands = ['u', 't', 'Enter', 'Backspace', 'ArrowLeft', 'Tab', 'l', '='];
const commands = ['u', 't', 'Enter', 'Backspace', 'ArrowLeft', 'ArrowRight', 'Tab', 'l', '='];
if (getSymbol(type) === symbolFunction) {
commands.push('c');
}
return commands;
}
export function Editor({state, setState, onResolve, onCancel}: EditorProps) {
function removeFocus(state: EditorState): EditorState {
if (state.kind === "input") {
return {...state, focus: false};
}
if (state.kind === "call") {
return {...state,
fn: removeFocus(state.fn),
input: removeFocus(state.input),
};
}
return state;
}
export function Editor({state, setState, onResolve, onCancel, filter, focus}: EditorProps) {
const [needCommand, setNeedCommand] = useState(false);
const commandInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (needCommand) {
commandInputRef.current?.focus();
}
}, [needCommand]);
const onMyResolve = (editorState: EditorState) => {
setState(editorState);
if (editorState.resolved) {
@ -82,10 +85,11 @@ export function Editor({state, setState, onResolve, onCancel}: EditorProps) {
setState({
kind: "call",
env: state.env,
fn: state,
fn: removeFocus(state),
input: initialEditorState,
resolved: undefined,
});
// focusNextElement();
return;
}
// t -> Transform
@ -95,7 +99,7 @@ export function Editor({state, setState, onResolve, onCancel}: EditorProps) {
kind: "call",
env: state.env,
fn: initialEditorState,
input: state,
input: removeFocus(state),
resolved: undefined,
});
return;
@ -104,6 +108,10 @@ export function Editor({state, setState, onResolve, onCancel}: EditorProps) {
focusPrevElement();
return;
}
if (e.key === "ArrowRight") {
focusNextElement();
return;
}
// l -> Let ... in ...
// = -> assign to name
if (e.key === 'l' || e.key === '=') {
@ -123,9 +131,17 @@ export function Editor({state, setState, onResolve, onCancel}: EditorProps) {
const renderBlock = () => {
switch (state.kind) {
case "input":
return <InputBlock state={state} setState={setState} filter={dontFilter} onResolve={onMyResolve} onCancel={onCancel} />;
return <InputBlock
state={state}
setState={setState}
filter={filter}
onResolve={onMyResolve}
onCancel={onCancel} />;
case "call":
return <CallBlock state={state} setState={setState} onResolve={onMyResolve} />;
return <CallBlock
state={state}
setState={setState}
onResolve={onMyResolve} />;
case "let":
return <></>;
case "lambda":
@ -140,12 +156,13 @@ export function Editor({state, setState, onResolve, onCancel}: EditorProps) {
:: <Type type={getType(state.resolved)} />
{ (needCommand)
? <input
ref={commandInputRef}
spellCheck={false}
autoFocus={true}
className="editable command"
placeholder="<enter command>"
onKeyDown={onCommand}
value=""/>
value={""}
onChange={() => {}} /> /* gets rid of React warning */
: <></>
}
</div>

View file

@ -1,5 +1,5 @@
import { Double, getType, Int, newDynamic, trie } from "dope2";
import { focusNextElement, focusPrevElement, getCaretPosition, setRightMostCaretPosition } from "./util/dom_trickery";
import { focusNextElement, focusPrevElement, setRightMostCaretPosition } from "./util/dom_trickery";
import { parseDouble, parseInt } from "./util/parse";
import { useEffect, useRef, useState } from "react";
@ -7,13 +7,13 @@ import { Type } from "./Type";
import "./InputBlock.css";
import type { Dynamic, State2Props } from "./util/extra";
import type { EditorState } from "./Editor";
export interface InputBlockState {
kind: "input";
env: any;
text: string;
resolved: undefined | Dynamic;
focus: boolean
}
interface InputBlockProps extends State2Props<InputBlockState> {
@ -22,70 +22,73 @@ interface InputBlockProps extends State2Props<InputBlockState> {
onCancel: () => void;
}
export function InputBlock({ state: {kind, env, text, resolved}, setState, filter, onResolve, onCancel }: InputBlockProps) {
const ref = useRef<any>(null);
useEffect(() => {
ref.current?.focus();
}, []);
const [i, setI] = useState(0); // selected suggestion
const [haveFocus, setHaveFocus] = useState(false); // whether to render suggestions or not
const setText = (text: string) => {
setState({kind, env, text, resolved});
}
const setResolved = (resolved: Dynamic) => {
setState({kind, env, text, resolved});
}
const singleSuggestion = trie.growPrefix(env.name2dyn)(text);
const computeSuggestions = (text, env, filter) => {
const asDouble = parseDouble(text);
const asInt = parseInt(text);
const suggestions = [
const ls = [
... (asDouble ? [[asDouble.toString(), newDynamic(asDouble)(Double)]] : []),
... (asInt ? [[asInt.toString(), newDynamic(BigInt(asInt))(Int)]] : []),
... trie.suggest(env.name2dyn)(text)(10),
]
return [
...ls.filter(filter), // ones that match filter come first
...ls.filter(s => !filter(s)),
];
}
... (text !== '') ? trie.suggest(env.name2dyn)(text)(Infinity) : [],
].filter(filter);
export function InputBlock({ state, setState, filter, onResolve, onCancel }: InputBlockProps) {
const {env, text, resolved, focus} = state;
const inputRef = useRef<HTMLInputElement>(null);
const [i, setI] = useState(0); // selected suggestion idx
const [haveFocus, setHaveFocus] = useState(false); // whether to render suggestions or not
const setText = (text: string) => {
setState({...state, text});
}
const singleSuggestion = trie.growPrefix(env.name2dyn)(text);
const suggestions = computeSuggestions(text, env, filter);
useEffect(() => {
setI(0); // reset
if (ref.current) {
ref.current.style.width = `${text.length === 0 ? 140 : (text.length*8.7)}px`;
if (inputRef.current) {
inputRef.current.style.width = `${text.length === 0 ? 140 : (text.length*8.7)}px`;
}
}, [text]);
useEffect(() => {
if (focus) {
inputRef.current?.focus();
}
}, [focus]);
const onSelectSuggestion = ([name, dynamic]) => {
// setText(name);
// ref.current.textContent = name;
// setRightMostCaretPosition(ref.current);
// setI(0);
// setResolved(dynamic);
console.log("onResolve inputblock..")
onResolve({
kind: "input",
env,
text: name,
resolved: dynamic,
focus: false,
});
};
const onInput = e => {
setText(e.target.value);
if (resolved) {
// un-resolve
onResolve({
kind: "input",
env,
text: e.target.value,
resolved: undefined,
focus: true,
});
}
};
const getCaretPosition = () => {
return ref.current.selectionStart;
return inputRef.current?.selectionStart || -1;
}
const onKeyDown = (e: React.KeyboardEvent) => {
@ -100,8 +103,7 @@ export function InputBlock({ state: {kind, env, text, resolved}, setState, filte
if (singleSuggestion.length > 0) {
const newText = text + singleSuggestion;
setText(newText);
// ref.current.textContent = newText;
setRightMostCaretPosition(ref.current);
setRightMostCaretPosition(inputRef.current);
e.preventDefault();
}
else {
@ -156,7 +158,7 @@ export function InputBlock({ state: {kind, env, text, resolved}, setState, filte
</span>
}
{/* Input box */}
<input ref={ref}
<input ref={inputRef}
placeholder="start typing..."
className="editable"
value={text}
@ -179,8 +181,7 @@ function Suggestions({ suggestions, onSelect, i, setI }) {
setI(j);
onSelect(suggestions[i]);
};
return (suggestions.length > 0) ?
return <>{(suggestions.length > 0) &&
<div className={"suggestions"}>
{suggestions.map(([name, dynamic], j) =>
<div
@ -189,8 +190,7 @@ function Suggestions({ suggestions, onSelect, i, setI }) {
onMouseEnter={onMouseEnter(j)}
onMouseDown={onMouseDown(j)}>
{name} :: <Type type={getType(dynamic)} />
</div>)
}
</div>)}
</div>
: <></>;
}</>;
}

11
src/LambdaBlock.tsx Normal file
View file

@ -0,0 +1,11 @@
import type { EditorState } from "./Editor";
import type { Dynamic } from "./util/extra";
export interface LambdaBlockState {
kind: "lambda";
env: any;
paramName: string;
expr: EditorState;
resolved: undefined | Dynamic;
}

View file

@ -32,7 +32,7 @@ export function Value({dynamic}) {
// case symbolSet:
// return <UnaryType type={type} cssClass="setType" prefix="{" suffix="}" />;
case symbolList:
return <List val={inst} elemType={type.params[0](type)} />;
return <ValueList val={inst} elemType={type.params[0](type)} />;
default:
return <>don't know how to show value</>;
@ -51,8 +51,8 @@ function ValueFunction() {
// function Sum({val, elemType}) {
// return
// }
function List({val, elemType}) {
return <span className="listType">[{val.map((v, i) => <Value dynamic={{i:v, t:elemType}}/>)}]</span>;
function ValueList({val, elemType}) {
return <span className="listType">[{val.map((v, i) => <Value key={i} dynamic={{i:v, t:elemType}}/>)}]</span>;
}
function ValueSum({val, leftType, rightType}) {
return match(val)

102
src/configurations.ts Normal file
View file

@ -0,0 +1,102 @@
import { getDefaultTypeParser, module2Env, ModuleStd, trie, Int, apply } from "dope2";
import type { EditorState } from "./Editor";
const mkType = getDefaultTypeParser();
export const extendedEnv = module2Env(ModuleStd.concat([
["functionWith3Params", { i: i => j => k => i + j + k, t: mkType("Int->Int->Int->Int") }],
]));
export const initialEditorState: EditorState = {
kind: "input",
env: extendedEnv,
text: "",
resolved: undefined,
focus: true,
};
const listPush = trie.get(initialEditorState.env.name2dyn)("list.push");
const listEmptyList = trie.get(initialEditorState.env.name2dyn)("list.emptyList");
const fourtyTwo = { i: 42n, t: Int };
export const nonEmptyEditorState: EditorState = {
kind: "call",
env: initialEditorState.env,
fn: {
kind: "call",
env: initialEditorState.env,
fn: {
kind: "input",
env: initialEditorState.env,
text: "list.push",
resolved: listPush,
focus: false,
},
input: {
kind: "input",
env: initialEditorState.env,
text: "list.emptyList",
resolved: listEmptyList,
focus: false,
},
resolved: apply(listEmptyList)(listPush),
// focus: ,
},
input: {
kind: "input",
env: initialEditorState.env,
text: "42",
resolved: fourtyTwo,
focus: false,
},
// resolved: apply(fourtyTwo)(apply(listEmptyList)(listPush)),
resolved: undefined,
};
const functionWith3Params = trie.get(initialEditorState.env.name2dyn)("functionWith3Params");
const fourtyThree = { i: 43n, t: Int };
const fourtyFour = { i: 44n, t: Int };
export const tripleFunctionCallEditorState: EditorState = {
kind: "call",
env: initialEditorState.env,
fn: {
kind: "call",
env: initialEditorState.env,
fn: {
kind: "call",
env: initialEditorState.env,
fn: {
kind: "input",
env: initialEditorState.env,
text: "functionWith3Params",
resolved: functionWith3Params,
focus: false,
},
input: {
kind: "input",
env: initialEditorState.env,
text: "42",
resolved: fourtyTwo,
focus: false,
},
resolved: apply(fourtyTwo)(functionWith3Params),
},
input: {
kind: "input",
env: initialEditorState.env,
text: "43",
resolved: fourtyThree,
focus: false,
},
resolved: apply(fourtyThree)(apply(fourtyTwo)(functionWith3Params)),
},
input: {
kind: "input",
env: initialEditorState.env,
text: "44",
resolved: fourtyFour,
focus: false,
},
resolved: apply(fourtyFour)(apply(fourtyThree)(apply(fourtyTwo)(functionWith3Params))),
};

6
todo.txt Normal file
View file

@ -0,0 +1,6 @@
- [DONE] figure out autofocus of text inputs to make app more usable
- maybe in a small example, see if focus can be controlled by react state?
- add 'let ... in...' block
- draw flow of events on a piece of paper to see if things can be simplified