@mnmal-ai/surface-core 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +10 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/compose.d.ts.map +1 -1
- package/dist/fullscreen.d.ts +19 -0
- package/dist/fullscreen.d.ts.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +782 -179
- package/dist/keys.d.ts +14 -0
- package/dist/keys.d.ts.map +1 -1
- package/dist/list-pager-state.d.ts +76 -0
- package/dist/list-pager-state.d.ts.map +1 -0
- package/dist/list-pager.d.ts +24 -0
- package/dist/list-pager.d.ts.map +1 -0
- package/dist/module.d.ts +19 -3
- package/dist/module.d.ts.map +1 -1
- package/dist/{overlay-DMjCbsaC.js → overlay-5s8slC38.js} +216 -9
- package/dist/pure.d.ts +3 -1
- package/dist/pure.d.ts.map +1 -1
- package/dist/pure.js +2 -2
- package/dist/terminal.d.ts +12 -2
- package/dist/terminal.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import { spinnerFrame } from "@mnmal-ai/surface-kit";
|
|
1
|
+
import { $ as parseCommandLine, A as RESET_GROUND, At as normaliseKeySpec, B as sanitise, C as emptyTranscript, Ct as wordLeft, D as CLEAR_PANE, Dt as RESERVED_GLOBAL_KEYS, E as sendTurn, Et as CompositionError, F as liveShrink, G as commandUsage, H as unavailableReason, I as prepareSequence, J as menuWindow, K as completeDraft, L as prepareTerminal, M as WHEEL_ARROWS_ON, Mt as mergeCompletions, N as anchorPadding, O as DEFAULT_GROUND, Ot as assertComposes, P as groundSequence, Q as paletteRows, R as reflowSequence, S as dropGaps, St as pasteToLine, T as markedFlights, Tt as applyInput, U as MENU_NAME_WIDTH, V as routeInput, W as clampSelection, X as paletteMatches, Y as moveSelection, Z as paletteQuery, _ as visibleRows, _t as atEnd, a as backspace, at as remember, b as arriveTurn, bt as insertChunk, c as listStep, ct as FOLLOWING, d as placement, dt as resolveFullscreen, et as keySpecOf, f as push, ft as scrollBy, g as typeChar, gt as EMPTY_DRAFT, h as turn, ht as ctrlC, i as back, it as mayWalk, j as WHEEL_ARROWS_OFF, jt as applySuggestion, k as REPRINT_LINES, kt as composeModules, l as move, lt as linesBelow, m as startFilter, mt as CTRL_C_WINDOW_MS, n as setPanel, nt as EMPTY_HISTORY, o as cursorRow, ot as settle, p as scrollDetail, pt as viewTop, q as enterRuns, r as defineModule, rt as HISTORY_MAX, s as endFilter, st as walk, t as panelOf, tt as resolveKey, u as pagerAt, ut as pageStep, v as addGap, vt as editKey, w as heldAsks, wt as wordRight, x as commitEntry, xt as maskDraft, y as answerEntry, yt as graphemeAt, z as reprintStart } from "./overlay-5s8slC38.js";
|
|
2
|
+
import { ListRows, spinnerFrame } from "@mnmal-ai/surface-kit";
|
|
3
3
|
import { Box, Static, Text, measureElement, render, useApp, useInput, usePaste, useStdout } from "ink";
|
|
4
4
|
import { Component, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { listLines, wrapCells } from "@mnmal-ai/surface-kit/list";
|
|
6
7
|
|
|
7
8
|
//#region src/app.tsx
|
|
8
9
|
/** Calls a module's view as a component, so a throw lands in the boundary and not in the shell. */
|
|
@@ -71,6 +72,15 @@ const OVERLAY_PAD_Y = 1;
|
|
|
71
72
|
* not scanned; both come out of the size the panel view is handed (operator, 2026-10-09). */
|
|
72
73
|
const PANEL_PAD_X = 4;
|
|
73
74
|
const PANEL_PAD_Y = 1;
|
|
75
|
+
/** What a panel view may draw in: its insets, its own or the default, come out of the outer size. */
|
|
76
|
+
function panelInner(outer, def) {
|
|
77
|
+
const x = def?.inset?.x ?? PANEL_PAD_X;
|
|
78
|
+
const y = def?.inset?.y ?? PANEL_PAD_Y;
|
|
79
|
+
return {
|
|
80
|
+
columns: Math.max(1, outer.columns - 2 * x),
|
|
81
|
+
rows: Math.max(1, outer.rows - 2 * y)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
74
84
|
/**
|
|
75
85
|
* The `/` palette and the completion menu, directly above the input on the band's ground: a fixed name column, the detail dimmed, the selection on the `selection` tone, a window
|
|
76
86
|
* that follows the selection, and a hint line.
|
|
@@ -117,11 +127,12 @@ function defaultPanelRows(terminalRows, opts) {
|
|
|
117
127
|
return Math.max(3, terminalRows - SHELL_ROWS - (opts?.band === true ? BAND_ROWS : 0));
|
|
118
128
|
}
|
|
119
129
|
/** What `run()` prepares the terminal with, or `null` when the host has prepared it already. */
|
|
120
|
-
function runTerminalOptions(opts) {
|
|
130
|
+
function runTerminalOptions(opts, fullscreen) {
|
|
121
131
|
if (opts.terminal === false) return null;
|
|
122
132
|
return {
|
|
123
133
|
anchor: opts.anchored === true,
|
|
124
|
-
...opts.terminal
|
|
134
|
+
...opts.terminal,
|
|
135
|
+
...fullscreen ? { fullscreen } : {}
|
|
125
136
|
};
|
|
126
137
|
}
|
|
127
138
|
function createApp(opts) {
|
|
@@ -140,9 +151,13 @@ function createApp(opts) {
|
|
|
140
151
|
const presenceModule = registry.presence?.module ?? "";
|
|
141
152
|
const panelRows = opts.panelRows ?? ((r) => defaultPanelRows(r, { band }));
|
|
142
153
|
const anchored = opts.anchored === true;
|
|
154
|
+
const fs = resolveFullscreen(opts.fullscreen, process.env.SURFACE_FULLSCREEN);
|
|
143
155
|
function App({ columns, rows }) {
|
|
144
156
|
const { exit } = useApp();
|
|
145
|
-
const
|
|
157
|
+
const ink = useStdout();
|
|
158
|
+
const stdout = ink.stdout;
|
|
159
|
+
const full = fs.on && stdout.isTTY === true;
|
|
160
|
+
const relays = anchored && !full;
|
|
146
161
|
const [term, setTerm] = useState(() => ({
|
|
147
162
|
columns: stdout.columns ?? 120,
|
|
148
163
|
rows: stdout.rows ?? 30
|
|
@@ -157,12 +172,16 @@ function createApp(opts) {
|
|
|
157
172
|
const heights = useRef(/* @__PURE__ */ new Map());
|
|
158
173
|
const [reprintFrom, setReprintFrom] = useState(0);
|
|
159
174
|
const relayout = useCallback(() => {
|
|
160
|
-
if (stdout.isTTY)
|
|
175
|
+
if (stdout.isTTY) ink.write(reflowSequence(stdout.rows));
|
|
161
176
|
putTranscript(dropGaps);
|
|
162
177
|
const kept = transcriptRef.current.committed;
|
|
163
178
|
setReprintFrom(reprintStart(kept.map((e) => heights.current.get(e.key) ?? 1), REPRINT_LINES));
|
|
164
179
|
setEpoch((e) => e + 1);
|
|
165
|
-
}, [
|
|
180
|
+
}, [
|
|
181
|
+
stdout,
|
|
182
|
+
ink,
|
|
183
|
+
putTranscript
|
|
184
|
+
]);
|
|
166
185
|
useEffect(() => {
|
|
167
186
|
const laidOut = {
|
|
168
187
|
columns: stdout.columns ?? 120,
|
|
@@ -174,7 +193,7 @@ function createApp(opts) {
|
|
|
174
193
|
columns: stdout.columns ?? 120,
|
|
175
194
|
rows: stdout.rows ?? 30
|
|
176
195
|
});
|
|
177
|
-
if (!
|
|
196
|
+
if (!relays) return;
|
|
178
197
|
clearTimeout(timer);
|
|
179
198
|
timer = setTimeout(() => {
|
|
180
199
|
const now = {
|
|
@@ -193,7 +212,11 @@ function createApp(opts) {
|
|
|
193
212
|
clearTimeout(timer);
|
|
194
213
|
stdout.off("resize", onResize);
|
|
195
214
|
};
|
|
196
|
-
}, [
|
|
215
|
+
}, [
|
|
216
|
+
stdout,
|
|
217
|
+
relayout,
|
|
218
|
+
relays
|
|
219
|
+
]);
|
|
197
220
|
const width = Math.max(1, (columns ?? term.columns) - 2 * gutter);
|
|
198
221
|
const height = rows ?? term.rows;
|
|
199
222
|
const showRail = registry.rail.length > 0 && width >= railMin;
|
|
@@ -201,12 +224,8 @@ function createApp(opts) {
|
|
|
201
224
|
columns: Math.max(1, width - (showRail ? RAIL_COLUMNS : 0)),
|
|
202
225
|
rows: Math.max(1, panelRows(height))
|
|
203
226
|
};
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
rows: Math.max(1, panelOuter.rows - 2)
|
|
207
|
-
};
|
|
208
|
-
const sizeRef = useRef(panelSize);
|
|
209
|
-
sizeRef.current = panelSize;
|
|
227
|
+
const outerRef = useRef(panelOuter);
|
|
228
|
+
outerRef.current = panelOuter;
|
|
210
229
|
const [live, setLive] = useState(/* @__PURE__ */ new Map());
|
|
211
230
|
const [draft, setDraft] = useState(EMPTY_DRAFT);
|
|
212
231
|
const [, setHistory] = useState(EMPTY_HISTORY);
|
|
@@ -238,6 +257,12 @@ function createApp(opts) {
|
|
|
238
257
|
armedRef.current = a;
|
|
239
258
|
setArmed(a);
|
|
240
259
|
}, []);
|
|
260
|
+
const [scroll, setScroll] = useState(FOLLOWING);
|
|
261
|
+
const scrollRef = useRef(FOLLOWING);
|
|
262
|
+
const putScroll = useCallback((next) => {
|
|
263
|
+
scrollRef.current = next;
|
|
264
|
+
setScroll(next);
|
|
265
|
+
}, []);
|
|
241
266
|
const defOf = useCallback((o) => o ? registry.panels.get(`${o.module}/${o.panel}`)?.def : void 0, []);
|
|
242
267
|
const putHistory = useCallback((h) => {
|
|
243
268
|
historyRef.current = h;
|
|
@@ -327,7 +352,7 @@ function createApp(opts) {
|
|
|
327
352
|
const ctxFor = useCallback((moduleId) => ({
|
|
328
353
|
theme,
|
|
329
354
|
signal: appAbort.current.signal,
|
|
330
|
-
size: () =>
|
|
355
|
+
size: () => panelInner(outerRef.current, defOf(overlayRef.current)),
|
|
331
356
|
open: (panel, state, opts) => {
|
|
332
357
|
if (disposed.current) return;
|
|
333
358
|
if (!registry.panels.has(`${moduleId}/${panel}`)) throw new Error(`module '${moduleId}' has no panel '${panel}'`);
|
|
@@ -338,6 +363,7 @@ function createApp(opts) {
|
|
|
338
363
|
state
|
|
339
364
|
});
|
|
340
365
|
},
|
|
366
|
+
panelOpen: () => overlayRef.current !== null,
|
|
341
367
|
update: (panel, fn) => {
|
|
342
368
|
if (disposed.current) return;
|
|
343
369
|
const o = overlayRef.current;
|
|
@@ -386,7 +412,8 @@ function createApp(opts) {
|
|
|
386
412
|
say,
|
|
387
413
|
guard,
|
|
388
414
|
putOverlay,
|
|
389
|
-
showToast
|
|
415
|
+
showToast,
|
|
416
|
+
defOf
|
|
390
417
|
]);
|
|
391
418
|
const runTurn = useCallback(async (line, module, handler, sent) => {
|
|
392
419
|
const id = nextId.current++;
|
|
@@ -438,6 +465,7 @@ function createApp(opts) {
|
|
|
438
465
|
}
|
|
439
466
|
}, [ctxFor, putTranscript]);
|
|
440
467
|
const handleLine = useCallback((line, sent) => {
|
|
468
|
+
putScroll(FOLLOWING);
|
|
441
469
|
const text = line.trim();
|
|
442
470
|
const open = overlayRef.current;
|
|
443
471
|
const owner = open ? registry.panels.get(`${open.module}/${open.panel}`)?.def : void 0;
|
|
@@ -472,13 +500,43 @@ function createApp(opts) {
|
|
|
472
500
|
putHistory,
|
|
473
501
|
runTurn,
|
|
474
502
|
guard,
|
|
475
|
-
putOverlay
|
|
503
|
+
putOverlay,
|
|
504
|
+
putScroll
|
|
476
505
|
]);
|
|
477
506
|
handleLineRef.current = handleLine;
|
|
507
|
+
const viewportRef = useRef(null);
|
|
508
|
+
const flightsRef = useRef(null);
|
|
509
|
+
const [measured, setMeasured] = useState({
|
|
510
|
+
content: 0,
|
|
511
|
+
viewport: 1
|
|
512
|
+
});
|
|
513
|
+
const measuredRef = useRef(measured);
|
|
514
|
+
const contentRows = useCallback(() => {
|
|
515
|
+
let rows = 0;
|
|
516
|
+
for (const e of transcriptRef.current.committed) rows += heights.current.get(e.key) ?? 1;
|
|
517
|
+
return rows + (flightsRef.current === null ? 0 : measureElement(flightsRef.current).height);
|
|
518
|
+
}, []);
|
|
519
|
+
const viewportRows = useCallback(() => viewportRef.current === null ? measuredRef.current.viewport : Math.max(1, measureElement(viewportRef.current).height), []);
|
|
520
|
+
useEffect(() => {
|
|
521
|
+
if (!full || viewportRef.current === null) return;
|
|
522
|
+
const now = {
|
|
523
|
+
content: contentRows(),
|
|
524
|
+
viewport: viewportRows()
|
|
525
|
+
};
|
|
526
|
+
const held = scrollRef.current.held;
|
|
527
|
+
if (held !== null && held >= Math.max(0, now.content - now.viewport)) putScroll(FOLLOWING);
|
|
528
|
+
const was = measuredRef.current;
|
|
529
|
+
if (now.content === was.content && now.viewport === was.viewport) return;
|
|
530
|
+
measuredRef.current = now;
|
|
531
|
+
setMeasured(now);
|
|
532
|
+
});
|
|
533
|
+
useEffect(() => {
|
|
534
|
+
if (fs.invalid !== null) print(`SURFACE_FULLSCREEN=${fs.invalid} ignored: use on or off`, "warn");
|
|
535
|
+
}, [print]);
|
|
478
536
|
const liveRef = useRef(null);
|
|
479
537
|
const lastLive = useRef(null);
|
|
480
538
|
useEffect(() => {
|
|
481
|
-
if (!
|
|
539
|
+
if (!relays || liveRef.current === null) return;
|
|
482
540
|
const now = {
|
|
483
541
|
height: measureElement(liveRef.current).height,
|
|
484
542
|
committed: transcript.committed.filter((e) => e.kind !== "gap").length,
|
|
@@ -491,9 +549,13 @@ function createApp(opts) {
|
|
|
491
549
|
const panelOpen = overlay !== null || menu !== null;
|
|
492
550
|
const wasPanelOpen = useRef(panelOpen);
|
|
493
551
|
useEffect(() => {
|
|
494
|
-
if (
|
|
552
|
+
if (relays && wasPanelOpen.current && !panelOpen) relayout();
|
|
495
553
|
wasPanelOpen.current = panelOpen;
|
|
496
|
-
}, [
|
|
554
|
+
}, [
|
|
555
|
+
panelOpen,
|
|
556
|
+
relayout,
|
|
557
|
+
relays
|
|
558
|
+
]);
|
|
497
559
|
const [pendingLine, setPendingLine] = useState(null);
|
|
498
560
|
useEffect(() => {
|
|
499
561
|
if (pendingLine === null) return;
|
|
@@ -501,7 +563,7 @@ function createApp(opts) {
|
|
|
501
563
|
handleLine(pendingLine);
|
|
502
564
|
}, [pendingLine, handleLine]);
|
|
503
565
|
const sendAfterClose = (line) => {
|
|
504
|
-
if (
|
|
566
|
+
if (relays) setPendingLine(line);
|
|
505
567
|
else handleLine(line);
|
|
506
568
|
};
|
|
507
569
|
const putDraft = useCallback((d) => {
|
|
@@ -580,7 +642,8 @@ function createApp(opts) {
|
|
|
580
642
|
} : null,
|
|
581
643
|
menu: menu?.kind ?? null,
|
|
582
644
|
draftEmpty: draft.text === "",
|
|
583
|
-
armed: armedOn !== null && overlay?.module === armedOn.module && overlay.panel === armedOn.panel && draft.text === armedOn.key ? armedOn.key : null
|
|
645
|
+
armed: armedOn !== null && overlay?.module === armedOn.module && overlay.panel === armedOn.panel && draft.text === armedOn.key ? armedOn.key : null,
|
|
646
|
+
...full ? { viewport: scrollRef.current.held === null ? "following" : "held" } : {}
|
|
584
647
|
};
|
|
585
648
|
const route = resolveKey(state, key, registry);
|
|
586
649
|
switch (route.kind) {
|
|
@@ -640,12 +703,16 @@ function createApp(opts) {
|
|
|
640
703
|
}
|
|
641
704
|
return;
|
|
642
705
|
}
|
|
643
|
-
case "panel":
|
|
644
|
-
|
|
645
|
-
|
|
706
|
+
case "panel":
|
|
707
|
+
case "panel-text": {
|
|
708
|
+
const def = registry.panels.get(`${route.module}/${route.panel}`)?.def;
|
|
709
|
+
const act = route.kind === "panel" ? def?.on?.[route.act] : void 0;
|
|
710
|
+
const typed = route.kind === "panel-text" ? def?.text : void 0;
|
|
711
|
+
if (!act && !typed || !overlay) return;
|
|
646
712
|
let next;
|
|
647
713
|
try {
|
|
648
|
-
|
|
714
|
+
const c = ctxFor(route.module);
|
|
715
|
+
next = act ? act(overlay.state, c) : typed?.(overlay.state, route.text, c);
|
|
649
716
|
} catch (err) {
|
|
650
717
|
guard(route.module, () => {
|
|
651
718
|
throw err;
|
|
@@ -731,6 +798,13 @@ function createApp(opts) {
|
|
|
731
798
|
putDraft(atEnd(step.text));
|
|
732
799
|
return;
|
|
733
800
|
}
|
|
801
|
+
case "scroll": {
|
|
802
|
+
const content = contentRows();
|
|
803
|
+
const viewport = viewportRows();
|
|
804
|
+
const step = route.act === "page-up" ? -pageStep(viewport) : pageStep(viewport);
|
|
805
|
+
putScroll(route.act === "bottom" ? FOLLOWING : scrollBy(scrollRef.current, step, content, viewport));
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
734
808
|
case "submit": {
|
|
735
809
|
putArmed(null);
|
|
736
810
|
const line = draft.text;
|
|
@@ -941,6 +1015,230 @@ function createApp(opts) {
|
|
|
941
1015
|
text: `… ${reprintFrom} earlier ${reprintFrom === 1 ? "entry" : "entries"} not reprinted`,
|
|
942
1016
|
tone: "dim"
|
|
943
1017
|
}, ...transcript.committed.slice(reprintFrom)];
|
|
1018
|
+
const flightRows = /* @__PURE__ */ jsxs(Fragment, { children: [markedFlights(transcript).map((f) => /* @__PURE__ */ jsx(Box, { children: waiting(f, false) }, `marked:${f.id}`)), heldAsks(transcript).map((f) => /* @__PURE__ */ jsxs(Box, {
|
|
1019
|
+
flexDirection: "column",
|
|
1020
|
+
children: [askView ? /* @__PURE__ */ jsx(Safe, {
|
|
1021
|
+
module: "app",
|
|
1022
|
+
theme,
|
|
1023
|
+
render: () => askView({
|
|
1024
|
+
entry: f,
|
|
1025
|
+
held: true,
|
|
1026
|
+
theme,
|
|
1027
|
+
size: { columns: width }
|
|
1028
|
+
})
|
|
1029
|
+
}) : /* @__PURE__ */ jsxs(Text, {
|
|
1030
|
+
color: theme.color.accent,
|
|
1031
|
+
children: [
|
|
1032
|
+
theme.glyph.prompt,
|
|
1033
|
+
" ",
|
|
1034
|
+
sanitise(f.question)
|
|
1035
|
+
]
|
|
1036
|
+
}), waiting(f, true)]
|
|
1037
|
+
}, f.id))] });
|
|
1038
|
+
const overlayRows = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1039
|
+
panel && overlay ? /* @__PURE__ */ jsx(Box, {
|
|
1040
|
+
backgroundColor: panel.ground ?? theme.color.raised,
|
|
1041
|
+
paddingX: panel.inset?.x ?? PANEL_PAD_X,
|
|
1042
|
+
paddingY: panel.inset?.y ?? PANEL_PAD_Y,
|
|
1043
|
+
flexDirection: "column",
|
|
1044
|
+
children: /* @__PURE__ */ jsx(Safe, {
|
|
1045
|
+
module: overlay.module,
|
|
1046
|
+
theme,
|
|
1047
|
+
render: () => panel.view({
|
|
1048
|
+
state: overlay.state,
|
|
1049
|
+
theme,
|
|
1050
|
+
size: panelInner(panelOuter, panel),
|
|
1051
|
+
...armed !== null && armed.module === overlay.module && armed.panel === overlay.panel && draft.text === armed.key ? { armed: {
|
|
1052
|
+
key: armed.key,
|
|
1053
|
+
label: armed.label
|
|
1054
|
+
} } : {}
|
|
1055
|
+
})
|
|
1056
|
+
}, `${overlay.module}/${overlay.panel}`)
|
|
1057
|
+
}) : null,
|
|
1058
|
+
menu?.kind === "palette" ? /* @__PURE__ */ jsx(MenuList, {
|
|
1059
|
+
theme,
|
|
1060
|
+
rows: paletteItems.map((c) => ({
|
|
1061
|
+
key: c.name,
|
|
1062
|
+
name: commandUsage(c),
|
|
1063
|
+
detail: c.summary
|
|
1064
|
+
})),
|
|
1065
|
+
selected: menu.selected,
|
|
1066
|
+
height: menuRows,
|
|
1067
|
+
empty: `no command matches ${draft.text}`,
|
|
1068
|
+
hint: "tab complete · enter run · esc close"
|
|
1069
|
+
}) : null,
|
|
1070
|
+
menu?.kind === "completion" ? /* @__PURE__ */ jsx(MenuList, {
|
|
1071
|
+
theme,
|
|
1072
|
+
rows: menu.items.map((s) => ({
|
|
1073
|
+
key: s.insert,
|
|
1074
|
+
name: s.label,
|
|
1075
|
+
detail: s.detail ?? ""
|
|
1076
|
+
})),
|
|
1077
|
+
selected: menu.selected,
|
|
1078
|
+
height: menuRows,
|
|
1079
|
+
empty: "no completions",
|
|
1080
|
+
hint: "tab complete · esc close"
|
|
1081
|
+
}) : null
|
|
1082
|
+
] });
|
|
1083
|
+
const pinnedRows = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1084
|
+
band ? /* @__PURE__ */ jsxs(Box, {
|
|
1085
|
+
borderStyle: "bold",
|
|
1086
|
+
borderTop: false,
|
|
1087
|
+
borderRight: false,
|
|
1088
|
+
borderBottom: false,
|
|
1089
|
+
borderColor: theme.color.edge,
|
|
1090
|
+
backgroundColor: theme.color.surfaceAlt,
|
|
1091
|
+
paddingX: 1,
|
|
1092
|
+
paddingY: 1,
|
|
1093
|
+
flexDirection: "column",
|
|
1094
|
+
children: [/* @__PURE__ */ jsxs(Text, { children: [
|
|
1095
|
+
shownDraft.text.slice(0, shownDraft.cursor),
|
|
1096
|
+
under === "" ? /* @__PURE__ */ jsx(Text, {
|
|
1097
|
+
color: theme.color.cursor,
|
|
1098
|
+
children: "█"
|
|
1099
|
+
}) : /* @__PURE__ */ jsx(Text, {
|
|
1100
|
+
inverse: true,
|
|
1101
|
+
children: under
|
|
1102
|
+
}),
|
|
1103
|
+
under === "" ? "" : shownDraft.text.slice(shownDraft.cursor + under.length)
|
|
1104
|
+
] }), /* @__PURE__ */ jsxs(Box, {
|
|
1105
|
+
marginTop: 1,
|
|
1106
|
+
children: [
|
|
1107
|
+
presence ? /* @__PURE__ */ jsx(Box, {
|
|
1108
|
+
flexShrink: 0,
|
|
1109
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
1110
|
+
bold: true,
|
|
1111
|
+
children: [sanitise(presence.name), " "]
|
|
1112
|
+
})
|
|
1113
|
+
}) : null,
|
|
1114
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1115
|
+
flexGrow: 1,
|
|
1116
|
+
minWidth: 0,
|
|
1117
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1118
|
+
color: theme.color.dim,
|
|
1119
|
+
wrap: "truncate",
|
|
1120
|
+
children: presence?.view ? /* @__PURE__ */ jsx(Safe, {
|
|
1121
|
+
module: presenceModule,
|
|
1122
|
+
theme,
|
|
1123
|
+
render: () => presence.view?.({ theme })
|
|
1124
|
+
}) : null
|
|
1125
|
+
})
|
|
1126
|
+
}),
|
|
1127
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1128
|
+
flexShrink: 0,
|
|
1129
|
+
paddingLeft: notice === null ? 0 : 2,
|
|
1130
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1131
|
+
color: theme.color.warn,
|
|
1132
|
+
children: notice ?? " "
|
|
1133
|
+
})
|
|
1134
|
+
})
|
|
1135
|
+
]
|
|
1136
|
+
})]
|
|
1137
|
+
}) : /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
1138
|
+
color: theme.color.accent,
|
|
1139
|
+
children: [theme.glyph.prompt, " "]
|
|
1140
|
+
}), shownDraft.text] }),
|
|
1141
|
+
toast ? /* @__PURE__ */ jsx(Text, {
|
|
1142
|
+
color: theme.color[toast.tone],
|
|
1143
|
+
children: toast.text
|
|
1144
|
+
}) : null,
|
|
1145
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
1146
|
+
...band ? { marginTop: 1 } : {},
|
|
1147
|
+
children: [registry.status.map((s) => /* @__PURE__ */ jsx(Box, {
|
|
1148
|
+
marginRight: 2,
|
|
1149
|
+
children: /* @__PURE__ */ jsx(Safe, {
|
|
1150
|
+
module: s.module,
|
|
1151
|
+
theme,
|
|
1152
|
+
render: () => s.item.view({ theme })
|
|
1153
|
+
})
|
|
1154
|
+
}, `${s.module}:${s.item.id}`)), [...status.entries()].map(([k, v]) => /* @__PURE__ */ jsxs(Text, {
|
|
1155
|
+
color: theme.color[v.tone],
|
|
1156
|
+
children: [v.text, " "]
|
|
1157
|
+
}, k))]
|
|
1158
|
+
})
|
|
1159
|
+
] });
|
|
1160
|
+
const railColumn = /* @__PURE__ */ jsx(Fragment, { children: showRail ? /* @__PURE__ */ jsx(Box, {
|
|
1161
|
+
flexDirection: "column",
|
|
1162
|
+
width: 32,
|
|
1163
|
+
marginLeft: 1,
|
|
1164
|
+
children: registry.rail.map((r) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Safe, {
|
|
1165
|
+
module: r.module,
|
|
1166
|
+
theme,
|
|
1167
|
+
render: () => r.item.view({ theme })
|
|
1168
|
+
}) }, `${r.module}:${r.item.id}`))
|
|
1169
|
+
}) : null });
|
|
1170
|
+
if (full) {
|
|
1171
|
+
const { content, viewport } = measured;
|
|
1172
|
+
const top = viewTop(scroll, content, viewport);
|
|
1173
|
+
const below = linesBelow(scroll, content, viewport);
|
|
1174
|
+
const lo = top - viewport;
|
|
1175
|
+
const hi = top + 2 * viewport;
|
|
1176
|
+
const shown = [];
|
|
1177
|
+
let at = 0;
|
|
1178
|
+
let skipped = null;
|
|
1179
|
+
for (const e of transcript.committed) {
|
|
1180
|
+
const h = heights.current.get(e.key);
|
|
1181
|
+
const rows = h ?? 1;
|
|
1182
|
+
if (at + rows > lo && at < hi || h === void 0) {
|
|
1183
|
+
if (skipped === null) skipped = at;
|
|
1184
|
+
shown.push(e);
|
|
1185
|
+
}
|
|
1186
|
+
at += rows;
|
|
1187
|
+
}
|
|
1188
|
+
const rowsAbove = skipped ?? at;
|
|
1189
|
+
const markerText = `↓ ${below} lines below · End to jump back`;
|
|
1190
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1191
|
+
flexDirection: "row",
|
|
1192
|
+
height,
|
|
1193
|
+
paddingX: gutter,
|
|
1194
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
1195
|
+
flexDirection: "column",
|
|
1196
|
+
flexGrow: 1,
|
|
1197
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
1198
|
+
ref: viewportRef,
|
|
1199
|
+
flexDirection: "column",
|
|
1200
|
+
flexGrow: 1,
|
|
1201
|
+
flexShrink: 1,
|
|
1202
|
+
minHeight: 1,
|
|
1203
|
+
overflow: "hidden",
|
|
1204
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
1205
|
+
flexDirection: "column",
|
|
1206
|
+
flexShrink: 0,
|
|
1207
|
+
marginTop: -(top - rowsAbove),
|
|
1208
|
+
children: [shown.map((e) => /* @__PURE__ */ jsx(Printed, {
|
|
1209
|
+
id: e.key,
|
|
1210
|
+
heights: heights.current,
|
|
1211
|
+
paddingX: 0,
|
|
1212
|
+
children: committed(e)
|
|
1213
|
+
}, e.key)), /* @__PURE__ */ jsx(Box, {
|
|
1214
|
+
ref: flightsRef,
|
|
1215
|
+
flexDirection: "column",
|
|
1216
|
+
flexShrink: 0,
|
|
1217
|
+
children: flightRows
|
|
1218
|
+
})]
|
|
1219
|
+
}), panelOpen ? /* @__PURE__ */ jsx(Box, {
|
|
1220
|
+
position: "absolute",
|
|
1221
|
+
bottom: 0,
|
|
1222
|
+
width: "100%",
|
|
1223
|
+
flexDirection: "column",
|
|
1224
|
+
children: overlayRows
|
|
1225
|
+
}) : below > 0 ? /* @__PURE__ */ jsx(Box, {
|
|
1226
|
+
position: "absolute",
|
|
1227
|
+
bottom: 0,
|
|
1228
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1229
|
+
color: theme.color.dim,
|
|
1230
|
+
wrap: "truncate",
|
|
1231
|
+
children: markerText.padEnd(panelOuter.columns)
|
|
1232
|
+
})
|
|
1233
|
+
}) : null]
|
|
1234
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
1235
|
+
flexDirection: "column",
|
|
1236
|
+
flexShrink: 0,
|
|
1237
|
+
children: pinnedRows
|
|
1238
|
+
})]
|
|
1239
|
+
}), railColumn]
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
944
1242
|
return /* @__PURE__ */ jsxs(Box, {
|
|
945
1243
|
flexDirection: "column",
|
|
946
1244
|
children: [/* @__PURE__ */ jsx(Static, {
|
|
@@ -959,170 +1257,475 @@ function createApp(opts) {
|
|
|
959
1257
|
flexDirection: "column",
|
|
960
1258
|
flexGrow: 1,
|
|
961
1259
|
children: [
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
children: [askView ? /* @__PURE__ */ jsx(Safe, {
|
|
966
|
-
module: "app",
|
|
967
|
-
theme,
|
|
968
|
-
render: () => askView({
|
|
969
|
-
entry: f,
|
|
970
|
-
held: true,
|
|
971
|
-
theme,
|
|
972
|
-
size: { columns: width }
|
|
973
|
-
})
|
|
974
|
-
}) : /* @__PURE__ */ jsxs(Text, {
|
|
975
|
-
color: theme.color.accent,
|
|
976
|
-
children: [
|
|
977
|
-
theme.glyph.prompt,
|
|
978
|
-
" ",
|
|
979
|
-
sanitise(f.question)
|
|
980
|
-
]
|
|
981
|
-
}), waiting(f, true)]
|
|
982
|
-
}, f.id)),
|
|
983
|
-
panel && overlay ? /* @__PURE__ */ jsx(Box, {
|
|
984
|
-
backgroundColor: theme.color.raised,
|
|
985
|
-
paddingX: PANEL_PAD_X,
|
|
986
|
-
paddingY: PANEL_PAD_Y,
|
|
987
|
-
flexDirection: "column",
|
|
988
|
-
children: /* @__PURE__ */ jsx(Safe, {
|
|
989
|
-
module: overlay.module,
|
|
990
|
-
theme,
|
|
991
|
-
render: () => panel.view({
|
|
992
|
-
state: overlay.state,
|
|
993
|
-
theme,
|
|
994
|
-
size: panelSize,
|
|
995
|
-
...armed !== null && armed.module === overlay.module && armed.panel === overlay.panel && draft.text === armed.key ? { armed: {
|
|
996
|
-
key: armed.key,
|
|
997
|
-
label: armed.label
|
|
998
|
-
} } : {}
|
|
999
|
-
})
|
|
1000
|
-
}, `${overlay.module}/${overlay.panel}`)
|
|
1001
|
-
}) : null,
|
|
1002
|
-
menu?.kind === "palette" ? /* @__PURE__ */ jsx(MenuList, {
|
|
1003
|
-
theme,
|
|
1004
|
-
rows: paletteItems.map((c) => ({
|
|
1005
|
-
key: c.name,
|
|
1006
|
-
name: commandUsage(c),
|
|
1007
|
-
detail: c.summary
|
|
1008
|
-
})),
|
|
1009
|
-
selected: menu.selected,
|
|
1010
|
-
height: menuRows,
|
|
1011
|
-
empty: `no command matches ${draft.text}`,
|
|
1012
|
-
hint: "tab complete · enter run · esc close"
|
|
1013
|
-
}) : null,
|
|
1014
|
-
menu?.kind === "completion" ? /* @__PURE__ */ jsx(MenuList, {
|
|
1015
|
-
theme,
|
|
1016
|
-
rows: menu.items.map((s) => ({
|
|
1017
|
-
key: s.insert,
|
|
1018
|
-
name: s.label,
|
|
1019
|
-
detail: s.detail ?? ""
|
|
1020
|
-
})),
|
|
1021
|
-
selected: menu.selected,
|
|
1022
|
-
height: menuRows,
|
|
1023
|
-
empty: "no completions",
|
|
1024
|
-
hint: "tab complete · esc close"
|
|
1025
|
-
}) : null,
|
|
1026
|
-
band ? /* @__PURE__ */ jsxs(Box, {
|
|
1027
|
-
borderStyle: "bold",
|
|
1028
|
-
borderTop: false,
|
|
1029
|
-
borderRight: false,
|
|
1030
|
-
borderBottom: false,
|
|
1031
|
-
borderColor: theme.color.edge,
|
|
1032
|
-
backgroundColor: theme.color.surfaceAlt,
|
|
1033
|
-
paddingX: 1,
|
|
1034
|
-
paddingY: 1,
|
|
1035
|
-
flexDirection: "column",
|
|
1036
|
-
children: [/* @__PURE__ */ jsxs(Text, { children: [
|
|
1037
|
-
shownDraft.text.slice(0, shownDraft.cursor),
|
|
1038
|
-
under === "" ? /* @__PURE__ */ jsx(Text, {
|
|
1039
|
-
color: theme.color.cursor,
|
|
1040
|
-
children: "█"
|
|
1041
|
-
}) : /* @__PURE__ */ jsx(Text, {
|
|
1042
|
-
inverse: true,
|
|
1043
|
-
children: under
|
|
1044
|
-
}),
|
|
1045
|
-
under === "" ? "" : shownDraft.text.slice(shownDraft.cursor + under.length)
|
|
1046
|
-
] }), /* @__PURE__ */ jsxs(Box, {
|
|
1047
|
-
marginTop: 1,
|
|
1048
|
-
children: [
|
|
1049
|
-
presence ? /* @__PURE__ */ jsx(Box, {
|
|
1050
|
-
flexShrink: 0,
|
|
1051
|
-
children: /* @__PURE__ */ jsxs(Text, {
|
|
1052
|
-
bold: true,
|
|
1053
|
-
children: [sanitise(presence.name), " "]
|
|
1054
|
-
})
|
|
1055
|
-
}) : null,
|
|
1056
|
-
/* @__PURE__ */ jsx(Box, {
|
|
1057
|
-
flexGrow: 1,
|
|
1058
|
-
minWidth: 0,
|
|
1059
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
1060
|
-
color: theme.color.dim,
|
|
1061
|
-
wrap: "truncate",
|
|
1062
|
-
children: presence?.view ? /* @__PURE__ */ jsx(Safe, {
|
|
1063
|
-
module: presenceModule,
|
|
1064
|
-
theme,
|
|
1065
|
-
render: () => presence.view?.({ theme })
|
|
1066
|
-
}) : null
|
|
1067
|
-
})
|
|
1068
|
-
}),
|
|
1069
|
-
/* @__PURE__ */ jsx(Box, {
|
|
1070
|
-
flexShrink: 0,
|
|
1071
|
-
paddingLeft: notice === null ? 0 : 2,
|
|
1072
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
1073
|
-
color: theme.color.warn,
|
|
1074
|
-
children: notice ?? " "
|
|
1075
|
-
})
|
|
1076
|
-
})
|
|
1077
|
-
]
|
|
1078
|
-
})]
|
|
1079
|
-
}) : /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
1080
|
-
color: theme.color.accent,
|
|
1081
|
-
children: [theme.glyph.prompt, " "]
|
|
1082
|
-
}), shownDraft.text] }),
|
|
1083
|
-
toast ? /* @__PURE__ */ jsx(Text, {
|
|
1084
|
-
color: theme.color[toast.tone],
|
|
1085
|
-
children: toast.text
|
|
1086
|
-
}) : null,
|
|
1087
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
1088
|
-
...band ? { marginTop: 1 } : {},
|
|
1089
|
-
children: [registry.status.map((s) => /* @__PURE__ */ jsx(Box, {
|
|
1090
|
-
marginRight: 2,
|
|
1091
|
-
children: /* @__PURE__ */ jsx(Safe, {
|
|
1092
|
-
module: s.module,
|
|
1093
|
-
theme,
|
|
1094
|
-
render: () => s.item.view({ theme })
|
|
1095
|
-
})
|
|
1096
|
-
}, `${s.module}:${s.item.id}`)), [...status.entries()].map(([k, v]) => /* @__PURE__ */ jsxs(Text, {
|
|
1097
|
-
color: theme.color[v.tone],
|
|
1098
|
-
children: [v.text, " "]
|
|
1099
|
-
}, k))]
|
|
1100
|
-
})
|
|
1260
|
+
flightRows,
|
|
1261
|
+
overlayRows,
|
|
1262
|
+
pinnedRows
|
|
1101
1263
|
]
|
|
1102
|
-
}),
|
|
1103
|
-
flexDirection: "column",
|
|
1104
|
-
width: 32,
|
|
1105
|
-
marginLeft: 1,
|
|
1106
|
-
children: registry.rail.map((r) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Safe, {
|
|
1107
|
-
module: r.module,
|
|
1108
|
-
theme,
|
|
1109
|
-
render: () => r.item.view({ theme })
|
|
1110
|
-
}) }, `${r.module}:${r.item.id}`))
|
|
1111
|
-
}) : null]
|
|
1264
|
+
}), railColumn]
|
|
1112
1265
|
})]
|
|
1113
1266
|
});
|
|
1114
1267
|
}
|
|
1115
1268
|
return {
|
|
1116
1269
|
registry,
|
|
1117
1270
|
App,
|
|
1271
|
+
fullscreen: fs.on,
|
|
1118
1272
|
async run() {
|
|
1119
|
-
const
|
|
1273
|
+
const full = fs.on && process.stdout.isTTY === true;
|
|
1274
|
+
const prepare = runTerminalOptions(opts, full);
|
|
1120
1275
|
const restore = prepare === null ? () => {} : prepareTerminal(process.stdout, prepare);
|
|
1121
|
-
await render(/* @__PURE__ */ jsx(App, {}), {
|
|
1276
|
+
await render(/* @__PURE__ */ jsx(App, {}), {
|
|
1277
|
+
exitOnCtrlC: false,
|
|
1278
|
+
alternateScreen: full
|
|
1279
|
+
}).waitUntilExit();
|
|
1122
1280
|
restore();
|
|
1123
1281
|
}
|
|
1124
1282
|
};
|
|
1125
1283
|
}
|
|
1126
1284
|
|
|
1127
1285
|
//#endregion
|
|
1128
|
-
|
|
1286
|
+
//#region src/list-pager.tsx
|
|
1287
|
+
/** A list longer than this is paged rather than printed. */
|
|
1288
|
+
const INLINE_LIST_ROWS = 10;
|
|
1289
|
+
const LIST_REPLY_KIND = "surface.list";
|
|
1290
|
+
/** Rows a page holds in a panel `rows` tall: the heading, filter and hint lines take the rest. */
|
|
1291
|
+
const pageSizeFor = (rows) => Math.max(5, rows - 6);
|
|
1292
|
+
const cleanSpan = (s) => s.em === true ? {
|
|
1293
|
+
text: sanitise(s.text),
|
|
1294
|
+
em: true
|
|
1295
|
+
} : { text: sanitise(s.text) };
|
|
1296
|
+
const cleanRow = (r) => ({
|
|
1297
|
+
key: r.key,
|
|
1298
|
+
name: typeof r.name === "string" ? sanitise(r.name) : r.name.map(cleanSpan),
|
|
1299
|
+
...r.details ? { details: r.details.map(sanitise) } : {},
|
|
1300
|
+
...r.value !== void 0 ? { value: sanitise(r.value) } : {}
|
|
1301
|
+
});
|
|
1302
|
+
/** Everything an app hands in reaches the terminal sanitised. */
|
|
1303
|
+
const cleanList = (l) => ({
|
|
1304
|
+
heading: sanitise(l.heading),
|
|
1305
|
+
rows: l.rows.map(cleanRow),
|
|
1306
|
+
...l.total !== void 0 ? { total: l.total } : {},
|
|
1307
|
+
...l.notes ? { notes: l.notes.map(sanitise) } : {}
|
|
1308
|
+
});
|
|
1309
|
+
const cleanDetail = (d) => ({
|
|
1310
|
+
title: sanitise(d.title),
|
|
1311
|
+
...d.meta !== void 0 ? { meta: sanitise(d.meta) } : {},
|
|
1312
|
+
...d.body !== void 0 ? { body: sanitise(d.body) } : {}
|
|
1313
|
+
});
|
|
1314
|
+
const inAll = (l) => l.total !== void 0 && l.total > l.rows.length ? `, ${l.total} in all` : "";
|
|
1315
|
+
const isPromise = (v) => typeof v?.then === "function";
|
|
1316
|
+
const errorText = (e) => sanitise(e instanceof Error ? e.message : String(e));
|
|
1317
|
+
/** The detail's body lines and the room the panel gives them. */
|
|
1318
|
+
function detailLayout(d, size) {
|
|
1319
|
+
const lines = d.body === void 0 ? [] : wrapCells(d.body, size.columns);
|
|
1320
|
+
const room = Math.max(1, size.rows - (d.meta === void 0 ? 3 : 4));
|
|
1321
|
+
return {
|
|
1322
|
+
lines,
|
|
1323
|
+
room,
|
|
1324
|
+
maxScroll: Math.max(0, lines.length - room)
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
function Hint({ theme, parts }) {
|
|
1328
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
1329
|
+
marginTop: 1,
|
|
1330
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1331
|
+
color: theme.color.dim,
|
|
1332
|
+
wrap: "truncate",
|
|
1333
|
+
children: parts.join(" · ")
|
|
1334
|
+
})
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
function ListPage({ step, depth, theme, size }) {
|
|
1338
|
+
const shown = visibleRows(step);
|
|
1339
|
+
const per = pageSizeFor(size.rows);
|
|
1340
|
+
const pages = Math.max(1, Math.ceil(shown.length / per));
|
|
1341
|
+
const at = placement(step, per);
|
|
1342
|
+
const page = Math.min(at.page, pages - 1);
|
|
1343
|
+
const slice = shown.slice(page * per, page * per + per);
|
|
1344
|
+
const total = step.list.total;
|
|
1345
|
+
const of = total !== void 0 && total > step.list.rows.length ? ` · ${step.list.rows.length} of ${total}` : "";
|
|
1346
|
+
const hint = [
|
|
1347
|
+
"↑↓ row",
|
|
1348
|
+
"←→ page",
|
|
1349
|
+
...step.hooks.open ? ["enter open"] : [],
|
|
1350
|
+
...step.hooks.related ? ["r related"] : [],
|
|
1351
|
+
"/ filter",
|
|
1352
|
+
depth > 1 || step.filter !== "" || step.filtering ? "esc back" : "esc close"
|
|
1353
|
+
];
|
|
1354
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1355
|
+
flexDirection: "column",
|
|
1356
|
+
children: [
|
|
1357
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1358
|
+
wrap: "truncate",
|
|
1359
|
+
children: [
|
|
1360
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1361
|
+
color: theme.color.accent,
|
|
1362
|
+
children: "📋 List "
|
|
1363
|
+
}),
|
|
1364
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1365
|
+
bold: true,
|
|
1366
|
+
children: step.list.heading
|
|
1367
|
+
}),
|
|
1368
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1369
|
+
color: theme.color.dim,
|
|
1370
|
+
children: `${of} · page ${page + 1} of ${pages}`
|
|
1371
|
+
})
|
|
1372
|
+
]
|
|
1373
|
+
}),
|
|
1374
|
+
step.filtering || step.filter !== "" ? /* @__PURE__ */ jsx(Text, {
|
|
1375
|
+
color: theme.color.dim,
|
|
1376
|
+
children: `filter: ${step.filter}${step.filtering ? "▌" : ""}`
|
|
1377
|
+
}) : null,
|
|
1378
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1379
|
+
marginTop: 1,
|
|
1380
|
+
flexDirection: "column",
|
|
1381
|
+
children: slice.length === 0 ? /* @__PURE__ */ jsx(Text, {
|
|
1382
|
+
color: theme.color.dim,
|
|
1383
|
+
children: `no rows match ${step.filter}`
|
|
1384
|
+
}) : /* @__PURE__ */ jsx(ListRows, {
|
|
1385
|
+
lines: listLines(slice, size.columns, page * per + 1),
|
|
1386
|
+
theme,
|
|
1387
|
+
cursor: at.cursor
|
|
1388
|
+
})
|
|
1389
|
+
}),
|
|
1390
|
+
/* @__PURE__ */ jsx(Hint, {
|
|
1391
|
+
theme,
|
|
1392
|
+
parts: hint
|
|
1393
|
+
})
|
|
1394
|
+
]
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
function DetailPage({ step, theme, size }) {
|
|
1398
|
+
const hint = [
|
|
1399
|
+
"↑↓ scroll",
|
|
1400
|
+
...step.hooks.related ? ["r related"] : [],
|
|
1401
|
+
"esc back"
|
|
1402
|
+
];
|
|
1403
|
+
if (step.detail === "loading" || "error" in step.detail) return /* @__PURE__ */ jsxs(Box, {
|
|
1404
|
+
flexDirection: "column",
|
|
1405
|
+
children: [step.detail === "loading" ? /* @__PURE__ */ jsx(Text, {
|
|
1406
|
+
color: theme.color.dim,
|
|
1407
|
+
children: "loading…"
|
|
1408
|
+
}) : /* @__PURE__ */ jsx(Text, {
|
|
1409
|
+
color: theme.color.warn,
|
|
1410
|
+
children: step.detail.error
|
|
1411
|
+
}), /* @__PURE__ */ jsx(Hint, {
|
|
1412
|
+
theme,
|
|
1413
|
+
parts: ["esc back"]
|
|
1414
|
+
})]
|
|
1415
|
+
});
|
|
1416
|
+
const d = step.detail;
|
|
1417
|
+
const { lines, room } = detailLayout(d, size);
|
|
1418
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1419
|
+
flexDirection: "column",
|
|
1420
|
+
children: [
|
|
1421
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1422
|
+
wrap: "truncate",
|
|
1423
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
1424
|
+
color: theme.color.accent,
|
|
1425
|
+
children: "📋 "
|
|
1426
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
1427
|
+
bold: true,
|
|
1428
|
+
children: d.title
|
|
1429
|
+
})]
|
|
1430
|
+
}),
|
|
1431
|
+
d.meta !== void 0 ? /* @__PURE__ */ jsx(Text, {
|
|
1432
|
+
color: theme.color.dim,
|
|
1433
|
+
wrap: "truncate",
|
|
1434
|
+
children: d.meta
|
|
1435
|
+
}) : null,
|
|
1436
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1437
|
+
marginTop: 1,
|
|
1438
|
+
flexDirection: "column",
|
|
1439
|
+
children: lines.slice(step.scroll, step.scroll + room).map((l, i) => /* @__PURE__ */ jsx(Text, { children: l }, i))
|
|
1440
|
+
}),
|
|
1441
|
+
/* @__PURE__ */ jsx(Hint, {
|
|
1442
|
+
theme,
|
|
1443
|
+
parts: hint
|
|
1444
|
+
})
|
|
1445
|
+
]
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
function PagerView({ state, theme, size }) {
|
|
1449
|
+
const step = state.steps[state.steps.length - 1];
|
|
1450
|
+
if (step === void 0) return null;
|
|
1451
|
+
if (step.kind === "list") return /* @__PURE__ */ jsx(ListPage, {
|
|
1452
|
+
step,
|
|
1453
|
+
depth: state.steps.length,
|
|
1454
|
+
theme,
|
|
1455
|
+
size
|
|
1456
|
+
});
|
|
1457
|
+
if (step.kind === "detail") return /* @__PURE__ */ jsx(DetailPage, {
|
|
1458
|
+
step,
|
|
1459
|
+
theme,
|
|
1460
|
+
size
|
|
1461
|
+
});
|
|
1462
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1463
|
+
flexDirection: "column",
|
|
1464
|
+
children: [step.error === void 0 ? /* @__PURE__ */ jsx(Text, {
|
|
1465
|
+
color: theme.color.dim,
|
|
1466
|
+
children: "loading…"
|
|
1467
|
+
}) : /* @__PURE__ */ jsx(Text, {
|
|
1468
|
+
color: theme.color.warn,
|
|
1469
|
+
children: step.error
|
|
1470
|
+
}), /* @__PURE__ */ jsx(Hint, {
|
|
1471
|
+
theme,
|
|
1472
|
+
parts: ["esc back"]
|
|
1473
|
+
})]
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
function ListReply({ data, theme, columns }) {
|
|
1477
|
+
const { list, paged } = data;
|
|
1478
|
+
const n = list.rows.length;
|
|
1479
|
+
const width = Math.max(1, columns - 2);
|
|
1480
|
+
let tail = null;
|
|
1481
|
+
if (paged) tail = `${n} rows — in the list (/list)${inAll(list)}`;
|
|
1482
|
+
else if (n > 10) tail = `…and ${n - 10} more — /list to page through them${inAll(list)}`;
|
|
1483
|
+
else if (list.total !== void 0 && list.total > n) tail = `… ${list.total} in all`;
|
|
1484
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1485
|
+
flexDirection: "column",
|
|
1486
|
+
children: [
|
|
1487
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1488
|
+
bold: true,
|
|
1489
|
+
wrap: "wrap",
|
|
1490
|
+
children: `${list.heading}:`
|
|
1491
|
+
}),
|
|
1492
|
+
paged ? null : /* @__PURE__ */ jsx(Box, {
|
|
1493
|
+
marginTop: 1,
|
|
1494
|
+
children: /* @__PURE__ */ jsx(ListRows, {
|
|
1495
|
+
lines: listLines(list.rows.slice(0, 10), width),
|
|
1496
|
+
theme
|
|
1497
|
+
})
|
|
1498
|
+
}),
|
|
1499
|
+
tail !== null ? /* @__PURE__ */ jsx(Text, {
|
|
1500
|
+
color: theme.color.dim,
|
|
1501
|
+
children: ` ${tail}`
|
|
1502
|
+
}) : null,
|
|
1503
|
+
(list.notes ?? []).length > 0 ? /* @__PURE__ */ jsx(Box, {
|
|
1504
|
+
marginTop: 1,
|
|
1505
|
+
flexDirection: "column",
|
|
1506
|
+
children: (list.notes ?? []).map((note, i) => /* @__PURE__ */ jsx(Text, {
|
|
1507
|
+
color: theme.color.dim,
|
|
1508
|
+
italic: true,
|
|
1509
|
+
wrap: "wrap",
|
|
1510
|
+
children: note
|
|
1511
|
+
}, i))
|
|
1512
|
+
}) : null
|
|
1513
|
+
]
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* The pager module and `present`, which a turn handler yields as its reply:
|
|
1518
|
+
* `yield { phase: 'final', reply: pager.present(list, { open, related }) }`. A list of up to
|
|
1519
|
+
* `INLINE_LIST_ROWS` prints inline. A longer one opens the pager when no panel is open, and the
|
|
1520
|
+
* transcript then carries only its heading and a pointer; otherwise its first rows print inline.
|
|
1521
|
+
* `/list` reopens the last long list where it was left.
|
|
1522
|
+
*/
|
|
1523
|
+
function listPager() {
|
|
1524
|
+
let own;
|
|
1525
|
+
let last = null;
|
|
1526
|
+
/**
|
|
1527
|
+
* Keeps the first step as the operator left it, and returns the state to show. Only for the
|
|
1528
|
+
* list `/list` would reopen: an older list paged while a newer one landed must not take its place.
|
|
1529
|
+
*/
|
|
1530
|
+
const keep = (s) => {
|
|
1531
|
+
const first = s.steps[0];
|
|
1532
|
+
const was = last?.steps[0];
|
|
1533
|
+
if (first?.kind === "list" && was?.kind === "list" && first.list === was.list) last = {
|
|
1534
|
+
steps: [first],
|
|
1535
|
+
token: s.token
|
|
1536
|
+
};
|
|
1537
|
+
return s;
|
|
1538
|
+
};
|
|
1539
|
+
const top = (s) => s.steps[s.steps.length - 1];
|
|
1540
|
+
const replaceTop = (s, step) => ({
|
|
1541
|
+
...s,
|
|
1542
|
+
steps: [...s.steps.slice(0, -1), step]
|
|
1543
|
+
});
|
|
1544
|
+
/** Settles an async step, only while the pager still shows the step it was started for. */
|
|
1545
|
+
const settle = (ctx, token, step) => ctx.update("pager", (cur) => {
|
|
1546
|
+
const t = top(cur);
|
|
1547
|
+
return cur.token === token && t !== void 0 ? replaceTop(cur, step(t)) : cur;
|
|
1548
|
+
});
|
|
1549
|
+
const open = (s, ctx) => {
|
|
1550
|
+
const t = top(s);
|
|
1551
|
+
if (t?.kind !== "list") return s;
|
|
1552
|
+
if (t.filtering) return keep(endFilter(s));
|
|
1553
|
+
const row = cursorRow(s);
|
|
1554
|
+
const hook = t.hooks.open;
|
|
1555
|
+
if (row === void 0 || hook === void 0) return s;
|
|
1556
|
+
const base = {
|
|
1557
|
+
kind: "detail",
|
|
1558
|
+
row,
|
|
1559
|
+
hooks: t.hooks,
|
|
1560
|
+
detail: "loading",
|
|
1561
|
+
scroll: 0
|
|
1562
|
+
};
|
|
1563
|
+
let got;
|
|
1564
|
+
try {
|
|
1565
|
+
got = hook(row);
|
|
1566
|
+
} catch (e) {
|
|
1567
|
+
return push(s, {
|
|
1568
|
+
...base,
|
|
1569
|
+
detail: { error: errorText(e) }
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
if (!isPromise(got)) return push(s, {
|
|
1573
|
+
...base,
|
|
1574
|
+
detail: cleanDetail(got)
|
|
1575
|
+
});
|
|
1576
|
+
const next = push(s, base);
|
|
1577
|
+
got.then((d) => settle(ctx, next.token, (cur) => cur.kind === "detail" ? {
|
|
1578
|
+
...cur,
|
|
1579
|
+
detail: cleanDetail(d)
|
|
1580
|
+
} : cur), (e) => settle(ctx, next.token, (cur) => cur.kind === "detail" ? {
|
|
1581
|
+
...cur,
|
|
1582
|
+
detail: { error: errorText(e) }
|
|
1583
|
+
} : cur));
|
|
1584
|
+
return next;
|
|
1585
|
+
};
|
|
1586
|
+
const related = (s, ctx) => {
|
|
1587
|
+
const t = top(s);
|
|
1588
|
+
if (t?.kind === "list" && t.filtering) return keep(typeChar(s, "r"));
|
|
1589
|
+
if (t === void 0 || t.kind === "loading-related") return s;
|
|
1590
|
+
const row = cursorRow(s);
|
|
1591
|
+
const hook = t.hooks.related;
|
|
1592
|
+
if (row === void 0 || hook === void 0) return s;
|
|
1593
|
+
const hooks = t.hooks;
|
|
1594
|
+
let got;
|
|
1595
|
+
try {
|
|
1596
|
+
got = hook(row);
|
|
1597
|
+
} catch (e) {
|
|
1598
|
+
return push(s, {
|
|
1599
|
+
kind: "loading-related",
|
|
1600
|
+
row,
|
|
1601
|
+
error: errorText(e)
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
if (!isPromise(got)) return push(s, listStep(cleanList(got), hooks));
|
|
1605
|
+
const next = push(s, {
|
|
1606
|
+
kind: "loading-related",
|
|
1607
|
+
row
|
|
1608
|
+
});
|
|
1609
|
+
got.then((l) => settle(ctx, next.token, () => listStep(cleanList(l), hooks)), (e) => settle(ctx, next.token, () => ({
|
|
1610
|
+
kind: "loading-related",
|
|
1611
|
+
row,
|
|
1612
|
+
error: errorText(e)
|
|
1613
|
+
})));
|
|
1614
|
+
return next;
|
|
1615
|
+
};
|
|
1616
|
+
const vertical = (delta) => (s, ctx) => {
|
|
1617
|
+
const t = top(s);
|
|
1618
|
+
if (t?.kind === "detail" && t.detail !== "loading" && !("error" in t.detail)) return scrollDetail(s, delta, detailLayout(t.detail, ctx.size()).maxScroll);
|
|
1619
|
+
return keep(move(s, delta, pageSizeFor(ctx.size().rows)));
|
|
1620
|
+
};
|
|
1621
|
+
const page = (delta) => (s, ctx) => keep(turn(s, delta, pageSizeFor(ctx.size().rows)));
|
|
1622
|
+
return {
|
|
1623
|
+
module: defineModule("surface.list", {
|
|
1624
|
+
init(ctx) {
|
|
1625
|
+
own = ctx;
|
|
1626
|
+
return () => {
|
|
1627
|
+
own = void 0;
|
|
1628
|
+
};
|
|
1629
|
+
},
|
|
1630
|
+
commands: [{
|
|
1631
|
+
name: "list",
|
|
1632
|
+
summary: "page through the last long list",
|
|
1633
|
+
args: [],
|
|
1634
|
+
run(_args, ctx) {
|
|
1635
|
+
if (last === null) {
|
|
1636
|
+
ctx.print("nothing to page through");
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
ctx.open("pager", last);
|
|
1640
|
+
}
|
|
1641
|
+
}],
|
|
1642
|
+
renderers: { [LIST_REPLY_KIND]: ({ data, theme, size }) => /* @__PURE__ */ jsx(ListReply, {
|
|
1643
|
+
data,
|
|
1644
|
+
theme,
|
|
1645
|
+
columns: size.columns
|
|
1646
|
+
}) },
|
|
1647
|
+
panels: { pager: {
|
|
1648
|
+
view: ({ state, theme, size }) => /* @__PURE__ */ jsx(PagerView, {
|
|
1649
|
+
state,
|
|
1650
|
+
theme,
|
|
1651
|
+
size
|
|
1652
|
+
}),
|
|
1653
|
+
keymap: [
|
|
1654
|
+
{
|
|
1655
|
+
key: "up",
|
|
1656
|
+
act: "up"
|
|
1657
|
+
},
|
|
1658
|
+
{
|
|
1659
|
+
key: "down",
|
|
1660
|
+
act: "down"
|
|
1661
|
+
},
|
|
1662
|
+
{
|
|
1663
|
+
key: "left",
|
|
1664
|
+
act: "left"
|
|
1665
|
+
},
|
|
1666
|
+
{
|
|
1667
|
+
key: "right",
|
|
1668
|
+
act: "right"
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
key: "enter",
|
|
1672
|
+
act: "open"
|
|
1673
|
+
},
|
|
1674
|
+
{
|
|
1675
|
+
key: "r",
|
|
1676
|
+
act: "related"
|
|
1677
|
+
},
|
|
1678
|
+
{
|
|
1679
|
+
key: "/",
|
|
1680
|
+
act: "filter"
|
|
1681
|
+
},
|
|
1682
|
+
{
|
|
1683
|
+
key: "backspace",
|
|
1684
|
+
act: "backspace"
|
|
1685
|
+
}
|
|
1686
|
+
],
|
|
1687
|
+
on: {
|
|
1688
|
+
up: vertical(-1),
|
|
1689
|
+
down: vertical(1),
|
|
1690
|
+
left: page(-1),
|
|
1691
|
+
right: page(1),
|
|
1692
|
+
open,
|
|
1693
|
+
related,
|
|
1694
|
+
filter: (s) => {
|
|
1695
|
+
const t = top(s);
|
|
1696
|
+
return keep(t?.kind === "list" && t.filtering ? typeChar(s, "/") : startFilter(s));
|
|
1697
|
+
},
|
|
1698
|
+
backspace: (s) => keep(backspace(s))
|
|
1699
|
+
},
|
|
1700
|
+
text: (s, text) => keep(typeChar(s, text)),
|
|
1701
|
+
escape: (s) => {
|
|
1702
|
+
const prev = back(s);
|
|
1703
|
+
return prev === void 0 ? void 0 : keep(prev);
|
|
1704
|
+
}
|
|
1705
|
+
} }
|
|
1706
|
+
}),
|
|
1707
|
+
present(list, hooks = {}) {
|
|
1708
|
+
const clean = cleanList(list);
|
|
1709
|
+
if (clean.rows.length <= 10) return {
|
|
1710
|
+
kind: LIST_REPLY_KIND,
|
|
1711
|
+
data: {
|
|
1712
|
+
list: clean,
|
|
1713
|
+
paged: false
|
|
1714
|
+
}
|
|
1715
|
+
};
|
|
1716
|
+
last = pagerAt(clean, hooks);
|
|
1717
|
+
const paged = own !== void 0 && !own.panelOpen();
|
|
1718
|
+
if (paged) own?.open("pager", last);
|
|
1719
|
+
return {
|
|
1720
|
+
kind: LIST_REPLY_KIND,
|
|
1721
|
+
data: {
|
|
1722
|
+
list: clean,
|
|
1723
|
+
paged
|
|
1724
|
+
}
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
//#endregion
|
|
1731
|
+
export { CLEAR_PANE, CTRL_C_WINDOW_MS, CompositionError, DEFAULT_GROUND, EMPTY_DRAFT, EMPTY_HISTORY, FOLLOWING, HISTORY_MAX, INLINE_LIST_ROWS, LIST_REPLY_KIND, MENU_NAME_WIDTH, RESERVED_GLOBAL_KEYS, RESET_GROUND, WHEEL_ARROWS_OFF, WHEEL_ARROWS_ON, addGap, anchorPadding, answerEntry, applyInput, applySuggestion, arriveTurn, assertComposes, atEnd, back, backspace, clampSelection, commandUsage, commitEntry, completeDraft, composeModules, createApp, ctrlC, cursorRow, defaultPanelRows, defineModule, editKey, emptyTranscript, endFilter, enterRuns, groundSequence, heldAsks, insertChunk, keySpecOf, linesBelow, listPager, listStep, liveShrink, markedFlights, maskDraft, mayWalk, menuWindow, mergeCompletions, move, moveSelection, normaliseKeySpec, pageSizeFor, pageStep, pagerAt, paletteMatches, paletteQuery, paletteRows, panelOf, parseCommandLine, pasteToLine, placement, prepareSequence, prepareTerminal, push, reflowSequence, remember, resolveFullscreen, resolveKey, routeInput, sanitise, scrollBy, scrollDetail, sendTurn, setPanel, settle, startFilter, turn, typeChar, viewTop, visibleRows, walk, wordLeft, wordRight };
|