refactor a bit
This commit is contained in:
parent
4fee37944d
commit
230916ceb1
12 changed files with 342 additions and 398 deletions
26
src/App.tsx
26
src/App.tsx
|
|
@ -4,14 +4,8 @@ import { ExprBlock, type ExprBlockState } from './ExprBlock';
|
||||||
import { GlobalContext } from './GlobalContext';
|
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 { removeFocus } from "./eval";
|
import { removeFocus } from "./eval";
|
||||||
|
import { actionShortcuts, getActions } from './actions';
|
||||||
|
|
||||||
const commands: [string, string[], string][] = [
|
|
||||||
["call" , ['c' ], "call" ],
|
|
||||||
["eval" , ['e','Tab','Enter'], "eval" ],
|
|
||||||
["transform", ['t', '.' ], "transform" ],
|
|
||||||
["let" , ['l', '=' ], "let … in …" ],
|
|
||||||
["lambda" , ['a' ], "λx: …" ],
|
|
||||||
];
|
|
||||||
|
|
||||||
const examples: [string, ExprBlockState][] = [
|
const examples: [string, ExprBlockState][] = [
|
||||||
["empty editor" , initialEditorState ],
|
["empty editor" , initialEditorState ],
|
||||||
|
|
@ -114,9 +108,9 @@ export function App() {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [highlighted, setHighlighted] = useState(
|
const [highlighted, setHighlighted] = useState(
|
||||||
commands.map(() => false));
|
actionShortcuts.map(() => false));
|
||||||
|
|
||||||
const doHighlight = Object.fromEntries(commands.map(([id], i) => {
|
const doHighlight = Object.fromEntries(actionShortcuts.map(([id], i) => {
|
||||||
return [id, () => {
|
return [id, () => {
|
||||||
setHighlighted(h => h.with(i, true));
|
setHighlighted(h => h.with(i, true));
|
||||||
setTimeout(() => setHighlighted(h => h.with(i, false)), 100);
|
setTimeout(() => setHighlighted(h => h.with(i, false)), 100);
|
||||||
|
|
@ -138,9 +132,10 @@ export function App() {
|
||||||
<button disabled={appState.history.length===1} onClick={onUndo}>Undo ({appState.history.length-1}) <kbd>Ctrl</kbd>+<kbd>Z</kbd></button>
|
<button disabled={appState.history.length===1} onClick={onUndo}>Undo ({appState.history.length-1}) <kbd>Ctrl</kbd>+<kbd>Z</kbd></button>
|
||||||
<button disabled={appState.future.length===0} onClick={onRedo}>Redo ({appState.future.length}) <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd></button>
|
<button disabled={appState.future.length===0} onClick={onRedo}>Redo ({appState.future.length}) <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd></button>
|
||||||
{
|
{
|
||||||
commands.map(([_, keys, descr], i) =>
|
actionShortcuts.map(([_, keys, descr], i) =>
|
||||||
<span key={i} className={'command' + (highlighted[i] ? (' highlighted') : '')}>
|
<span key={i} className={'command' + (highlighted[i] ? (' highlighted') : '')}>
|
||||||
{keys.map((key, j) => <kbd key={j}>{key}</kbd>)}
|
{keys.map((key, j) => <kbd key={j}>{key}</kbd>)}
|
||||||
|
|
||||||
{descr}
|
{descr}
|
||||||
</span>)
|
</span>)
|
||||||
}
|
}
|
||||||
|
|
@ -173,18 +168,11 @@ export function App() {
|
||||||
// console.log('suggestionPriority of App, always 0');
|
// console.log('suggestionPriority of App, always 0');
|
||||||
return 0;
|
return 0;
|
||||||
}}
|
}}
|
||||||
addParam={(s: ExprBlockState) => {
|
addParam={() => {
|
||||||
pushHistory(state => ({
|
getActions({doHighlight}, pushHistory).c();
|
||||||
kind: "call",
|
|
||||||
fn: removeFocus(state),
|
|
||||||
input: initialEditorState,
|
|
||||||
}));
|
|
||||||
doHighlight.call();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</GlobalContext>
|
</GlobalContext>
|
||||||
|
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,6 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.commandInput {
|
|
||||||
width: 30px;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.keyword {
|
.keyword {
|
||||||
color: blue;
|
color: blue;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
import { useContext } from "react";
|
||||||
|
|
||||||
import { getSymbol, getType, symbolFunction } from "dope2";
|
import { getType } from "dope2";
|
||||||
|
|
||||||
import { CallBlock, type CallBlockState } from "./CallBlock";
|
import { CallBlock, type CallBlockState } from "./CallBlock";
|
||||||
import { EnvContext } from "./EnvContext";
|
import { EnvContext } from "./EnvContext";
|
||||||
|
|
@ -9,11 +9,11 @@ import { InputBlock, type InputBlockState } from "./InputBlock";
|
||||||
import { LambdaBlock, type LambdaBlockState } from "./LambdaBlock";
|
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 { evalEditorBlock, type ResolvedType } from "./eval";
|
||||||
import { evalEditorBlock, removeFocus, type ResolvedType } from "./eval";
|
|
||||||
import { focusNextElement, focusPrevElement } from "./util/dom_trickery";
|
|
||||||
|
|
||||||
import "./ExprBlock.css";
|
import "./ExprBlock.css";
|
||||||
|
import { Input } from "./Input";
|
||||||
|
import { getActions } from "./actions";
|
||||||
|
|
||||||
export type ExprBlockState =
|
export type ExprBlockState =
|
||||||
InputBlockState
|
InputBlockState
|
||||||
|
|
@ -34,160 +34,57 @@ interface ExprBlockProps extends State2Props<ExprBlockState> {
|
||||||
addParam: (e: ExprBlockState) => void;
|
addParam: (e: ExprBlockState) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCommands(type) {
|
|
||||||
const commands = ['e', 't', 'Enter', 'Backspace', 'ArrowLeft', 'ArrowRight', 'Tab', 'l', '=', '.'];
|
|
||||||
if (getSymbol(type) === symbolFunction) {
|
|
||||||
commands.push('c');
|
|
||||||
}
|
|
||||||
return commands;
|
|
||||||
}
|
|
||||||
function getShortCommands(type) {
|
|
||||||
if (getSymbol(type) === symbolFunction) {
|
|
||||||
return 'c|Tab|.';
|
|
||||||
}
|
|
||||||
return 'Tab|.';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ExprBlock({state, setState, suggestionPriority, onCancel, addParam}: ExprBlockProps) {
|
export function ExprBlock({state, setState, suggestionPriority, onCancel, addParam}: ExprBlockProps) {
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
const [needCommand, setNeedCommand] = useState(false);
|
|
||||||
const commandInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
useEffect(() => {
|
|
||||||
if (needCommand) {
|
|
||||||
commandInputRef.current?.focus();
|
|
||||||
}
|
|
||||||
}, [needCommand]);
|
|
||||||
|
|
||||||
const globalContext = useContext(GlobalContext);
|
const globalContext = useContext(GlobalContext);
|
||||||
const onCommand = (e: React.KeyboardEvent) => {
|
|
||||||
const commands = ['e', 't', 'Enter', 'Backspace', 'ArrowLeft', 'ArrowRight', 'Tab', 'l', 'L', '=', '.', 'c', 'a'];
|
const renderBlock = {
|
||||||
if (!commands.includes(e.key)) {
|
input: () => <InputBlock
|
||||||
return;
|
state={state as InputBlockState}
|
||||||
}
|
setState={setState as (callback:(p:InputBlockState)=>ExprBlockState)=>void}
|
||||||
e.preventDefault();
|
suggestionPriority={suggestionPriority}
|
||||||
setNeedCommand(false);
|
onCancel={onCancel}
|
||||||
// u -> pass Up
|
addParam={addParam}
|
||||||
if (e.key === "e" || e.key === "Enter" || e.key === "Tab" && !e.shiftKey) {
|
/>,
|
||||||
// onResolve(state);
|
call: () => <CallBlock
|
||||||
globalContext?.doHighlight.eval();
|
state={state as CallBlockState}
|
||||||
return;
|
setState={setState as (callback:(p:CallBlockState)=>ExprBlockState)=>void}
|
||||||
}
|
suggestionPriority={suggestionPriority}
|
||||||
if (e.key === "Tab" && e.shiftKey) {
|
/>,
|
||||||
setNeedCommand(false);
|
let: () => <LetInBlock
|
||||||
focusPrevElement();
|
state={state as LetInBlockState}
|
||||||
}
|
setState={setState as (callback:(p:LetInBlockState)=>ExprBlockState)=>void}
|
||||||
// c -> Call
|
suggestionPriority={suggestionPriority}
|
||||||
if (e.key === "c") {
|
addParam={addParam}
|
||||||
// we become CallBlock
|
/>,
|
||||||
setState(state => ({
|
lambda: () => <LambdaBlock
|
||||||
kind: "call",
|
state={state as LambdaBlockState}
|
||||||
fn: removeFocus(state),
|
setState={setState as (callback:(p:LambdaBlockState)=>ExprBlockState)=>void}
|
||||||
input: initialEditorState,
|
suggestionPriority={suggestionPriority}
|
||||||
}));
|
addParam={addParam}
|
||||||
globalContext?.doHighlight.call();
|
/>,
|
||||||
// focusNextElement();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// t -> Transform
|
|
||||||
if (e.key === "t" || e.key === ".") {
|
|
||||||
// we become CallBlock
|
|
||||||
setState(state => ({
|
|
||||||
kind: "call",
|
|
||||||
fn: initialEditorState,
|
|
||||||
input: removeFocus(state),
|
|
||||||
}));
|
|
||||||
globalContext?.doHighlight.transform();
|
|
||||||
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 === '=' && !e.shiftKey) {
|
|
||||||
// we become LetInBlock
|
|
||||||
setState(state => ({
|
|
||||||
kind: "let",
|
|
||||||
name: "",
|
|
||||||
focus: true,
|
|
||||||
value: removeFocus(state),
|
|
||||||
inner: removeFocus(initialEditorState),
|
|
||||||
}));
|
|
||||||
globalContext?.doHighlight.let();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (e.key === 'L' || e.key === '=' && e.shiftKey) {
|
|
||||||
setState(state => ({
|
|
||||||
kind: "let",
|
|
||||||
name: "",
|
|
||||||
focus: true,
|
|
||||||
value: removeFocus(initialEditorState),
|
|
||||||
inner: removeFocus(state),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
// a -> lAmbdA
|
|
||||||
if (e.key === "a") {
|
|
||||||
setState(state => ({
|
|
||||||
kind: "lambda",
|
|
||||||
paramName: "",
|
|
||||||
focus: true,
|
|
||||||
expr: removeFocus(state),
|
|
||||||
}));
|
|
||||||
globalContext?.doHighlight.lambda();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderBlock = () => {
|
|
||||||
switch (state.kind) {
|
|
||||||
case "input":
|
|
||||||
return <InputBlock
|
|
||||||
state={state}
|
|
||||||
setState={setState as (callback:(p:InputBlockState)=>ExprBlockState)=>void}
|
|
||||||
suggestionPriority={suggestionPriority}
|
|
||||||
onCancel={onCancel}
|
|
||||||
addParam={addParam}
|
|
||||||
/>;
|
|
||||||
case "call":
|
|
||||||
return <CallBlock
|
|
||||||
state={state}
|
|
||||||
setState={setState as (callback:(p:CallBlockState)=>ExprBlockState)=>void}
|
|
||||||
suggestionPriority={suggestionPriority}
|
|
||||||
/>;
|
|
||||||
case "let":
|
|
||||||
return <LetInBlock
|
|
||||||
state={state}
|
|
||||||
setState={setState as (callback:(p:LetInBlockState)=>ExprBlockState)=>void}
|
|
||||||
suggestionPriority={suggestionPriority}
|
|
||||||
addParam={addParam}
|
|
||||||
/>;
|
|
||||||
case "lambda":
|
|
||||||
return <LambdaBlock
|
|
||||||
state={state}
|
|
||||||
setState={setState as (callback:(p:LambdaBlockState)=>ExprBlockState)=>void}
|
|
||||||
suggestionPriority={suggestionPriority}
|
|
||||||
addParam={addParam}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const [resolved] = evalEditorBlock(state, env);
|
const [resolved] = evalEditorBlock(state, env);
|
||||||
|
const actions = getActions(globalContext, setState);
|
||||||
|
const extraHandlers = Object.fromEntries(Object.entries(actions).map(([shortcut, action]) =>
|
||||||
|
[shortcut, (e) => { e.preventDefault(); action(); }]))
|
||||||
|
|
||||||
return <span className={"editor" + ((resolved.kind!=="value") ? " "+resolved.kind : "")}>
|
return <span className={"editor" + ((resolved.kind!=="value") ? " "+resolved.kind : "")}>
|
||||||
{renderBlock()}
|
{renderBlock[state.kind]()}
|
||||||
<div className="typeSignature">
|
<div className="typeSignature">
|
||||||
:: <Type type={getType(resolved)} />
|
:: <Type type={getType(resolved)} />
|
||||||
</div>
|
</div>
|
||||||
<input
|
<Input
|
||||||
ref={commandInputRef}
|
placeholder="<c>"
|
||||||
spellCheck={false}
|
text=""
|
||||||
className="editable commandInput"
|
suggestion=""
|
||||||
placeholder={`<c>`}
|
focus={false}
|
||||||
onKeyDown={onCommand}
|
onEnter={() => {}}
|
||||||
value={""}
|
onCancel={onCancel}
|
||||||
onChange={() => {}} />
|
onTextChange={() => {}}
|
||||||
|
setFocus={() => {}}
|
||||||
|
extraHandlers={extraHandlers}
|
||||||
|
/>
|
||||||
</span>;
|
</span>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
156
src/Input.tsx
Normal file
156
src/Input.tsx
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
import { useEffect, useRef, type ReactNode, type KeyboardEvent, useState } from "react";
|
||||||
|
|
||||||
|
interface InputProps {
|
||||||
|
placeholder: string;
|
||||||
|
text: string;
|
||||||
|
suggestion: string;
|
||||||
|
onTextChange: (text: string) => void;
|
||||||
|
focus: boolean;
|
||||||
|
setFocus: (focus: boolean) => void;
|
||||||
|
|
||||||
|
onEnter: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
extraHandlers: {[key:string]: (e: KeyboardEvent) => void}
|
||||||
|
|
||||||
|
children?: ReactNode | ReactNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const autoInputWidth = (ref: React.RefObject<HTMLInputElement| null>, text, emptyWidth=150) => {
|
||||||
|
if (ref.current) {
|
||||||
|
ref.current.style.width = `${text.length === 0 ? emptyWidth : (text.length*8.0)}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCaretPosition(ref: React.RefObject<HTMLInputElement| null>): number {
|
||||||
|
return ref.current?.selectionStart || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move caret all the way to the right in the currently focused element
|
||||||
|
function setRightMostCaretPosition(elem) {
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNode(elem);
|
||||||
|
if (elem.lastChild) { // if no text is entered, there is no lastChild
|
||||||
|
range.setStart(elem.lastChild, elem.textContent.length);
|
||||||
|
range.setEnd(elem.lastChild, elem.textContent.length);
|
||||||
|
const selection = window.getSelection();
|
||||||
|
selection?.removeAllRanges();
|
||||||
|
selection?.addRange(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusNextElement() {
|
||||||
|
const editable = Array.from<any>(document.querySelectorAll('input.editable'));
|
||||||
|
const index = editable.indexOf(document.activeElement);
|
||||||
|
const nextElem = editable[index+1];
|
||||||
|
if (nextElem) {
|
||||||
|
nextElem.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusPrevElement() {
|
||||||
|
const editable = Array.from<any>(document.querySelectorAll('input.editable'));
|
||||||
|
const index = editable.indexOf(document.activeElement);
|
||||||
|
const prevElem = editable[index-1]
|
||||||
|
if (prevElem) {
|
||||||
|
prevElem.focus();
|
||||||
|
setRightMostCaretPosition(prevElem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Input({placeholder, text, suggestion, onTextChange, focus, setFocus, onEnter, onCancel, extraHandlers, children}: InputProps) {
|
||||||
|
const ref = useRef<HTMLInputElement>(null);
|
||||||
|
const [hideChildren, setHideChildren] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (focus) {
|
||||||
|
ref.current?.focus();
|
||||||
|
setHideChildren(false);
|
||||||
|
}
|
||||||
|
}, [focus]);
|
||||||
|
|
||||||
|
useEffect(() => autoInputWidth(ref, (text+(focus?suggestion:'')) || placeholder), [ref, text, suggestion, focus]);
|
||||||
|
|
||||||
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
|
setHideChildren(false);
|
||||||
|
|
||||||
|
const keys = {
|
||||||
|
// auto-complete
|
||||||
|
Tab: () => {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
focusPrevElement();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// not shift key
|
||||||
|
if (suggestion.length > 0) {
|
||||||
|
// complete with greyed out text
|
||||||
|
const newText = text + suggestion;
|
||||||
|
onTextChange(newText);
|
||||||
|
setRightMostCaretPosition(ref.current);
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
onEnter();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Enter: () => {
|
||||||
|
onEnter();
|
||||||
|
e.preventDefault();
|
||||||
|
},
|
||||||
|
|
||||||
|
// cancel
|
||||||
|
Backspace: () => {
|
||||||
|
if (text.length === 0) {
|
||||||
|
onCancel();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// navigate with arrows
|
||||||
|
ArrowLeft: () => {
|
||||||
|
if (getCaretPosition(ref) <= 0) {
|
||||||
|
focusPrevElement();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ArrowRight: () => {
|
||||||
|
if (getCaretPosition(ref) === text.length) {
|
||||||
|
focusNextElement();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Escape: () => {
|
||||||
|
if (!hideChildren) {
|
||||||
|
setHideChildren(true);
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
...extraHandlers,
|
||||||
|
};
|
||||||
|
const handler = keys[e.key];
|
||||||
|
if (handler) {
|
||||||
|
handler(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return <span className="inputBlock">
|
||||||
|
{focus && !hideChildren && children}
|
||||||
|
<span className="editable suggest">{text}{focus && suggestion}</span>
|
||||||
|
<input ref={ref}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className="editable"
|
||||||
|
value={text}
|
||||||
|
onInput={(e) =>
|
||||||
|
// @ts-ignore
|
||||||
|
onTextChange(e.target.value)}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
onFocus={() => setFocus(true)}
|
||||||
|
onBlur={() => setFocus(false)}
|
||||||
|
spellCheck={false}
|
||||||
|
/>
|
||||||
|
</span>;
|
||||||
|
}
|
||||||
|
|
@ -7,11 +7,9 @@ import type { Dynamic, ResolvedType } from "./eval";
|
||||||
import "./InputBlock.css";
|
import "./InputBlock.css";
|
||||||
import { Type } from "./Type";
|
import { Type } from "./Type";
|
||||||
import type { ExprBlockState, State2Props } from "./ExprBlock";
|
import type { ExprBlockState, State2Props } from "./ExprBlock";
|
||||||
import { autoInputWidth, focusNextElement, focusPrevElement, setRightMostCaretPosition } from "./util/dom_trickery";
|
import { attemptParseLiteral } from "./eval";
|
||||||
import { attemptParseLiteral, removeFocus } from "./eval";
|
import { Input } from "./Input";
|
||||||
import { GlobalContext } from "./GlobalContext";
|
|
||||||
import { initialEditorState } from "./configurations";
|
import { initialEditorState } from "./configurations";
|
||||||
import type { CallBlockState } from "./CallBlock";
|
|
||||||
|
|
||||||
interface Literal {
|
interface Literal {
|
||||||
kind: "literal";
|
kind: "literal";
|
||||||
|
|
@ -64,17 +62,14 @@ const computeSuggestions = (text, env, suggestionPriority: (s: ResolvedType) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InputBlock({ state, setState, suggestionPriority, onCancel, addParam }: 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);
|
||||||
const [i, setI] = useState(0); // selected suggestion idx
|
const [i, setI] = useState(0); // selected suggestion idx
|
||||||
const [haveFocus, setHaveFocus] = useState(false); // whether to render suggestions or not
|
|
||||||
|
|
||||||
const singleSuggestion = trie.growPrefix(env.names)(text);
|
const singleSuggestion = trie.growPrefix(env.names)(text);
|
||||||
const suggestions = useMemo(() => computeSuggestions(text, env, suggestionPriority), [text, suggestionPriority, env]);
|
const suggestions = useMemo(() => computeSuggestions(text, env, suggestionPriority), [text, suggestionPriority, env]);
|
||||||
|
|
||||||
useEffect(() => autoInputWidth(inputRef, text+singleSuggestion), [inputRef, text, singleSuggestion]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (focus) {
|
if (focus) {
|
||||||
|
|
@ -88,10 +83,6 @@ export function InputBlock({ state, setState, suggestionPriority, onCancel, addP
|
||||||
}
|
}
|
||||||
}, [suggestions.length]);
|
}, [suggestions.length]);
|
||||||
|
|
||||||
const getCaretPosition = () => {
|
|
||||||
return inputRef.current?.selectionStart || -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onTextChange = newText => {
|
const onTextChange = newText => {
|
||||||
setState(state => ({...state,
|
setState(state => ({...state,
|
||||||
text: newText,
|
text: newText,
|
||||||
|
|
@ -101,73 +92,8 @@ export function InputBlock({ state, setState, suggestionPriority, onCancel, addP
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// fired before onInput
|
const onSelectSuggestion = () => {
|
||||||
const onKeyDown = (e: React.KeyboardEvent) => {
|
const [_priority, kind, name, dynamic] = suggestions[i];
|
||||||
const fns = {
|
|
||||||
Tab: () => {
|
|
||||||
if (e.shiftKey) {
|
|
||||||
focusPrevElement();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// not shift key
|
|
||||||
if (singleSuggestion.length > 0) {
|
|
||||||
const newText = text + singleSuggestion;
|
|
||||||
onTextChange(newText);
|
|
||||||
setRightMostCaretPosition(inputRef.current);
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
onSelectSuggestion(suggestions[i]);
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ArrowDown: () => {
|
|
||||||
setI((i + 1) % suggestions.length);
|
|
||||||
e.preventDefault();
|
|
||||||
},
|
|
||||||
ArrowUp: () => {
|
|
||||||
setI((suggestions.length + i - 1) % suggestions.length);
|
|
||||||
e.preventDefault();
|
|
||||||
},
|
|
||||||
ArrowLeft: () => {
|
|
||||||
if (getCaretPosition() <= 0) {
|
|
||||||
focusPrevElement();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ArrowRight: () => {
|
|
||||||
if (getCaretPosition() === text.length) {
|
|
||||||
focusNextElement();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Enter: () => {
|
|
||||||
onSelectSuggestion(suggestions[i]);
|
|
||||||
e.preventDefault();
|
|
||||||
},
|
|
||||||
Backspace: () => {
|
|
||||||
if (text.length === 0) {
|
|
||||||
onCancel();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
" ": () => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (text.length > 0) {
|
|
||||||
addParam(initialEditorState);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
fns[e.key]?.();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onInput = e => {
|
|
||||||
onTextChange(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSelectSuggestion = ([priority, kind, name, dynamic]: PrioritizedSuggestionType) => {
|
|
||||||
if (kind === "literal") {
|
if (kind === "literal") {
|
||||||
setState(state => ({
|
setState(state => ({
|
||||||
...state,
|
...state,
|
||||||
|
|
@ -184,29 +110,41 @@ export function InputBlock({ state, setState, suggestionPriority, onCancel, addP
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return <span className="inputBlock">
|
const extraHandlers = {
|
||||||
{/* Dropdown suggestions */}
|
ArrowDown: (e) => {
|
||||||
{haveFocus &&
|
setI((i + 1) % suggestions.length);
|
||||||
<span className="suggestionsPlaceholder">
|
e.preventDefault();
|
||||||
|
},
|
||||||
|
ArrowUp: (e) => {
|
||||||
|
setI((suggestions.length + i - 1) % suggestions.length);
|
||||||
|
e.preventDefault();
|
||||||
|
},
|
||||||
|
" ": (e) => {
|
||||||
|
if (text.length > 0) {
|
||||||
|
addParam(initialEditorState);
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Input
|
||||||
|
placeholder="<name or literal>"
|
||||||
|
focus={focus}
|
||||||
|
setFocus={focus => setState(state => ({...state, focus}))}
|
||||||
|
onCancel={onCancel}
|
||||||
|
onEnter={onSelectSuggestion}
|
||||||
|
onTextChange={onTextChange}
|
||||||
|
text={text}
|
||||||
|
suggestion={singleSuggestion}
|
||||||
|
extraHandlers={extraHandlers}
|
||||||
|
>
|
||||||
|
{focus && <span className="suggestionsPlaceholder">
|
||||||
<Suggestions
|
<Suggestions
|
||||||
suggestions={suggestions}
|
suggestions={suggestions}
|
||||||
onSelect={onSelectSuggestion}
|
onSelect={onSelectSuggestion}
|
||||||
i={i} setI={setI} />
|
i={i} setI={setI} />
|
||||||
</span>
|
</span>}
|
||||||
}
|
</Input>
|
||||||
{/* Single 'grey' suggestion */}
|
|
||||||
<span className="editable suggest">{text}{singleSuggestion}</span>
|
|
||||||
{/* Input box */}
|
|
||||||
<input ref={inputRef}
|
|
||||||
placeholder="<name or literal>"
|
|
||||||
className="editable"
|
|
||||||
value={text}
|
|
||||||
onInput={onInput}
|
|
||||||
onKeyDown={onKeyDown}
|
|
||||||
onFocus={() => setHaveFocus(true)}
|
|
||||||
onBlur={() => setHaveFocus(false)}
|
|
||||||
spellCheck={false}/>
|
|
||||||
</span>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Suggestions({ suggestions, onSelect, i, setI }) {
|
function Suggestions({ suggestions, onSelect, i, setI }) {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import { useContext, useEffect, useRef } from "react";
|
import { useContext } from "react";
|
||||||
|
|
||||||
import { eqType, getSymbol, reduceUnification, trie } from "dope2";
|
import { eqType, getSymbol, reduceUnification } from "dope2";
|
||||||
|
|
||||||
import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock";
|
import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock";
|
||||||
import { EnvContext } from "./EnvContext";
|
import { EnvContext } from "./EnvContext";
|
||||||
import { evalEditorBlock, makeInnerEnv, makeTypeVar } from "./eval";
|
import { evalEditorBlock, makeInnerEnv, makeTypeVar } from "./eval";
|
||||||
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";
|
import { Input } from "./Input";
|
||||||
|
|
||||||
export interface LambdaBlockState {
|
export interface LambdaBlockState {
|
||||||
kind: "lambda";
|
kind: "lambda";
|
||||||
|
|
@ -28,7 +27,6 @@ interface LambdaBlockProps<
|
||||||
|
|
||||||
export function LambdaBlock({state, setState, suggestionPriority, addParam}: LambdaBlockProps) {
|
export function LambdaBlock({state, setState, suggestionPriority, addParam}: LambdaBlockProps) {
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
const nameRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
const setParamName = paramName => setState(state => ({
|
const setParamName = paramName => setState(state => ({
|
||||||
...state,
|
...state,
|
||||||
|
|
@ -39,21 +37,6 @@ export function LambdaBlock({state, setState, suggestionPriority, addParam}: Lam
|
||||||
expr: callback(state.expr),
|
expr: callback(state.expr),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const onChangeName = (e) => {
|
|
||||||
if (state.paramName === "" && e.key === 'Backspace') {
|
|
||||||
setState(state => state.expr);
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (state.focus) {
|
|
||||||
nameRef.current?.focus();
|
|
||||||
}
|
|
||||||
}, [state.focus]);
|
|
||||||
|
|
||||||
useEffect(() => autoInputWidth(nameRef, state.paramName, 60), [nameRef, state.paramName]);
|
|
||||||
|
|
||||||
const [paramType, staticInnerEnv] = makeTypeVar(env, state.paramName);
|
const [paramType, staticInnerEnv] = makeTypeVar(env, state.paramName);
|
||||||
|
|
||||||
const [exprResolved] = evalEditorBlock(state.expr, staticInnerEnv);
|
const [exprResolved] = evalEditorBlock(state.expr, staticInnerEnv);
|
||||||
|
|
@ -66,23 +49,23 @@ export function LambdaBlock({state, setState, suggestionPriority, addParam}: Lam
|
||||||
kind: "unknown",
|
kind: "unknown",
|
||||||
t: inferredParamType,
|
t: inferredParamType,
|
||||||
unification: new Map(), // <- is this correct?
|
unification: new Map(), // <- is this correct?
|
||||||
})
|
});
|
||||||
|
|
||||||
// const {exprResolved, env: newEnv} = computeLambdaBlockType(state.paramName, state.expr, env);
|
|
||||||
|
|
||||||
return <span className="lambdaBlock">
|
return <span className="lambdaBlock">
|
||||||
<span className="keyword">λ</span>
|
<span className="keyword">λ</span>
|
||||||
|
|
||||||
<span className="lambdaInputParam">
|
<span className="lambdaInputParam">
|
||||||
<input
|
<Input
|
||||||
ref={nameRef}
|
placeholder="<name>"
|
||||||
className='editable'
|
text={state.paramName}
|
||||||
value={state.paramName}
|
suggestion=""
|
||||||
placeholder="<name>"
|
focus={state.focus}
|
||||||
onKeyDown={onChangeName}
|
onEnter={() => {}}
|
||||||
onChange={e => setParamName(e.target.value)}
|
onCancel={() => {}}
|
||||||
spellCheck={false}
|
onTextChange={txt => setParamName(txt)}
|
||||||
/>
|
setFocus={focus => setState(state => ({...state, focus}))}
|
||||||
|
extraHandlers={{}}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<div className="typeSignature">
|
<div className="typeSignature">
|
||||||
:: <Type type={inferredParamType} />
|
:: <Type type={inferredParamType} />
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import { useContext, useEffect, useRef } from "react";
|
import { useContext } from "react";
|
||||||
|
|
||||||
import { ExprBlock, type ExprBlockState } from "./ExprBlock";
|
import { ExprBlock, type ExprBlockState } from "./ExprBlock";
|
||||||
import { EnvContext } from "./EnvContext";
|
import { EnvContext } from "./EnvContext";
|
||||||
import { evalEditorBlock, makeInnerEnv, scoreResolved, type ResolvedType } from "./eval";
|
import { evalEditorBlock, makeInnerEnv, scoreResolved, type ResolvedType } from "./eval";
|
||||||
import { type State2Props } from "./ExprBlock";
|
import { type State2Props } from "./ExprBlock";
|
||||||
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";
|
import { Input } from "./Input";
|
||||||
|
|
||||||
export interface LetInBlockState {
|
export interface LetInBlockState {
|
||||||
kind: "let";
|
kind: "let";
|
||||||
|
|
@ -39,9 +38,6 @@ function DeclColumns({state: {name, value, inner, focus}, setState, suggestionPr
|
||||||
|
|
||||||
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
|
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
|
||||||
const setValue = callback => setState(state => ({...state, value: callback(state.value)}));
|
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 valueSuggestionPriority = (suggestion: ResolvedType) => {
|
||||||
const innerEnv = makeInnerEnv(env, name, suggestion);
|
const innerEnv = makeInnerEnv(env, name, suggestion);
|
||||||
|
|
@ -49,28 +45,22 @@ function DeclColumns({state: {name, value, inner, focus}, setState, suggestionPr
|
||||||
return scoreResolved(resolved, suggestionPriority);
|
return scoreResolved(resolved, suggestionPriority);
|
||||||
};
|
};
|
||||||
|
|
||||||
const nameRef = useRef<HTMLInputElement>(null);
|
|
||||||
useEffect(() => {
|
|
||||||
if (focus) {
|
|
||||||
nameRef.current?.focus();
|
|
||||||
}
|
|
||||||
}, [focus]);
|
|
||||||
|
|
||||||
useEffect(() => autoInputWidth(nameRef, name, 60), [nameRef, name]);
|
|
||||||
|
|
||||||
const [valueResolved] = evalEditorBlock(value, env);
|
const [valueResolved] = evalEditorBlock(value, env);
|
||||||
const innerEnv = makeInnerEnv(env, name, valueResolved);
|
const innerEnv = makeInnerEnv(env, name, valueResolved);
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<span className="keyword column">let </span>
|
<span className="keyword column">let </span>
|
||||||
<span className="column rightAlign">
|
<span className="column rightAlign">
|
||||||
<input
|
<Input
|
||||||
ref={nameRef}
|
|
||||||
className='editable'
|
|
||||||
value={name}
|
|
||||||
placeholder="<name>"
|
placeholder="<name>"
|
||||||
onChange={onChangeName}
|
text={name}
|
||||||
spellCheck={false}
|
suggestion=""
|
||||||
|
focus={focus}
|
||||||
|
onEnter={() => {}}
|
||||||
|
onCancel={() => {}}
|
||||||
|
onTextChange={name => setState(state => ({...state, name}))}
|
||||||
|
setFocus={focus => setState(state => ({...state, focus}))}
|
||||||
|
extraHandlers={{}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span className="keyword column"> = </span>
|
<span className="keyword column"> = </span>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-variation-settings: "wdth" 100;
|
font-variation-settings: "wdth" 100;
|
||||||
|
|
||||||
|
color: darkgrey;
|
||||||
}
|
}
|
||||||
|
|
||||||
.functionType {
|
.functionType {
|
||||||
|
|
|
||||||
60
src/actions.ts
Normal file
60
src/actions.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { initialEditorState } from "./configurations";
|
||||||
|
import { removeFocus } from "./eval";
|
||||||
|
|
||||||
|
export const actionShortcuts: [string, string[], string][] = [
|
||||||
|
["call" , ['c'], "expr ⌴" ],
|
||||||
|
["transform", ['.'], "⌴ expr" ],
|
||||||
|
["assign" , ['a'], "let (⌴ = expr) in ⌴"],
|
||||||
|
["declare" , ['d'], "let (⌴ = ⌴) in expr"],
|
||||||
|
["lambda" , ['l'], "λ⌴. expr" ],
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getActions(globalContext, setState) {
|
||||||
|
return {
|
||||||
|
c: () => {
|
||||||
|
setState(state => ({
|
||||||
|
kind: "call",
|
||||||
|
fn: removeFocus(state),
|
||||||
|
input: initialEditorState,
|
||||||
|
}));
|
||||||
|
globalContext?.doHighlight.call();
|
||||||
|
},
|
||||||
|
'.': () => {
|
||||||
|
setState(state => ({
|
||||||
|
kind: "call",
|
||||||
|
fn: initialEditorState,
|
||||||
|
input: removeFocus(state),
|
||||||
|
}));
|
||||||
|
globalContext?.doHighlight.transform();
|
||||||
|
},
|
||||||
|
a: () => {
|
||||||
|
setState(state => ({
|
||||||
|
kind: "let",
|
||||||
|
name: "",
|
||||||
|
focus: true,
|
||||||
|
value: removeFocus(state),
|
||||||
|
inner: removeFocus(initialEditorState),
|
||||||
|
}));
|
||||||
|
globalContext?.doHighlight.assign();
|
||||||
|
},
|
||||||
|
d: () => {
|
||||||
|
setState(state => ({
|
||||||
|
kind: "let",
|
||||||
|
name: "",
|
||||||
|
focus: true,
|
||||||
|
value: removeFocus(initialEditorState),
|
||||||
|
inner: removeFocus(state),
|
||||||
|
}));
|
||||||
|
globalContext?.doHighlight.declare();
|
||||||
|
},
|
||||||
|
l: () => {
|
||||||
|
setState(state => ({
|
||||||
|
kind: "lambda",
|
||||||
|
paramName: "",
|
||||||
|
focus: true,
|
||||||
|
expr: removeFocus(state),
|
||||||
|
}));
|
||||||
|
globalContext?.doHighlight.lambda();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
24
src/eval.ts
24
src/eval.ts
|
|
@ -4,6 +4,7 @@ import type { ExprBlockState } from "./ExprBlock";
|
||||||
import type { InputValueType } from "./InputBlock";
|
import type { InputValueType } from "./InputBlock";
|
||||||
|
|
||||||
const IS_DEV = (import.meta.env.MODE === "development");
|
const IS_DEV = (import.meta.env.MODE === "development");
|
||||||
|
const VERBOSE = false;
|
||||||
|
|
||||||
export interface Environment {
|
export interface Environment {
|
||||||
names: any;
|
names: any;
|
||||||
|
|
@ -43,12 +44,6 @@ export interface Unknown {
|
||||||
// the value of every block is either known (Dynamic), an error, or unknown
|
// the value of every block is either known (Dynamic), an error, or unknown
|
||||||
export type ResolvedType = Dynamic | DeepError | Unknown;
|
export type ResolvedType = Dynamic | DeepError | Unknown;
|
||||||
|
|
||||||
// export const evalEditorBlock = (s: ExprBlockState, env: Environment): [ResolvedType,Environment] => {
|
|
||||||
// const [resolved] = proxyEditorBlock(s, env);
|
|
||||||
// const [t, newEnv] = recomputeTypeVarsForEnv(resolved.t, env);
|
|
||||||
// return [{...resolved, t }, newEnv];
|
|
||||||
// };
|
|
||||||
|
|
||||||
class NotFoundError extends Error {}
|
class NotFoundError extends Error {}
|
||||||
|
|
||||||
export const evalEditorBlock = (s: ExprBlockState, env: Environment): [ResolvedType,Environment] => {
|
export const evalEditorBlock = (s: ExprBlockState, env: Environment): [ResolvedType,Environment] => {
|
||||||
|
|
@ -74,10 +69,10 @@ export function evalInputBlock(text: string, value: InputValueType, env: Environ
|
||||||
const found = trie.get(env.names)(text);
|
const found = trie.get(env.names)(text);
|
||||||
if (found) {
|
if (found) {
|
||||||
if (found.kind === "unknown") {
|
if (found.kind === "unknown") {
|
||||||
console.log('returning', text, 'as-is');
|
// console.log('returning', text, 'as-is');
|
||||||
return [found, env]; // don't rewrite lambda parameters
|
return [found, env]; // don't rewrite lambda parameters
|
||||||
}
|
}
|
||||||
console.log('rewriting', text);
|
// console.log('rewriting', text);
|
||||||
return recomputeTypeVarsForEnv(text, found, env);
|
return recomputeTypeVarsForEnv(text, found, env);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -171,7 +166,7 @@ function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, e
|
||||||
const [abstractOutputType, env2] = makeTypeVar(env, "<out>");
|
const [abstractOutputType, env2] = makeTypeVar(env, "<out>");
|
||||||
const matchFnType = fnType(_ => inputResolved.t)(_ => abstractOutputType);
|
const matchFnType = fnType(_ => inputResolved.t)(_ => abstractOutputType);
|
||||||
|
|
||||||
if (IS_DEV) {
|
if (VERBOSE) {
|
||||||
console.log('========= evalCallBlock3 =========')
|
console.log('========= evalCallBlock3 =========')
|
||||||
console.log('env :', env);
|
console.log('env :', env);
|
||||||
console.log('fnKind :', fnResolved.kind);
|
console.log('fnKind :', fnResolved.kind);
|
||||||
|
|
@ -197,7 +192,7 @@ function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, e
|
||||||
// we don't want to 'bubble up' our outType substitution, because it's just a temporary variable
|
// we don't want to 'bubble up' our outType substitution, because it's just a temporary variable
|
||||||
const unificationWithoutOutType = new Map([...unification].filter(([symbol]) => symbol !== abstractOutputType.symbol));
|
const unificationWithoutOutType = new Map([...unification].filter(([symbol]) => symbol !== abstractOutputType.symbol));
|
||||||
|
|
||||||
if (IS_DEV) {
|
if (VERBOSE) {
|
||||||
console.log('unification :', prettyU(unification));
|
console.log('unification :', prettyU(unification));
|
||||||
console.log('unificationInvR :', prettyRU(unificationR));
|
console.log('unificationInvR :', prettyRU(unificationR));
|
||||||
console.log('unifiedFnType :', prettyT(unifiedFnType));
|
console.log('unifiedFnType :', prettyT(unifiedFnType));
|
||||||
|
|
@ -212,7 +207,7 @@ function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, e
|
||||||
|
|
||||||
// const grandUnification = unificationWithoutOutType;
|
// const grandUnification = unificationWithoutOutType;
|
||||||
|
|
||||||
if (IS_DEV) {
|
if (VERBOSE) {
|
||||||
console.log('grandUnification :', prettyU(grandUnification));
|
console.log('grandUnification :', prettyU(grandUnification));
|
||||||
console.log('==================================')
|
console.log('==================================')
|
||||||
}
|
}
|
||||||
|
|
@ -278,8 +273,6 @@ function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, e
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
// throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,7 +289,7 @@ const prettyRU = (rUni: Map<string, Type>) => {
|
||||||
export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
||||||
const [paramType, staticInnerEnv] = makeTypeVar(env, paramName);
|
const [paramType, staticInnerEnv] = makeTypeVar(env, paramName);
|
||||||
|
|
||||||
if (IS_DEV) {
|
if (VERBOSE) {
|
||||||
console.log('====== begin evalLambdaBlock ======')
|
console.log('====== begin evalLambdaBlock ======')
|
||||||
console.log('paramName :', paramName);
|
console.log('paramName :', paramName);
|
||||||
console.log('paramType :', prettyT(paramType));
|
console.log('paramType :', prettyT(paramType));
|
||||||
|
|
@ -345,7 +338,7 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: En
|
||||||
|
|
||||||
// const [lambdaResolvedNormalized, resultEnv] = recomputeTypeVarsForEnv(paramName, lambdaResolved, env);
|
// const [lambdaResolvedNormalized, resultEnv] = recomputeTypeVarsForEnv(paramName, lambdaResolved, env);
|
||||||
|
|
||||||
if (IS_DEV) {
|
if (VERBOSE) {
|
||||||
console.log('======= end evalLambdaBlock =======')
|
console.log('======= end evalLambdaBlock =======')
|
||||||
console.log('paramType :', prettyT(paramType));
|
console.log('paramType :', prettyT(paramType));
|
||||||
console.log('exprType :', prettyT(exprResolved.t));
|
console.log('exprType :', prettyT(exprResolved.t));
|
||||||
|
|
@ -353,7 +346,6 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: En
|
||||||
console.log('exprUnificationR :', prettyRU(reduced));
|
console.log('exprUnificationR :', prettyRU(reduced));
|
||||||
console.log('lambdaType :', prettyT(lambdaT));
|
console.log('lambdaType :', prettyT(lambdaT));
|
||||||
console.log('lambdaTypeSubsituted:', prettyT(lambdaTSubstituted));
|
console.log('lambdaTypeSubsituted:', prettyT(lambdaTSubstituted));
|
||||||
// console.log('normalizedT :', prettyT(lambdaResolvedNormalized.t));
|
|
||||||
console.log('===================================')
|
console.log('===================================')
|
||||||
}
|
}
|
||||||
return [lambdaResolved, env];
|
return [lambdaResolved, env];
|
||||||
|
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
import type { Ref } from "react";
|
|
||||||
|
|
||||||
// If there is a caret anywhere in the document (user entering text), returns the position of the caret in the focused element
|
|
||||||
export function getCaretPosition(): number | undefined {
|
|
||||||
const selection = window.getSelection();
|
|
||||||
if (selection) {
|
|
||||||
const range = selection.getRangeAt(0);
|
|
||||||
const clonedRange = range.cloneRange();
|
|
||||||
return clonedRange.startOffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// export function setCursorPosition(elem, pos) {
|
|
||||||
// const range = document.createRange();
|
|
||||||
// range.selectNode(elem);
|
|
||||||
// range.setStart(elem, pos);
|
|
||||||
// range.setEnd(elem, pos);
|
|
||||||
// const selection = window.getSelection();
|
|
||||||
// selection?.removeAllRanges();
|
|
||||||
// selection?.addRange(range);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Move caret all the way to the right in the currently focused element
|
|
||||||
export function setRightMostCaretPosition(elem) {
|
|
||||||
const range = document.createRange();
|
|
||||||
range.selectNode(elem);
|
|
||||||
if (elem.lastChild) { // if no text is entered, there is no lastChild
|
|
||||||
range.setStart(elem.lastChild, elem.textContent.length);
|
|
||||||
range.setEnd(elem.lastChild, elem.textContent.length);
|
|
||||||
const selection = window.getSelection();
|
|
||||||
selection?.removeAllRanges();
|
|
||||||
selection?.addRange(range);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function focusNextElement() {
|
|
||||||
const editable = Array.from<any>(document.querySelectorAll('input'));
|
|
||||||
const index = editable.indexOf(document.activeElement);
|
|
||||||
editable[index+1]?.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function focusPrevElement() {
|
|
||||||
const editable = Array.from<any>(document.querySelectorAll('input'));
|
|
||||||
const index = editable.indexOf(document.activeElement);
|
|
||||||
const prevElem = editable[index-1]
|
|
||||||
if (prevElem) {
|
|
||||||
prevElem.focus();
|
|
||||||
setRightMostCaretPosition(prevElem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const autoInputWidth = (inputRef: React.RefObject<HTMLInputElement| null>, text, emptyWidth=150) => {
|
|
||||||
if (inputRef.current) {
|
|
||||||
inputRef.current.style.width = `${text.length === 0 ? emptyWidth : (text.length*8.0)}px`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue