making some good progress
This commit is contained in:
parent
5f3d697866
commit
e901fc3f76
15 changed files with 546 additions and 165 deletions
220
src/Editor.tsx
220
src/Editor.tsx
|
|
@ -1,138 +1,100 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import {growPrefix, suggest, getType, symbolFunction, symbolProduct, symbolSum, symbolDict, symbolSet, symbolList, symbolSetIterator, symbolDictIterator, getSymbol, getHumanReadableName} from "dope2";
|
||||
import { getSymbol, getType, module2Env, ModuleStd, symbolFunction } from "dope2";
|
||||
|
||||
import "./Editor.css";
|
||||
import { InputBlock, type InputBlockState } from "./InputBlock";
|
||||
import { type Dynamic, type State2Props } from "./util/extra";
|
||||
import { CallBlock, type CallBlockState } from "./CallBlock";
|
||||
|
||||
export function Editor({env}) {
|
||||
return <Block env={env} />;
|
||||
interface LetInBlockState {
|
||||
kind: "let";
|
||||
env: any;
|
||||
name: string;
|
||||
value: EditorState;
|
||||
inner: EditorState;
|
||||
}
|
||||
|
||||
function getCursorPosition() {
|
||||
const selection = window.getSelection();
|
||||
if (selection) {
|
||||
const range = selection.getRangeAt(0);
|
||||
const clonedRange = range.cloneRange();
|
||||
return clonedRange.startOffset;
|
||||
interface LambdaBlockState {
|
||||
kind: "lambda";
|
||||
paramName: string;
|
||||
expr: EditorState;
|
||||
}
|
||||
|
||||
export type EditorState =
|
||||
InputBlockState
|
||||
| CallBlockState
|
||||
| LetInBlockState
|
||||
| LambdaBlockState;
|
||||
|
||||
export const initialEditorState: EditorState = {
|
||||
kind: "input",
|
||||
env: module2Env(ModuleStd),
|
||||
text: "",
|
||||
resolved: undefined,
|
||||
};
|
||||
|
||||
type EditorProps = State2Props<EditorState>;
|
||||
|
||||
const dontFilter = () => true;
|
||||
|
||||
export function Editor({state, setState}: EditorProps) {
|
||||
let onResolve;
|
||||
switch (state.kind) {
|
||||
case "input":
|
||||
onResolve = (resolved: InputBlockState) => {
|
||||
console.log('resolved!', state, resolved);
|
||||
if (resolved) {
|
||||
const type = getType(resolved.resolved);
|
||||
if (getSymbol(type) === symbolFunction) {
|
||||
console.log('function!');
|
||||
// we were InputBlock
|
||||
// now we become FunctionBlock
|
||||
setState({
|
||||
kind: "call",
|
||||
env: state.env,
|
||||
fn: resolved,
|
||||
input: initialEditorState,
|
||||
})
|
||||
}
|
||||
}
|
||||
// const type = getType(state.resolved);
|
||||
// if (getSymbol(type) === symbolFunction) {
|
||||
// console.log('function!');
|
||||
// console.log('editor state:', state);
|
||||
// }
|
||||
}
|
||||
return <InputBlock state={state} setState={setState} filter={dontFilter} onResolve={onResolve} />;
|
||||
case "call":
|
||||
onResolve = (d: Dynamic) => {}
|
||||
return <CallBlock state={state} setState={setState} onResolve={onResolve} />;
|
||||
case "let":
|
||||
return <></>;
|
||||
case "lambda":
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
function setCursorPosition(elem, pos) {
|
||||
const range = document.createRange();
|
||||
range.selectNode(elem);
|
||||
range.setStart(elem, pos);
|
||||
range.setEnd(elem, pos);
|
||||
const selection = window.getSelection();
|
||||
if (!selection) {
|
||||
console.log('no selection!')
|
||||
}
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
}
|
||||
// function DynamicBlock({env, name, dynamic}) {
|
||||
// const type = getType(dynamic);
|
||||
// if (getSymbol(type) === symbolFunction) {
|
||||
// return <FunctionBlock env={env} name={name} funDynamic={dynamic} />;
|
||||
// }
|
||||
// else return <>{getInst(dynamic).toString()} :: <Type type={type}/></>;
|
||||
// }
|
||||
|
||||
function Block({env}) {
|
||||
const [text, setText] = useState("edit me!");
|
||||
const ref = useRef<any>(null);
|
||||
const singleSuggestion = growPrefix(env.name2dyn)(text);
|
||||
const suggestions = suggest(env.name2dyn)(text)(16);
|
||||
const [i, setI] = useState(0);
|
||||
const resetFocus = () => {
|
||||
ref.current?.focus();
|
||||
};
|
||||
useEffect(resetFocus, [ref.current])
|
||||
const onSelect = ([name]) => {
|
||||
setText(name);
|
||||
ref.current.innerText = name;
|
||||
setCursorPosition(ref.current.lastChild, name.length);
|
||||
setI(0);
|
||||
}
|
||||
const onInput = e => {
|
||||
const pos = getCursorPosition();
|
||||
setText(e.target.innerText);
|
||||
setCursorPosition(e.target.lastChild, pos);
|
||||
};
|
||||
const onKeyDown = e => {
|
||||
if (e.key === "Tab") {
|
||||
const newText = text + singleSuggestion;
|
||||
setText(newText);
|
||||
ref.current.innerText = newText;
|
||||
setCursorPosition(ref.current.lastChild, newText.length);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (e.key === "ArrowDown") {
|
||||
setI((i + 1) % suggestions.length);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (e.key === "ArrowUp") {
|
||||
setI((i - 1) % suggestions.length);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (e.key === "Enter") {
|
||||
onSelect(suggestions[i]);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
};
|
||||
return <span>
|
||||
<span ref={ref} contentEditable="plaintext-only" onInput={onInput} tabIndex={0} onKeyDown={onKeyDown} onBlur={() =>{
|
||||
// hacky, but couldn't find another way:
|
||||
setTimeout(resetFocus, 0);
|
||||
}}></span>
|
||||
<Suggestions suggestions={suggestions} onSelect={onSelect} i={i} setI={setI}/>
|
||||
<span className="text-block suggest">{singleSuggestion}</span>
|
||||
</span>;
|
||||
}
|
||||
// function InputBlock({env, done, type}) {
|
||||
// const filterInputType = ([_, dynamic]) => {
|
||||
// try {
|
||||
// unify(type, getType(dynamic));
|
||||
// return true;
|
||||
// } catch (e) {
|
||||
// if (!(e instanceof UnifyError)) {
|
||||
// console.error(e);
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// return <>
|
||||
// <span className="typeAnnot"><InputBlock env={env} done={done} filter={filterInputType} /></span>
|
||||
// <span className="typeAnnot">:: <Type type={type}/></span>
|
||||
// </>;
|
||||
// }
|
||||
|
||||
function Suggestions({suggestions, onSelect, i, setI}) {
|
||||
return (suggestions.length > 0) ?
|
||||
<div className="suggestions">
|
||||
{suggestions.map(([name, dynamic], j) => <div key={`${i}_${name}`} className={i===j?"selected":""} onClick={() => setI(j)} onDoubleClick={() => onSelect(suggestions[i])}>{name} :: <Type type={getType(dynamic)}/></div>)}
|
||||
</div>
|
||||
: <></>;
|
||||
}
|
||||
|
||||
|
||||
function Type({type}) {
|
||||
const symbol = getSymbol(type);
|
||||
switch (symbol) {
|
||||
case symbolFunction:
|
||||
return <BinaryType type={type} cssClass="functionType" infix="→" prefix="" suffix=""/>;
|
||||
case symbolProduct:
|
||||
return <BinaryType type={type} cssClass="productType" infix="⨯" prefix="" suffix=""/>;
|
||||
case symbolSum:
|
||||
return <BinaryType type={type} cssClass="sumType" infix="+" prefix="" suffix=""/>;
|
||||
case symbolDict:
|
||||
return <BinaryType type={type} cssClass="dictType" infix="⇒" prefix="{" suffix="}"/>;
|
||||
case symbolSet:
|
||||
return <UnaryType type={type} cssClass="setType" prefix="{" suffix="}" />;
|
||||
case symbolList:
|
||||
return <UnaryType type={type} cssClass="listType" prefix="[" suffix="]" />;
|
||||
case symbolSetIterator:
|
||||
return <UnaryType type={type} cssClass="setType iteratorType" prefix="{*" suffix="}" />;
|
||||
case symbolDictIterator:
|
||||
return <BinaryType type={type} cssClass="dictType iteratorType" infix="*⇒" prefix="{" suffix="}"/>;
|
||||
|
||||
default:
|
||||
return <div className="type">{getHumanReadableName(symbol)}</div>
|
||||
}
|
||||
}
|
||||
|
||||
function BinaryType({type, cssClass, infix, prefix, suffix}) {
|
||||
return <div className={`type ${cssClass}`}>
|
||||
{prefix}
|
||||
<Type type={type.params[0](type)}/>
|
||||
<span className="infix">{infix}</span>
|
||||
<Type type={type.params[1](type)}/>
|
||||
{suffix}
|
||||
</div>
|
||||
}
|
||||
|
||||
function UnaryType({type, cssClass, prefix, suffix}) {
|
||||
return <div className={`type ${cssClass}`}>
|
||||
{prefix}
|
||||
<Type type={type.params[0](type)}/>
|
||||
{suffix}
|
||||
</div>
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue