From fa70d2f3f46085e2fdb5904ea5a36b917bf5408c Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Tue, 13 May 2025 00:15:47 +0200 Subject: [PATCH] further improved keyboard handling --- src/CallBlock.tsx | 30 +++++++++++++++++++++++++----- src/Editor.tsx | 6 +++++- src/InputBlock.css | 2 +- src/InputBlock.tsx | 11 ++++++----- src/Type.css | 2 +- 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/CallBlock.tsx b/src/CallBlock.tsx index f93dced..b99acbc 100644 --- a/src/CallBlock.tsx +++ b/src/CallBlock.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; -import { apply, UnifyError, assignFn, getType } from "dope2"; +import { apply, UnifyError, assignFn, getType, getSymbol, symbolFunction } from "dope2"; import { Editor, type EditorState } from "./Editor"; import { Value } from "./Value"; @@ -97,7 +97,8 @@ export function CallBlock({ state, setState, onResolve }: CallBlockProps) { + onFnResolve={onFnResolve} + input={state.input} />
{/* Sequence of input parameters */} @@ -116,7 +117,7 @@ export function CallBlock({ state, setState, onResolve }: CallBlockProps) { ; } -function FunctionHeader({ fn, setFn, onFnResolve }) { +function FunctionHeader({ fn, setFn, input, onFnResolve }) { if (fn.kind === "call") { // if the function we're calling is itself the result of a function call, // then we are anonymous, and so we don't draw a function name @@ -129,9 +130,28 @@ function FunctionHeader({ fn, setFn, onFnResolve }) { return setFn({...fn, fn: fnFn})} - onFnResolve={onFnFnResolve} />; + onFnResolve={onFnFnResolve} + input={fn.input} />; } else { + const filterCompatibleFns = ([_name, dynamic]: [string, Dynamic]) => { + if (input.resolved) { + try { + const type = getType(dynamic); + if (getSymbol(type) !== symbolFunction) { + return false; + } + assignFn(type, getType(input.resolved)); + } catch (e) { + if (!(e instanceof UnifyError)) { + throw e; + } + return false; + } + } + return true; + } + // end of recursion - draw function name return
𝑓𝑛  @@ -141,7 +161,7 @@ function FunctionHeader({ fn, setFn, onFnResolve }) { focus={false} onResolve={onFnResolve} onCancel={() => {/*todo*/}} - filter={() => true} /> + filter={filterCompatibleFns} />
; } } diff --git a/src/Editor.tsx b/src/Editor.tsx index a2eb6f8..8c3fbf4 100644 --- a/src/Editor.tsx +++ b/src/Editor.tsx @@ -75,10 +75,14 @@ export function Editor({state, setState, onResolve, onCancel, filter, focus}: Ed e.preventDefault(); setNeedCommand(false); // u -> pass Up - if (e.key === "u" || e.key === "Enter" || e.key === "Tab") { + if (e.key === "u" || e.key === "Enter" || e.key === "Tab" && !e.shiftKey) { onResolve(state); return; } + if (e.key === "Tab" && e.shiftKey) { + setNeedCommand(false); + focusPrevElement(); + } // c -> Call if (e.key === "c") { // we become CallBlock diff --git a/src/InputBlock.css b/src/InputBlock.css index 2492cbb..e9dd622 100644 --- a/src/InputBlock.css +++ b/src/InputBlock.css @@ -26,7 +26,7 @@ margin-left: 4px; border: solid 1px dodgerblue; cursor: pointer; - max-height: calc(100vh - 44px); + max-height: calc(100vh - 64px); overflow: auto; z-index: 10; background-color: white; diff --git a/src/InputBlock.tsx b/src/InputBlock.tsx index fea4c4c..c070d3e 100644 --- a/src/InputBlock.tsx +++ b/src/InputBlock.tsx @@ -2,7 +2,7 @@ import { Double, getType, Int, newDynamic, trie } from "dope2"; import { focusNextElement, focusPrevElement, setRightMostCaretPosition } from "./util/dom_trickery"; import { parseDouble, parseInt } from "./util/parse"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { Type } from "./Type"; import "./InputBlock.css"; @@ -29,12 +29,13 @@ const computeSuggestions = (text, env, filter) => { const ls = [ ... (asDouble ? [[asDouble.toString(), newDynamic(asDouble)(Double)]] : []), ... (asInt ? [[asInt.toString(), newDynamic(BigInt(asInt))(Int)]] : []), - ... trie.suggest(env.name2dyn)(text)(10), + ... trie.suggest(env.name2dyn)(text)(Infinity), ] return [ ...ls.filter(filter), // ones that match filter come first ...ls.filter(s => !filter(s)), - ]; + ] + // .slice(0,30); } export function InputBlock({ state, setState, filter, onResolve, onCancel }: InputBlockProps) { @@ -48,7 +49,7 @@ export function InputBlock({ state, setState, filter, onResolve, onCancel }: Inp } const singleSuggestion = trie.growPrefix(env.name2dyn)(text); - const suggestions = computeSuggestions(text, env, filter); + const suggestions = useMemo(() => computeSuggestions(text, env, filter), [text]); useEffect(() => { setI(0); // reset @@ -186,7 +187,7 @@ function Suggestions({ suggestions, onSelect, i, setI }) { {suggestions.map(([name, dynamic], j) =>
{name} :: diff --git a/src/Type.css b/src/Type.css index c103aae..d97e4b0 100644 --- a/src/Type.css +++ b/src/Type.css @@ -58,7 +58,7 @@ .iteratorType { border-style: dashed; - animation: flickerAnimation 500ms steps(1) normal infinite; + /* animation: flickerAnimation 500ms steps(1) normal infinite; */ } /* Animations */