Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
596 changes: 554 additions & 42 deletions packages/cli/src/repl/application.ts

Large diffs are not rendered by default.

31 changes: 25 additions & 6 deletions packages/cli/src/repl/components/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@
import type { ReplDrawerRef, ReplSurface } from "../route.ts";

export type ReplAction =
/** Text for whichever field has focus. */
| { readonly kind: "type"; readonly text: string }
/** Remove the last decoded text unit from whichever field has focus. */
| { readonly kind: "erase" }
/**
* Text for one named field, or for the entry draft when it names none.
*
* The name travels with the text because focus belongs to the mounted tree
* and this vocabulary does not: a control that emitted bare text would have
* its characters delivered to whichever field the application last recorded,
* which is not necessarily the one under the cursor.
*/
| { readonly kind: "type"; readonly text: string; readonly field?: string }
/** Remove the last Unicode scalar value from one named field, or the draft. */
| { readonly kind: "erase"; readonly field?: string }
/** Admit the draft as this execution's one entry. */
| { readonly kind: "submit" }
| { readonly kind: "select-surface"; readonly surface: ReplSurface }
Expand All @@ -33,5 +40,17 @@ export type ReplAction =
| { readonly kind: "go-live" }
| { readonly kind: "pause" }
| { readonly kind: "continue" }
/** Answer the question waiting right now. */
| { readonly kind: "answer" };
/** Answer the question waiting right now, from the form as it stands. */
| { readonly kind: "answer" }
/** Give this form field the focus that text and Backspace act on. */
| { readonly kind: "select-field"; readonly field: string }
/** Put one offered enum value into one field. */
| { readonly kind: "choose"; readonly field: string; readonly option: string }
/**
* Move the read-only message region by whole lines.
*
* A delta rather than a position, because the control that emits it knows
* which way it points and nothing else: how far the region can go is the
* frame's to decide, and clamping belongs where the height is known.
*/
| { readonly kind: "scroll"; readonly delta: number };
29 changes: 27 additions & 2 deletions packages/cli/src/repl/components/rows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,26 @@ function activation(input: ReplViewData): ReplAction | undefined {
if (select === "close") {
return { kind: "close-drawer" };
}
if (select === "form-field") {
const field = named["field"];
return typeof field === "string" ? { kind: "select-field", field } : undefined;
}
if (select === "form-choice") {
const field = named["field"];
const option = named["option"];
return typeof field === "string" && typeof option === "string"
? { kind: "choose", field, option }
: undefined;
}
if (select === "form-submit") {
return { kind: "answer" };
}
if (select === "scroll") {
// A direction, not a position: the control knows which way it points, and
// how far the region may travel is the frame's to decide.
const delta = named["delta"];
return typeof delta === "number" ? { kind: "scroll", delta } : undefined;
}
return undefined;
}

Expand All @@ -159,14 +179,19 @@ export const FIELD: ReplComponent<ReplAction> = component<ReplAction>({
node.render(rendered(node.input));
node.onInput((input: ReplViewData) => node.render(rendered(input)));
node.claim((event: ReplInputEvent): ReplAction | undefined => {
// The field this line edits, read off its own description. Text reaches
// the field the person is actually on rather than whichever one the
// application last recorded as selected.
const named = optional(node.input, "field");
const owns: { readonly field?: string } = named === undefined ? {} : { field: named };
if (event.kind === "text") {
return { kind: "type", text: event.text };
return { kind: "type", text: event.text, ...owns };
}
if (event.kind === "pointer") {
return undefined;
}
if (event.key === "Backspace") {
return { kind: "erase" };
return { kind: "erase", ...owns };
}
if (event.key === "Enter") {
return submission(node.input);
Expand Down
Loading
Loading