hitting spacebar always adds a parameter to the first ancestor that is a CallBlock
This commit is contained in:
parent
5b6bcf5ffa
commit
5c3018b8c7
8 changed files with 140 additions and 43 deletions
13
src/App.tsx
13
src/App.tsx
|
|
@ -1,10 +1,9 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { GlobalContext } from './GlobalContext';
|
|
||||||
import { ExprBlock, type ExprBlockState } from './ExprBlock';
|
import { ExprBlock, type ExprBlockState } from './ExprBlock';
|
||||||
import { extendedEnv } from './EnvContext';
|
import { GlobalContext } from './GlobalContext';
|
||||||
import { biggerExample, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, tripleFunctionCallEditorState } from "./configurations";
|
import { biggerExample, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, tripleFunctionCallEditorState } from "./configurations";
|
||||||
import { evalEditorBlock } from "./eval";
|
import { removeFocus } from "./eval";
|
||||||
|
|
||||||
const commands: [string, string[], string][] = [
|
const commands: [string, string[], string][] = [
|
||||||
["call" , ['c' ], "call" ],
|
["call" , ['c' ], "call" ],
|
||||||
|
|
@ -174,6 +173,14 @@ export function App() {
|
||||||
// console.log('suggestionPriority of App, always 0');
|
// console.log('suggestionPriority of App, always 0');
|
||||||
return 0;
|
return 0;
|
||||||
}}
|
}}
|
||||||
|
addParam={(s: ExprBlockState) => {
|
||||||
|
pushHistory(state => ({
|
||||||
|
kind: "call",
|
||||||
|
fn: removeFocus(state),
|
||||||
|
input: initialEditorState,
|
||||||
|
}));
|
||||||
|
doHighlight.call();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</GlobalContext>
|
</GlobalContext>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ import { useContext } from "react";
|
||||||
|
|
||||||
import { ExprBlock, type ExprBlockState, type SetStateFn, type State2Props } from "./ExprBlock";
|
import { ExprBlock, type ExprBlockState, type SetStateFn, type State2Props } from "./ExprBlock";
|
||||||
import { EnvContext } from "./EnvContext";
|
import { EnvContext } from "./EnvContext";
|
||||||
import { evalCallBlock2, evalEditorBlock, scoreResolved, type ResolvedType } from "./eval";
|
import { addFocusRightMost, evalCallBlock2, evalEditorBlock, removeFocus, scoreResolved, type ResolvedType } from "./eval";
|
||||||
import { GlobalContext } from "./GlobalContext";
|
import { GlobalContext } from "./GlobalContext";
|
||||||
import { Value } from "./Value";
|
import { Value } from "./Value";
|
||||||
|
|
||||||
import "./CallBlock.css";
|
import "./CallBlock.css";
|
||||||
|
import { initialEditorState } from "./configurations";
|
||||||
|
|
||||||
export interface CallBlockState {
|
export interface CallBlockState {
|
||||||
kind: "call";
|
kind: "call";
|
||||||
|
|
@ -40,7 +41,7 @@ function nestedInputProperties({state, setState, suggestionPriority}: CallBlockP
|
||||||
setState(state => ({...state, input: callback(state.input)}));
|
setState(state => ({...state, input: callback(state.input)}));
|
||||||
}
|
}
|
||||||
const onInputCancel = () => {
|
const onInputCancel = () => {
|
||||||
setState(state => state.fn); // we become our function
|
setState(state => addFocusRightMost(state.fn)); // we become our function
|
||||||
}
|
}
|
||||||
const inputSuggestionPriorirty = (inputSuggestion: ResolvedType) => computePriority(
|
const inputSuggestionPriorirty = (inputSuggestion: ResolvedType) => computePriority(
|
||||||
evalEditorBlock(state.fn, env)[0], // fn *may* be set
|
evalEditorBlock(state.fn, env)[0], // fn *may* be set
|
||||||
|
|
@ -53,9 +54,18 @@ function nestedInputProperties({state, setState, suggestionPriority}: CallBlockP
|
||||||
|
|
||||||
export function CallBlock(props: CallBlockProps) {
|
export function CallBlock(props: CallBlockProps) {
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
|
const globalContext = useContext(GlobalContext);
|
||||||
|
const addParam = (s: ExprBlockState) => {
|
||||||
|
props.setState(state => ({
|
||||||
|
kind: "call",
|
||||||
|
fn: removeFocus(state),
|
||||||
|
input: s,
|
||||||
|
}));
|
||||||
|
globalContext?.doHighlight.call();
|
||||||
|
};
|
||||||
const [resolved] = evalEditorBlock(props.state, env);
|
const [resolved] = evalEditorBlock(props.state, env);
|
||||||
return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}>
|
return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}>
|
||||||
<FunctionHeader {...props} />
|
<FunctionHeader {...props} addParam={addParam} />
|
||||||
<div className="functionParams">
|
<div className="functionParams">
|
||||||
<div className="outputParam">
|
<div className="outputParam">
|
||||||
{/* Sequence of input parameters */}
|
{/* Sequence of input parameters */}
|
||||||
|
|
@ -63,6 +73,7 @@ export function CallBlock(props: CallBlockProps) {
|
||||||
{...props}
|
{...props}
|
||||||
depth={0}
|
depth={0}
|
||||||
errorDepth={(resolved.kind === "error") ? (resolved.depth) : -1}
|
errorDepth={(resolved.kind === "error") ? (resolved.depth) : -1}
|
||||||
|
addParam={addParam}
|
||||||
/>
|
/>
|
||||||
{ (resolved.kind === "error") && resolved.e.toString()
|
{ (resolved.kind === "error") && resolved.e.toString()
|
||||||
|| (resolved.kind === "value") && <Value dynamic={resolved} />
|
|| (resolved.kind === "value") && <Value dynamic={resolved} />
|
||||||
|
|
@ -91,12 +102,12 @@ function FunctionHeader(props) {
|
||||||
// end of recursion - draw function name
|
// end of recursion - draw function name
|
||||||
return <span className="functionName">
|
return <span className="functionName">
|
||||||
𝑓𝑛
|
𝑓𝑛
|
||||||
<ExprBlock {...nestedProperties} />
|
<ExprBlock {...nestedProperties} addParam={props.addParam} />
|
||||||
</span>;
|
</span>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputParams({ depth, errorDepth, ...rest }) {
|
function InputParams({ depth, errorDepth, addParam, ...rest }) {
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
const globalContext = useContext(GlobalContext);
|
const globalContext = useContext(GlobalContext);
|
||||||
const isOffending = depth === errorDepth;
|
const isOffending = depth === errorDepth;
|
||||||
|
|
@ -107,10 +118,12 @@ function InputParams({ depth, errorDepth, ...rest }) {
|
||||||
{...nestedFnProperties(rest as CallBlockProps, env)}
|
{...nestedFnProperties(rest as CallBlockProps, env)}
|
||||||
depth={depth+1}
|
depth={depth+1}
|
||||||
errorDepth={errorDepth}
|
errorDepth={errorDepth}
|
||||||
|
addParam={addParam}
|
||||||
/>}
|
/>}
|
||||||
{/* Our own input param */}
|
{/* Our own input param */}
|
||||||
<ExprBlock
|
<ExprBlock
|
||||||
{...nestedInputProperties(rest as CallBlockProps, env)}
|
{...nestedInputProperties(rest as CallBlockProps, env)}
|
||||||
|
addParam={addParam}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { LambdaBlock, type LambdaBlockState } from "./LambdaBlock";
|
||||||
import { LetInBlock, type LetInBlockState } from "./LetInBlock";
|
import { LetInBlock, type LetInBlockState } from "./LetInBlock";
|
||||||
import { Type } from "./Type";
|
import { Type } from "./Type";
|
||||||
import { initialEditorState } from "./configurations";
|
import { initialEditorState } from "./configurations";
|
||||||
import { evalEditorBlock, type ResolvedType } from "./eval";
|
import { evalEditorBlock, removeFocus, type ResolvedType } from "./eval";
|
||||||
import { focusNextElement, focusPrevElement } from "./util/dom_trickery";
|
import { focusNextElement, focusPrevElement } from "./util/dom_trickery";
|
||||||
|
|
||||||
import "./ExprBlock.css";
|
import "./ExprBlock.css";
|
||||||
|
|
@ -31,6 +31,7 @@ export interface State2Props<InType, OutType = InType> {
|
||||||
|
|
||||||
interface ExprBlockProps extends State2Props<ExprBlockState> {
|
interface ExprBlockProps extends State2Props<ExprBlockState> {
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
addParam: (e: ExprBlockState) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCommands(type) {
|
function getCommands(type) {
|
||||||
|
|
@ -47,20 +48,7 @@ function getShortCommands(type) {
|
||||||
return 'Tab|.';
|
return 'Tab|.';
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFocus(state: ExprBlockState): ExprBlockState {
|
export function ExprBlock({state, setState, suggestionPriority, onCancel, addParam}: ExprBlockProps) {
|
||||||
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 ExprBlock({state, setState, onCancel, suggestionPriority}: ExprBlockProps) {
|
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
const [needCommand, setNeedCommand] = useState(false);
|
const [needCommand, setNeedCommand] = useState(false);
|
||||||
const commandInputRef = useRef<HTMLInputElement>(null);
|
const commandInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
@ -95,7 +83,6 @@ export function ExprBlock({state, setState, onCancel, suggestionPriority}: ExprB
|
||||||
kind: "call",
|
kind: "call",
|
||||||
fn: removeFocus(state),
|
fn: removeFocus(state),
|
||||||
input: initialEditorState,
|
input: initialEditorState,
|
||||||
resolved: undefined,
|
|
||||||
}));
|
}));
|
||||||
globalContext?.doHighlight.call();
|
globalContext?.doHighlight.call();
|
||||||
// focusNextElement();
|
// focusNextElement();
|
||||||
|
|
@ -108,7 +95,6 @@ export function ExprBlock({state, setState, onCancel, suggestionPriority}: ExprB
|
||||||
kind: "call",
|
kind: "call",
|
||||||
fn: initialEditorState,
|
fn: initialEditorState,
|
||||||
input: removeFocus(state),
|
input: removeFocus(state),
|
||||||
resolved: undefined,
|
|
||||||
}));
|
}));
|
||||||
globalContext?.doHighlight.transform();
|
globalContext?.doHighlight.transform();
|
||||||
return;
|
return;
|
||||||
|
|
@ -127,9 +113,10 @@ export function ExprBlock({state, setState, onCancel, suggestionPriority}: ExprB
|
||||||
// we become LetInBlock
|
// we become LetInBlock
|
||||||
setState(state => ({
|
setState(state => ({
|
||||||
kind: "let",
|
kind: "let",
|
||||||
inner: removeFocus(initialEditorState),
|
|
||||||
name: "",
|
name: "",
|
||||||
|
focus: true,
|
||||||
value: removeFocus(state),
|
value: removeFocus(state),
|
||||||
|
inner: removeFocus(initialEditorState),
|
||||||
}));
|
}));
|
||||||
globalContext?.doHighlight.let();
|
globalContext?.doHighlight.let();
|
||||||
return;
|
return;
|
||||||
|
|
@ -137,9 +124,10 @@ export function ExprBlock({state, setState, onCancel, suggestionPriority}: ExprB
|
||||||
if (e.key === 'L' || e.key === '=' && e.shiftKey) {
|
if (e.key === 'L' || e.key === '=' && e.shiftKey) {
|
||||||
setState(state => ({
|
setState(state => ({
|
||||||
kind: "let",
|
kind: "let",
|
||||||
inner: removeFocus(state),
|
|
||||||
name: "",
|
name: "",
|
||||||
|
focus: true,
|
||||||
value: removeFocus(initialEditorState),
|
value: removeFocus(initialEditorState),
|
||||||
|
inner: removeFocus(state),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
// a -> lAmbdA
|
// a -> lAmbdA
|
||||||
|
|
@ -147,6 +135,7 @@ export function ExprBlock({state, setState, onCancel, suggestionPriority}: ExprB
|
||||||
setState(state => ({
|
setState(state => ({
|
||||||
kind: "lambda",
|
kind: "lambda",
|
||||||
paramName: "",
|
paramName: "",
|
||||||
|
focus: true,
|
||||||
expr: removeFocus(state),
|
expr: removeFocus(state),
|
||||||
}));
|
}));
|
||||||
globalContext?.doHighlight.lambda();
|
globalContext?.doHighlight.lambda();
|
||||||
|
|
@ -162,6 +151,7 @@ export function ExprBlock({state, setState, onCancel, suggestionPriority}: ExprB
|
||||||
setState={setState as (callback:(p:InputBlockState)=>ExprBlockState)=>void}
|
setState={setState as (callback:(p:InputBlockState)=>ExprBlockState)=>void}
|
||||||
suggestionPriority={suggestionPriority}
|
suggestionPriority={suggestionPriority}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
|
addParam={addParam}
|
||||||
/>;
|
/>;
|
||||||
case "call":
|
case "call":
|
||||||
return <CallBlock
|
return <CallBlock
|
||||||
|
|
@ -174,12 +164,14 @@ export function ExprBlock({state, setState, onCancel, suggestionPriority}: ExprB
|
||||||
state={state}
|
state={state}
|
||||||
setState={setState as (callback:(p:LetInBlockState)=>ExprBlockState)=>void}
|
setState={setState as (callback:(p:LetInBlockState)=>ExprBlockState)=>void}
|
||||||
suggestionPriority={suggestionPriority}
|
suggestionPriority={suggestionPriority}
|
||||||
|
addParam={addParam}
|
||||||
/>;
|
/>;
|
||||||
case "lambda":
|
case "lambda":
|
||||||
return <LambdaBlock
|
return <LambdaBlock
|
||||||
state={state}
|
state={state}
|
||||||
setState={setState as (callback:(p:LambdaBlockState)=>ExprBlockState)=>void}
|
setState={setState as (callback:(p:LambdaBlockState)=>ExprBlockState)=>void}
|
||||||
suggestionPriority={suggestionPriority}
|
suggestionPriority={suggestionPriority}
|
||||||
|
addParam={addParam}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
cursor: text;
|
||||||
|
outline: 0;
|
||||||
}
|
}
|
||||||
.suggest {
|
.suggest {
|
||||||
top: 2.4px;
|
top: 2.4px;
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,12 @@ import { EnvContext } from "./EnvContext";
|
||||||
import type { Dynamic, ResolvedType } from "./eval";
|
import type { Dynamic, ResolvedType } from "./eval";
|
||||||
import "./InputBlock.css";
|
import "./InputBlock.css";
|
||||||
import { Type } from "./Type";
|
import { Type } from "./Type";
|
||||||
import type { State2Props } from "./ExprBlock";
|
import type { ExprBlockState, State2Props } from "./ExprBlock";
|
||||||
import { autoInputWidth, focusNextElement, focusPrevElement, setRightMostCaretPosition } from "./util/dom_trickery";
|
import { autoInputWidth, focusNextElement, focusPrevElement, setRightMostCaretPosition } from "./util/dom_trickery";
|
||||||
import { attemptParseLiteral } from "./eval";
|
import { attemptParseLiteral, removeFocus } from "./eval";
|
||||||
|
import { GlobalContext } from "./GlobalContext";
|
||||||
|
import { initialEditorState } from "./configurations";
|
||||||
|
import type { CallBlockState } from "./CallBlock";
|
||||||
|
|
||||||
interface Literal {
|
interface Literal {
|
||||||
kind: "literal";
|
kind: "literal";
|
||||||
|
|
@ -32,8 +35,9 @@ export interface InputBlockState {
|
||||||
export type SuggestionType = ['literal'|'name', string, Dynamic];
|
export type SuggestionType = ['literal'|'name', string, Dynamic];
|
||||||
export type PrioritizedSuggestionType = [number, ...SuggestionType];
|
export type PrioritizedSuggestionType = [number, ...SuggestionType];
|
||||||
|
|
||||||
interface InputBlockProps extends State2Props<InputBlockState> {
|
interface InputBlockProps extends State2Props<InputBlockState,ExprBlockState> {
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
addParam: (e: ExprBlockState) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const computeSuggestions = (text, env, suggestionPriority: (s: ResolvedType) => number): PrioritizedSuggestionType[] => {
|
const computeSuggestions = (text, env, suggestionPriority: (s: ResolvedType) => number): PrioritizedSuggestionType[] => {
|
||||||
|
|
@ -55,11 +59,12 @@ const computeSuggestions = (text, env, suggestionPriority: (s: ResolvedType) =>
|
||||||
]
|
]
|
||||||
// return []; // <-- uncomment to disable suggestions (useful for debugging)
|
// return []; // <-- uncomment to disable suggestions (useful for debugging)
|
||||||
return ls
|
return ls
|
||||||
.map(suggestion => [suggestionPriority(suggestion[2]), ...suggestion] as PrioritizedSuggestionType)
|
.map((suggestion) => [suggestionPriority(suggestion[2]), ...suggestion] as PrioritizedSuggestionType)
|
||||||
.sort(([priorityA], [priorityB]) => priorityB - priorityA)
|
.sort(([priorityA], [priorityB]) => priorityB - priorityA)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InputBlock({ state, setState, suggestionPriority, onCancel }: InputBlockProps) {
|
export function InputBlock({ state, setState, suggestionPriority, onCancel, addParam }: InputBlockProps) {
|
||||||
|
const globalContext = useContext(GlobalContext);
|
||||||
const {text, focus} = state;
|
const {text, focus} = state;
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
@ -147,7 +152,13 @@ export function InputBlock({ state, setState, suggestionPriority, onCancel }: In
|
||||||
onCancel();
|
onCancel();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
" ": () => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (text.length > 0) {
|
||||||
|
addParam(initialEditorState);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
fns[e.key]?.();
|
fns[e.key]?.();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,24 @@ import { autoInputWidth } from "./util/dom_trickery";
|
||||||
|
|
||||||
import "./LambdaBlock.css";
|
import "./LambdaBlock.css";
|
||||||
import { Type } from "./Type";
|
import { Type } from "./Type";
|
||||||
|
import type { CallBlockState } from "./CallBlock";
|
||||||
|
|
||||||
export interface LambdaBlockState {
|
export interface LambdaBlockState {
|
||||||
kind: "lambda";
|
kind: "lambda";
|
||||||
paramName: string;
|
paramName: string;
|
||||||
|
focus: boolean;
|
||||||
expr: ExprBlockState;
|
expr: ExprBlockState;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LambdaBlockProps<
|
interface LambdaBlockProps<
|
||||||
FnState=ExprBlockState,
|
FnState=ExprBlockState,
|
||||||
InputState=ExprBlockState,
|
InputState=ExprBlockState,
|
||||||
> extends State2Props<LambdaBlockState,ExprBlockState> {}
|
> extends State2Props<LambdaBlockState,ExprBlockState> {
|
||||||
|
addParam: (e: ExprBlockState) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockProps) {
|
export function LambdaBlock({state, setState, suggestionPriority, addParam}: LambdaBlockProps) {
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
const nameRef = useRef<HTMLInputElement>(null);
|
const nameRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
|
@ -43,8 +47,10 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (state.focus) {
|
||||||
nameRef.current?.focus();
|
nameRef.current?.focus();
|
||||||
}, []);
|
}
|
||||||
|
}, [state.focus]);
|
||||||
|
|
||||||
useEffect(() => autoInputWidth(nameRef, state.paramName, 60), [nameRef, state.paramName]);
|
useEffect(() => autoInputWidth(nameRef, state.paramName, 60), [nameRef, state.paramName]);
|
||||||
|
|
||||||
|
|
@ -75,6 +81,7 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr
|
||||||
placeholder="<name>"
|
placeholder="<name>"
|
||||||
onKeyDown={onChangeName}
|
onKeyDown={onChangeName}
|
||||||
onChange={e => setParamName(e.target.value)}
|
onChange={e => setParamName(e.target.value)}
|
||||||
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<div className="typeSignature">
|
<div className="typeSignature">
|
||||||
|
|
@ -93,6 +100,7 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr
|
||||||
// console.log('suggestionPriority of lambdaInner... just passing through');
|
// console.log('suggestionPriority of lambdaInner... just passing through');
|
||||||
return suggestionPriority(s);
|
return suggestionPriority(s);
|
||||||
}}
|
}}
|
||||||
|
addParam={addParam}
|
||||||
/>
|
/>
|
||||||
</EnvContext>
|
</EnvContext>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,19 @@ import { autoInputWidth } from "./util/dom_trickery";
|
||||||
import { GlobalContext } from "./GlobalContext";
|
import { GlobalContext } from "./GlobalContext";
|
||||||
|
|
||||||
import "./LetInBlock.css";
|
import "./LetInBlock.css";
|
||||||
|
import type { CallBlockState } from "./CallBlock";
|
||||||
|
|
||||||
export interface LetInBlockState {
|
export interface LetInBlockState {
|
||||||
kind: "let";
|
kind: "let";
|
||||||
name: string;
|
name: string;
|
||||||
|
focus: boolean;
|
||||||
value: ExprBlockState;
|
value: ExprBlockState;
|
||||||
inner: ExprBlockState;
|
inner: ExprBlockState;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LetInBlockProps extends State2Props<LetInBlockState,ExprBlockState> {}
|
interface LetInBlockProps extends State2Props<LetInBlockState,ExprBlockState> {
|
||||||
|
addParam: (e: ExprBlockState) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export function LetInBlock(props: LetInBlockProps) {
|
export function LetInBlock(props: LetInBlockProps) {
|
||||||
return <span className="letIn">
|
return <span className="letIn">
|
||||||
|
|
@ -29,7 +33,7 @@ export function LetInBlock(props: LetInBlockProps) {
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
function DeclColumns({state: {name, value, inner}, setState, suggestionPriority}) {
|
function DeclColumns({state: {name, value, inner, focus}, setState, suggestionPriority, addParam}) {
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
const globalContext = useContext(GlobalContext);
|
const globalContext = useContext(GlobalContext);
|
||||||
|
|
||||||
|
|
@ -47,8 +51,11 @@ function DeclColumns({state: {name, value, inner}, setState, suggestionPriority}
|
||||||
|
|
||||||
const nameRef = useRef<HTMLInputElement>(null);
|
const nameRef = useRef<HTMLInputElement>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (focus) {
|
||||||
nameRef.current?.focus();
|
nameRef.current?.focus();
|
||||||
}, []);
|
}
|
||||||
|
}, [focus]);
|
||||||
|
|
||||||
useEffect(() => autoInputWidth(nameRef, name, 60), [nameRef, name]);
|
useEffect(() => autoInputWidth(nameRef, name, 60), [nameRef, name]);
|
||||||
|
|
||||||
const [valueResolved] = evalEditorBlock(value, env);
|
const [valueResolved] = evalEditorBlock(value, env);
|
||||||
|
|
@ -63,6 +70,7 @@ function DeclColumns({state: {name, value, inner}, setState, suggestionPriority}
|
||||||
value={name}
|
value={name}
|
||||||
placeholder="<name>"
|
placeholder="<name>"
|
||||||
onChange={onChangeName}
|
onChange={onChangeName}
|
||||||
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span className="keyword column"> = </span>
|
<span className="keyword column"> = </span>
|
||||||
|
|
@ -72,6 +80,7 @@ function DeclColumns({state: {name, value, inner}, setState, suggestionPriority}
|
||||||
setState={setValue}
|
setState={setValue}
|
||||||
suggestionPriority={valueSuggestionPriority}
|
suggestionPriority={valueSuggestionPriority}
|
||||||
onCancel={() => setState(state => state.inner)} // keep inner
|
onCancel={() => setState(state => state.inner)} // keep inner
|
||||||
|
addParam={addParam}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
{inner.kind === "let" &&
|
{inner.kind === "let" &&
|
||||||
|
|
@ -81,13 +90,14 @@ function DeclColumns({state: {name, value, inner}, setState, suggestionPriority}
|
||||||
state={inner}
|
state={inner}
|
||||||
setState={setInner}
|
setState={setInner}
|
||||||
suggestionPriority={suggestionPriority}
|
suggestionPriority={suggestionPriority}
|
||||||
|
addParam={addParam}
|
||||||
/>
|
/>
|
||||||
</EnvContext>
|
</EnvContext>
|
||||||
}
|
}
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function InnerMost({state, setState, suggestionPriority}) {
|
function InnerMost({state, setState, suggestionPriority, addParam}) {
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
const globalContext = useContext(GlobalContext);
|
const globalContext = useContext(GlobalContext);
|
||||||
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
|
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
|
||||||
|
|
@ -100,6 +110,7 @@ function InnerMost({state, setState, suggestionPriority}) {
|
||||||
state={state.inner}
|
state={state.inner}
|
||||||
setState={setInner}
|
setState={setInner}
|
||||||
suggestionPriority={suggestionPriority}
|
suggestionPriority={suggestionPriority}
|
||||||
|
addParam={addParam}
|
||||||
/>
|
/>
|
||||||
</EnvContext>;
|
</EnvContext>;
|
||||||
}
|
}
|
||||||
|
|
@ -110,6 +121,7 @@ function InnerMost({state, setState, suggestionPriority}) {
|
||||||
setState={setInner}
|
setState={setInner}
|
||||||
suggestionPriority={suggestionPriority}
|
suggestionPriority={suggestionPriority}
|
||||||
onCancel={onCancel} // keep value
|
onCancel={onCancel} // keep value
|
||||||
|
addParam={addParam}
|
||||||
/>
|
/>
|
||||||
</EnvContext>
|
</EnvContext>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
52
src/eval.ts
52
src/eval.ts
|
|
@ -471,3 +471,55 @@ function makeError(env: Environment, e: Error, unification: Unification=new Map(
|
||||||
typeVars: new Set([...env.typeVars, UNBOUND_SYMBOLS[idx]]),
|
typeVars: new Set([...env.typeVars, UNBOUND_SYMBOLS[idx]]),
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function removeFocus(state: ExprBlockState): ExprBlockState {
|
||||||
|
if (state.kind === "input") {
|
||||||
|
return { ...state, focus: false };
|
||||||
|
}
|
||||||
|
else if (state.kind === "call") {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
fn: removeFocus(state.fn),
|
||||||
|
input: removeFocus(state.input),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (state.kind === "lambda") {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
focus: false,
|
||||||
|
expr: removeFocus(state.expr),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else { // state.kind === "let"
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
focus: false,
|
||||||
|
value: removeFocus(state.value),
|
||||||
|
inner: removeFocus(state.inner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addFocusRightMost(state: ExprBlockState) : ExprBlockState {
|
||||||
|
if (state.kind === "input") {
|
||||||
|
return { ...state, focus: true };
|
||||||
|
}
|
||||||
|
else if (state.kind === "call") {
|
||||||
|
return {
|
||||||
|
... state,
|
||||||
|
input: addFocusRightMost(state.input),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (state.kind === "lambda") {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
expr: addFocusRightMost(state.expr),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else { // state.kind === "let"
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
inner: addFocusRightMost(state.inner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue