import { getSymbol, getType, symbolFunction } from "dope2"; import { useEffect, useReducer, useRef, useState } from "react"; import { CallBlock, type CallBlockState } from "./CallBlock"; import { InputBlock, type InputBlockState } from "./InputBlock"; import { Type } from "./Type"; import { type Dynamic, type State2Props } from "./util/extra"; import "./Editor.css"; import type { LetInBlockState } from "./LetInBlock"; import { focusNextElement, focusPrevElement } from "./util/dom_trickery"; import type { LambdaBlockState } from "./LambdaBlock"; import { initialEditorState } from "./configurations"; export type EditorState = InputBlockState | CallBlockState | LetInBlockState | LambdaBlockState; interface EditorProps extends State2Props { focus: boolean; filter: (suggestion: [string, Dynamic]) => boolean; onResolve: (state: EditorState) => void; onCancel: () => void; } function getCommands(type) { const commands = ['u', 't', 'Enter', 'Backspace', 'ArrowLeft', 'ArrowRight', 'Tab', 'l', '=']; if (getSymbol(type) === symbolFunction) { commands.push('c'); } return commands; } 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(null); useEffect(() => { if (needCommand) { commandInputRef.current?.focus(); } }, [needCommand]); const onMyResolve = (editorState: EditorState) => { setState(editorState); if (editorState.resolved) { setNeedCommand(true); } else { // unresolved setNeedCommand(false); onResolve(editorState); // pass up the fact that we're unresolved } } // const onMyCancel const onCommand = (e: React.KeyboardEvent) => { const type = getType(state.resolved); const commands = getCommands(type); if (!commands.includes(e.key)) { return; } e.preventDefault(); setNeedCommand(false); // u -> pass Up if (e.key === "u" || e.key === "Enter" || e.key === "Tab") { onResolve(state); return; } // c -> Call if (e.key === "c") { // we become CallBlock setState({ kind: "call", env: state.env, fn: removeFocus(state), input: initialEditorState, resolved: undefined, }); // focusNextElement(); return; } // t -> Transform if (e.key === "t") { // we become CallBlock setState({ kind: "call", env: state.env, fn: initialEditorState, input: removeFocus(state), resolved: undefined, }); return; } if (e.key === "Backspace" || e.key === "ArrowLeft") { focusPrevElement(); return; } if (e.key === "ArrowRight") { focusNextElement(); return; } // l -> Let ... in ... // = -> assign to name if (e.key === 'l' || e.key === '=') { // we become LetInBlock setState({ kind: "let", env: state.env, inner: initialEditorState, name: "", value: state, resolved: undefined, }); return; } }; const renderBlock = () => { switch (state.kind) { case "input": return ; case "call": return ; case "let": return <>; case "lambda": return <>; } } return <> {renderBlock()} { (state.resolved) ?
:: { (needCommand) ? {}} /> /* gets rid of React warning */ : <> }
: <> } ; }