116 lines
No EOL
3.6 KiB
TypeScript
116 lines
No EOL
3.6 KiB
TypeScript
import { useContext, useEffect, useRef } from "react";
|
|
|
|
import { ExprBlock, type ExprBlockState } from "./ExprBlock";
|
|
import { EnvContext } from "./EnvContext";
|
|
import { evalEditorBlock, makeInnerEnv, scoreResolved, type ResolvedType } from "./eval";
|
|
import { type State2Props } from "./ExprBlock";
|
|
import { autoInputWidth } from "./util/dom_trickery";
|
|
import { GlobalContext } from "./GlobalContext";
|
|
|
|
import "./LetInBlock.css";
|
|
|
|
export interface LetInBlockState {
|
|
kind: "let";
|
|
name: string;
|
|
value: ExprBlockState;
|
|
inner: ExprBlockState;
|
|
}
|
|
|
|
interface LetInBlockProps extends State2Props<LetInBlockState,ExprBlockState> {}
|
|
|
|
export function LetInBlock(props: LetInBlockProps) {
|
|
return <span className="letIn">
|
|
<div className="decl">
|
|
<DeclColumns {...props} />
|
|
</div>
|
|
<div className="inner">
|
|
<InnerMost {...props} />
|
|
</div>
|
|
</span>
|
|
}
|
|
|
|
function DeclColumns({state: {name, value, inner}, setState, suggestionPriority}) {
|
|
const env = useContext(EnvContext);
|
|
const globalContext = useContext(GlobalContext);
|
|
|
|
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
|
|
const setValue = callback => setState(state => ({...state, value: callback(state.value)}));
|
|
const onChangeName = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
setState(state => ({...state, name: e.target.value}));
|
|
}
|
|
|
|
const valueSuggestionPriority = (suggestion: ResolvedType) => {
|
|
const innerEnv = makeInnerEnv(env, name, suggestion);
|
|
const resolved = evalEditorBlock(inner, innerEnv);
|
|
return scoreResolved(resolved, suggestionPriority);
|
|
};
|
|
|
|
const nameRef = useRef<HTMLInputElement>(null);
|
|
useEffect(() => {
|
|
nameRef.current?.focus();
|
|
}, []);
|
|
useEffect(() => autoInputWidth(nameRef, name, 60), [nameRef, name]);
|
|
|
|
const valueResolved = evalEditorBlock(value, env);
|
|
const innerEnv = makeInnerEnv(env, name, valueResolved);
|
|
|
|
return <>
|
|
<span className="keyword column">let </span>
|
|
<span className="column rightAlign">
|
|
<input
|
|
ref={nameRef}
|
|
className='editable'
|
|
value={name}
|
|
placeholder="<name>"
|
|
onChange={onChangeName}
|
|
/>
|
|
</span>
|
|
<span className="keyword column"> = </span>
|
|
<span className="column">
|
|
<ExprBlock
|
|
state={value}
|
|
setState={setValue}
|
|
suggestionPriority={valueSuggestionPriority}
|
|
onCancel={() => setState(state => state.inner)} // keep inner
|
|
/>
|
|
</span>
|
|
{inner.kind === "let" &&
|
|
globalContext?.syntacticSugar &&
|
|
<EnvContext value={innerEnv}>
|
|
<DeclColumns
|
|
state={inner}
|
|
setState={setInner}
|
|
suggestionPriority={suggestionPriority}
|
|
/>
|
|
</EnvContext>
|
|
}
|
|
</>;
|
|
}
|
|
|
|
function InnerMost({state, setState, suggestionPriority}) {
|
|
const env = useContext(EnvContext);
|
|
const globalContext = useContext(GlobalContext);
|
|
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
|
|
const valueResolved = evalEditorBlock(state.value, env);
|
|
const innerEnv = makeInnerEnv(env, state.name, valueResolved);
|
|
const onCancel = () => setState(state => state.value);
|
|
if (state.inner.kind === "let" && globalContext?.syntacticSugar) {
|
|
return <EnvContext value={innerEnv}>
|
|
<InnerMost
|
|
state={state.inner}
|
|
setState={setInner}
|
|
suggestionPriority={suggestionPriority}
|
|
/>
|
|
</EnvContext>;
|
|
}
|
|
else {
|
|
return <EnvContext value={innerEnv}>
|
|
<ExprBlock
|
|
state={state.inner}
|
|
setState={setInner}
|
|
suggestionPriority={suggestionPriority}
|
|
onCancel={onCancel} // keep value
|
|
/>
|
|
</EnvContext>
|
|
}
|
|
} |