change the way text suggestions are rendered + option to disable syntactic sugar

This commit is contained in:
Joeri Exelmans 2025-05-15 22:22:45 +02:00
parent ea8c015eff
commit 2d81e42447
12 changed files with 357 additions and 291 deletions

View file

@ -1,6 +1,6 @@
import { useContext, useEffect, useMemo, useRef, useState } from "react";
import { memo, useContext, useEffect, useMemo, useRef, useState } from "react";
import { Double, getType, Int, newDynamic, prettyT, trie } from "dope2";
import { getType, prettyT, trie } from "dope2";
import { EnvContext } from "./EnvContext";
import type { Dynamic } from "./eval";
@ -8,7 +8,7 @@ import "./InputBlock.css";
import { Type } from "./Type";
import type { State2Props } from "./Editor";
import { autoInputWidth, focusNextElement, focusPrevElement, setRightMostCaretPosition } from "./util/dom_trickery";
import { parseDouble, parseInt } from "./util/parse";
import { attemptParseLiteral } from "./eval";
interface Literal {
kind: "literal";
@ -38,13 +38,15 @@ interface InputBlockProps extends State2Props<InputBlockState> {
}
const computeSuggestions = (text, env, suggestionPriority: (s: SuggestionType) => number): PrioritizedSuggestionType[] => {
const asDouble = parseDouble(text);
const asInt = parseInt(text);
const literals = attemptParseLiteral(text);
const ls = [
... (asDouble ? [["literal", asDouble.toString(), newDynamic(asDouble)(Double)]] : []),
... (asInt ? [["literal", asInt.toString(), newDynamic(BigInt(asInt))(Int)]] : []),
... trie.suggest(env.name2dyn)(text)(Infinity).map(([name,type]) => ["name", name, type]),
// literals
... literals.map((lit) => ["literal", text, lit]),
// names
... trie.suggest(env.name2dyn)(text)(Infinity)
.map(([name,type]) => ["name", name, type]),
]
// return ls;
return ls
@ -60,9 +62,9 @@ export function InputBlock({ state, setState, suggestionPriority, onCancel }: In
const [haveFocus, setHaveFocus] = useState(false); // whether to render suggestions or not
const singleSuggestion = trie.growPrefix(env.name2dyn)(text);
const suggestions = useMemo(() => computeSuggestions(text, env, suggestionPriority), [text]);
const suggestions = useMemo(() => computeSuggestions(text, env, suggestionPriority), [text, suggestionPriority, env]);
useEffect(() => autoInputWidth(inputRef, text), [inputRef, text]);
useEffect(() => autoInputWidth(inputRef, text+singleSuggestion), [inputRef, text, singleSuggestion]);
useEffect(() => {
if (focus) {
@ -81,7 +83,6 @@ export function InputBlock({ state, setState, suggestionPriority, onCancel }: In
}
const onTextChange = newText => {
const found = trie.get(env.name2dyn)(newText);
setState(state => ({...state, text: newText}));
}
@ -162,51 +163,67 @@ export function InputBlock({ state, setState, suggestionPriority, onCancel }: In
}
};
return <span>
<span className="">
{/* Dropdown suggestions */}
{haveFocus &&
<span style={{display:'inline-block'}}>
return <span className="inputBlock">
{/* Dropdown suggestions */}
{haveFocus &&
<span className="suggestionsPlaceholder">
<Suggestions
suggestions={suggestions}
onSelect={onSelectSuggestion}
i={i} setI={setI} />
</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}/>
{/* Single 'grey' suggestion */}
<span className="text-block suggest">{singleSuggestion}</span>
</span>
</span>
}
{/* 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 }) {
return <>{(suggestions.length > 0) &&
<div className={"suggestions"}>
{suggestions.map((suggestion, j) =>
<SuggestionMemo key={j}
{...{setI, j,
onSelect,
highlighted: i===j,
suggestion}}/>)}
</div>
}</>;
}
interface SuggestionProps {
setI: any;
j: number;
onSelect: any;
highlighted: boolean;
suggestion: PrioritizedSuggestionType;
}
function Suggestion({ setI, j, onSelect, highlighted, suggestion: [priority, kind, name, dynamic] }: SuggestionProps) {
const onMouseEnter = j => () => {
setI(j);
};
const onMouseDown = j => () => {
setI(j);
onSelect(suggestions[i]);
onSelect([priority, kind, name, dynamic]);
};
return <>{(suggestions.length > 0) &&
<div className={"suggestions"}>
{suggestions.map(([priority, kind, name, dynamic], j) =>
<div
key={`${j}_${name}`}
className={(i === j ? " selected" : "")}
onMouseEnter={onMouseEnter(j)}
onMouseDown={onMouseDown(j)}>
({priority}) ({kind}) {name} :: <Type type={getType(dynamic)} />
</div>)}
</div>
}</>;
}
return <div
key={`${j}_${name}`}
className={(highlighted ? " selected" : "")}
onMouseEnter={onMouseEnter(j)}
onMouseDown={onMouseDown(j)}>
({priority}) ({kind}) {name} :: <Type type={getType(dynamic)} />
</div>
}
const SuggestionMemo = memo<SuggestionProps>(Suggestion);