@aragon-agent/cli 0.6.3 → 0.6.7
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/CHANGELOG.md +142 -0
- package/README.md +203 -13
- package/dist/agent/controller.d.ts +158 -2
- package/dist/agent/controller.js +400 -1
- package/dist/agent/reducer.d.ts +102 -19
- package/dist/agent/reducer.js +162 -71
- package/dist/agent/system-prompt.d.ts +14 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/agent/usage.d.ts +34 -0
- package/dist/agent/usage.js +43 -0
- package/dist/cli.js +132 -23
- package/dist/commands/builtins.js +101 -1
- package/dist/compaction/child.js +5 -0
- package/dist/compaction/command.js +14 -1
- package/dist/compaction/compactor.d.ts +94 -24
- package/dist/compaction/compactor.js +247 -98
- package/dist/compaction/context-command.d.ts +33 -0
- package/dist/compaction/context-command.js +166 -0
- package/dist/compaction/meter.d.ts +251 -0
- package/dist/compaction/meter.js +428 -0
- package/dist/compaction/pressure.d.ts +6 -0
- package/dist/compaction/pressure.js +4 -0
- package/dist/compaction/types.d.ts +77 -0
- package/dist/compaction/wiring.d.ts +56 -0
- package/dist/compaction/wiring.js +107 -25
- package/dist/config/cli-commands.js +5 -0
- package/dist/config/env.d.ts +1 -1
- package/dist/config/env.js +63 -2
- package/dist/config/load.d.ts +28 -0
- package/dist/config/load.js +83 -1
- package/dist/config/schema.d.ts +129 -0
- package/dist/config/schema.js +69 -0
- package/dist/config/store.js +47 -1
- package/dist/exec/events.d.ts +138 -1
- package/dist/exec/events.js +42 -0
- package/dist/exec/index.js +43 -6
- package/dist/exec/progress-reporter.d.ts +23 -0
- package/dist/exec/progress-reporter.js +88 -0
- package/dist/exec/runner.d.ts +34 -0
- package/dist/exec/runner.js +137 -25
- package/dist/fast/reviewer.d.ts +19 -1
- package/dist/fast/reviewer.js +22 -0
- package/dist/fast/types.d.ts +15 -0
- package/dist/fast/wiring.d.ts +10 -1
- package/dist/fast/wiring.js +18 -1
- package/dist/input/limits.d.ts +72 -0
- package/dist/input/limits.js +63 -0
- package/dist/input/paste-parse.d.ts +71 -0
- package/dist/input/paste-parse.js +95 -0
- package/dist/input/stdin-filter.d.ts +81 -0
- package/dist/input/stdin-filter.js +379 -0
- package/dist/logging/install.d.ts +2 -0
- package/dist/logging/install.js +53 -1
- package/dist/proc/classify.d.ts +52 -0
- package/dist/proc/classify.js +143 -0
- package/dist/proc/kill-tree.d.ts +42 -0
- package/dist/proc/kill-tree.js +119 -0
- package/dist/proc/limits.d.ts +84 -0
- package/dist/proc/limits.js +84 -0
- package/dist/proc/log-ring.d.ts +68 -0
- package/dist/proc/log-ring.js +102 -0
- package/dist/proc/prompt.d.ts +24 -0
- package/dist/proc/prompt.js +49 -0
- package/dist/proc/readiness.d.ts +102 -0
- package/dist/proc/readiness.js +219 -0
- package/dist/proc/supervisor.d.ts +103 -0
- package/dist/proc/supervisor.js +465 -0
- package/dist/proc/types.d.ts +175 -0
- package/dist/proc/types.js +15 -0
- package/dist/session/persist.js +36 -1
- package/dist/todo/limits.d.ts +4 -17
- package/dist/todo/limits.js +4 -17
- package/dist/todo/panel-rows.d.ts +8 -6
- package/dist/todo/panel-rows.js +19 -6
- package/dist/tools/bash-tool.d.ts +51 -2
- package/dist/tools/bash-tool.js +321 -51
- package/dist/tools/fs-tools.d.ts +17 -0
- package/dist/tools/index.d.ts +55 -2
- package/dist/tools/index.js +39 -1
- package/dist/tools/proc-tools.d.ts +25 -0
- package/dist/tools/proc-tools.js +102 -0
- package/dist/ui/App.d.ts +15 -2
- package/dist/ui/App.js +330 -69
- package/dist/ui/AutocompletePopup.d.ts +3 -1
- package/dist/ui/AutocompletePopup.js +9 -11
- package/dist/ui/BottomStatusRow.d.ts +1 -1
- package/dist/ui/Composer.d.ts +56 -1
- package/dist/ui/Composer.js +50 -6
- package/dist/ui/PromptInput.d.ts +52 -3
- package/dist/ui/PromptInput.js +201 -55
- package/dist/ui/StatusBar.d.ts +40 -15
- package/dist/ui/StatusBar.js +51 -9
- package/dist/ui/TeamPanel.d.ts +3 -1
- package/dist/ui/TeamPanel.js +13 -9
- package/dist/ui/TodoPanel.d.ts +5 -5
- package/dist/ui/TodoPanel.js +56 -24
- package/dist/ui/Transcript.js +29 -1
- package/dist/ui/UpdateLine.d.ts +1 -1
- package/dist/ui/capabilities.js +5 -5
- package/dist/ui/composer-limits.d.ts +31 -0
- package/dist/ui/composer-limits.js +50 -0
- package/dist/ui/composer-rows.d.ts +73 -0
- package/dist/ui/composer-rows.js +201 -0
- package/dist/ui/editor-reducer.d.ts +37 -0
- package/dist/ui/editor-reducer.js +88 -12
- package/dist/ui/entries/ServiceCard.d.ts +66 -0
- package/dist/ui/entries/ServiceCard.js +116 -0
- package/dist/ui/entries/UserEntry.d.ts +17 -3
- package/dist/ui/entries/UserEntry.js +34 -5
- package/dist/ui/exit-snapshot.d.ts +9 -0
- package/dist/ui/layout/ScrollViewport.d.ts +1 -1
- package/dist/ui/layout/autocomplete.d.ts +18 -0
- package/dist/ui/layout/autocomplete.js +26 -0
- package/dist/ui/layout/budget.d.ts +34 -6
- package/dist/ui/layout/budget.js +32 -7
- package/dist/ui/layout/rail.d.ts +4 -53
- package/dist/ui/layout/rail.js +10 -62
- package/dist/ui/layout/team-panel.d.ts +18 -0
- package/dist/ui/layout/team-panel.js +22 -0
- package/dist/ui/layout/todo-layout.d.ts +24 -0
- package/dist/ui/layout/todo-layout.js +27 -0
- package/dist/ui/layout/virtual-window.js +51 -1
- package/dist/ui/overlays/HelpOverlay.js +3 -0
- package/dist/ui/overlays/PlanReviewOverlay.js +10 -2
- package/dist/ui/overlays/QuestionOverlay.js +5 -1
- package/dist/ui/overlays/SettingsScreen.js +14 -1
- package/dist/ui/paste-frames.d.ts +87 -0
- package/dist/ui/paste-frames.js +112 -0
- package/dist/ui/paste-tokens.d.ts +90 -0
- package/dist/ui/paste-tokens.js +147 -0
- package/dist/ui/screen.d.ts +16 -0
- package/dist/ui/screen.js +45 -2
- package/dist/ui/selection/selection-controller.d.ts +1 -1
- package/dist/ui/transcript-text.js +55 -2
- package/dist/ui/use-wheel-routing.d.ts +1 -1
- package/package.json +2 -2
- package/dist/input/stdin-mouse-filter.d.ts +0 -47
- package/dist/input/stdin-mouse-filter.js +0 -153
package/dist/ui/Composer.d.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import React from 'react';
|
|
18
18
|
import type { Theme } from './theme.js';
|
|
19
19
|
import type { TermCapabilities } from './capabilities.js';
|
|
20
|
+
import { pickGlyphs } from './glyphs.js';
|
|
20
21
|
import { type CommandOption } from './PromptInput.js';
|
|
21
22
|
import { type AgentMode } from '../agent/agent-mode.js';
|
|
22
23
|
export interface ComposerProps {
|
|
@@ -48,6 +49,8 @@ export interface ComposerProps {
|
|
|
48
49
|
*/
|
|
49
50
|
modeToggleKey?: string;
|
|
50
51
|
popupMaxRows?: number;
|
|
52
|
+
popupMaxHeight?: number;
|
|
53
|
+
onPopupRowsChange?: (rows: number) => void;
|
|
51
54
|
/**
|
|
52
55
|
* Rows of newer output below the viewport, forwarded to the input row's chip
|
|
53
56
|
* (tui-selection-and-scroll-follow §4.2 / G7).
|
|
@@ -60,9 +63,61 @@ export interface ComposerProps {
|
|
|
60
63
|
* un-faded in `hintText`.
|
|
61
64
|
*/
|
|
62
65
|
scrolledLines?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Live background services (background-service-supervision §3.8), 0 when none.
|
|
68
|
+
*
|
|
69
|
+
* IT IS STATE, NOT A FADEABLE HINT, exactly like `scrolledLines` above: the
|
|
70
|
+
* clause it adds is the only visible way to stop something, and `hintText`
|
|
71
|
+
* below keeps it out of the faded form for the same reason `esc abort` is kept
|
|
72
|
+
* there.
|
|
73
|
+
*/
|
|
74
|
+
services?: number;
|
|
75
|
+
/**
|
|
76
|
+
* The composer's DRAFT row count, forwarded upward (tui-paste-handling
|
|
77
|
+
* section 5.5).
|
|
78
|
+
*
|
|
79
|
+
* `hasDraft` stays here, because the border reacts to it and routing that
|
|
80
|
+
* through `App` would re-render the transcript on the first keystroke of every
|
|
81
|
+
* message. The row count is the opposite case: `layout/budget.ts` needs it, so
|
|
82
|
+
* it has to reach `App` — and it changes on a line break rather than on a
|
|
83
|
+
* character, which is what keeps the re-render rate the same (R-6).
|
|
84
|
+
*/
|
|
85
|
+
onDraftRows?: (rows: number) => void;
|
|
86
|
+
/** Paste refusals from `PromptInput`, routed to `App`'s notice dispatch. */
|
|
87
|
+
onNotice?: (level: 'warn' | 'error', text: string) => void;
|
|
63
88
|
theme: Theme;
|
|
64
89
|
caps: TermCapabilities;
|
|
65
90
|
onSubmit: (text: string) => void;
|
|
66
91
|
onHelp?: () => void;
|
|
67
92
|
}
|
|
68
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Build the hint row.
|
|
95
|
+
*
|
|
96
|
+
* The `running` branch is NEVER abbreviated, no matter how experienced the user
|
|
97
|
+
* is. Once the status bar's hint cluster was removed, the composer became the
|
|
98
|
+
* only place that names the abort key — and fading that out after eight
|
|
99
|
+
* submissions would leave a long-running task with no visible way to stop it.
|
|
100
|
+
* Progressive disclosure is for teaching discoverable things, not for retiring
|
|
101
|
+
* an emergency exit (§4.6 / A-14 / M-10b).
|
|
102
|
+
*/
|
|
103
|
+
declare function hintText(opts: {
|
|
104
|
+
running: boolean;
|
|
105
|
+
submitCount: number;
|
|
106
|
+
glyphs: ReturnType<typeof pickGlyphs>;
|
|
107
|
+
mode: AgentMode;
|
|
108
|
+
toggleKey: string;
|
|
109
|
+
/** Live background services; 0 leaves both rows byte-identical to today's. */
|
|
110
|
+
services: number;
|
|
111
|
+
}): string;
|
|
112
|
+
/**
|
|
113
|
+
* `hintText`, for the interrupt-ladder tests (AC-43).
|
|
114
|
+
*
|
|
115
|
+
* EXPORTED UNDER A TEST-FACING NAME rather than exporting `hintText` itself, so
|
|
116
|
+
* the private helper stays private in the module graph and nothing in `src/`
|
|
117
|
+
* grows a second caller by accident. The row is the ONLY visible way to stop a
|
|
118
|
+
* process the agent left running, so it is worth asserting on directly rather
|
|
119
|
+
* than fishing the string out of a rendered frame.
|
|
120
|
+
*/
|
|
121
|
+
export declare const hintTextForTest: typeof hintText;
|
|
122
|
+
export declare function Composer({ isActive, running, history, commands, cwd, showHint, submitCount, hintsEnabled, agentMode, services, modeToggleKey, popupMaxRows, popupMaxHeight, onPopupRowsChange, scrolledLines, onDraftRows, onNotice, theme, caps, onSubmit, onHelp, }: ComposerProps): React.ReactElement;
|
|
123
|
+
export {};
|
package/dist/ui/Composer.js
CHANGED
|
@@ -32,9 +32,28 @@ import { MODE_LABEL, MODE_TOGGLE_KEYS, nextMode } from '../agent/agent-mode.js';
|
|
|
32
32
|
* Progressive disclosure is for teaching discoverable things, not for retiring
|
|
33
33
|
* an emergency exit (§4.6 / A-14 / M-10b).
|
|
34
34
|
*/
|
|
35
|
-
function hintText(
|
|
35
|
+
function hintText(opts) {
|
|
36
|
+
const { running, submitCount, glyphs, mode, toggleKey, services } = opts;
|
|
36
37
|
const dot = ` ${glyphs.midDot} `;
|
|
38
|
+
// THE EXIT CLAUSE SURVIVES IN BOTH ROWS (P1-4). The obvious edit here is to
|
|
39
|
+
// replace `ctrl+c*2 exit` with `ctrl+c stop N` — and that would delete the
|
|
40
|
+
// only visible way to quit at exactly the moment Ctrl+C stops meaning "quit",
|
|
41
|
+
// on the screen a user reaches when something has already gone wrong. Both
|
|
42
|
+
// clauses are shown; the row is `wrap="truncate"` and they are ordered
|
|
43
|
+
// most-urgent-first, so a terminal too narrow for all of them drops the exit
|
|
44
|
+
// clause last rather than the abort key.
|
|
37
45
|
if (running) {
|
|
46
|
+
if (services > 0) {
|
|
47
|
+
return [
|
|
48
|
+
`${glyphs.enterKey} steer`,
|
|
49
|
+
// Rung two is named because the whole point of the ladder is that the
|
|
50
|
+
// first press can fail; a user who does not know there is a second press
|
|
51
|
+
// is left exactly where the reported screenshot left them.
|
|
52
|
+
`esc abort${glyphs.times}2 force`,
|
|
53
|
+
`ctrl+c stop ${services}`,
|
|
54
|
+
`ctrl+c${glyphs.times}2 exit`,
|
|
55
|
+
].join(dot);
|
|
56
|
+
}
|
|
38
57
|
return [`${glyphs.enterKey} steer`, 'esc abort', `ctrl+c${glyphs.times}2 exit`].join(dot);
|
|
39
58
|
}
|
|
40
59
|
// The hint names the DESTINATION, not the current state — the correct label
|
|
@@ -45,9 +64,14 @@ function hintText(running, submitCount, glyphs, mode, toggleKey) {
|
|
|
45
64
|
// retire things the user has demonstrably learned, and `submitCount` counts
|
|
46
65
|
// submissions — a user can send fifty messages without ever discovering
|
|
47
66
|
// Shift+Tab. Same argument that keeps `esc abort` un-faded above.
|
|
67
|
+
// NEVER FADED, for the reason the props doc gives: it is the only visible way
|
|
68
|
+
// to stop a process the agent left running, and progressive disclosure retires
|
|
69
|
+
// things the user has demonstrably learned — not emergency exits.
|
|
70
|
+
const stop = services > 0 ? [`ctrl+c stop ${services}`] : [];
|
|
48
71
|
if (submitCount >= HINT_FADE_AFTER)
|
|
49
|
-
return [toggle, '? help'].join(dot);
|
|
72
|
+
return [...stop, toggle, '? help'].join(dot);
|
|
50
73
|
return [
|
|
74
|
+
...stop,
|
|
51
75
|
`${glyphs.enterKey} send`,
|
|
52
76
|
`${glyphs.shiftEnter} newline`,
|
|
53
77
|
'/ commands',
|
|
@@ -56,9 +80,22 @@ function hintText(running, submitCount, glyphs, mode, toggleKey) {
|
|
|
56
80
|
'? help',
|
|
57
81
|
].join(dot);
|
|
58
82
|
}
|
|
59
|
-
|
|
83
|
+
/**
|
|
84
|
+
* `hintText`, for the interrupt-ladder tests (AC-43).
|
|
85
|
+
*
|
|
86
|
+
* EXPORTED UNDER A TEST-FACING NAME rather than exporting `hintText` itself, so
|
|
87
|
+
* the private helper stays private in the module graph and nothing in `src/`
|
|
88
|
+
* grows a second caller by accident. The row is the ONLY visible way to stop a
|
|
89
|
+
* process the agent left running, so it is worth asserting on directly rather
|
|
90
|
+
* than fishing the string out of a rendered frame.
|
|
91
|
+
*/
|
|
92
|
+
export const hintTextForTest = hintText;
|
|
93
|
+
export function Composer({ isActive, running, history, commands, cwd, showHint, submitCount, hintsEnabled, agentMode, services = 0, modeToggleKey = MODE_TOGGLE_KEYS.primary, popupMaxRows, popupMaxHeight, onPopupRowsChange, scrolledLines = 0, onDraftRows, onNotice, theme, caps, onSubmit, onHelp, }) {
|
|
60
94
|
const [hasDraft, setHasDraft] = useState(false);
|
|
61
|
-
const onDraftChange = useCallback((next) =>
|
|
95
|
+
const onDraftChange = useCallback((next) => {
|
|
96
|
+
setHasDraft(next.hasDraft);
|
|
97
|
+
onDraftRows?.(next.rows);
|
|
98
|
+
}, [onDraftRows]);
|
|
62
99
|
// Plan mode inserts ONE branch, above `draft` and below `running`. The
|
|
63
100
|
// ordering is load-bearing: an in-flight run's border must stay `toolRunning`,
|
|
64
101
|
// because "is it running" is more urgent than "which mode".
|
|
@@ -73,8 +110,15 @@ export function Composer({ isActive, running, history, commands, cwd, showHint,
|
|
|
73
110
|
: theme.idleBorder ?? theme.border;
|
|
74
111
|
const glyphs = pickGlyphs(caps);
|
|
75
112
|
const visible = showHint && hintsEnabled;
|
|
76
|
-
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsx(PromptInput, { isActive: isActive, running: running, history: history, commands: commands, cwd: cwd, theme: theme, caps: caps, popupMaxRows: popupMaxRows, onSubmit: onSubmit, onHelp: onHelp, agentMode: agentMode, bordered: { color: borderColor }, onDraftChange: onDraftChange, scrolledLines: scrolledLines }), visible && (
|
|
113
|
+
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsx(PromptInput, { isActive: isActive, running: running, history: history, commands: commands, cwd: cwd, theme: theme, caps: caps, popupMaxRows: popupMaxRows, popupMaxHeight: popupMaxHeight, onPopupRowsChange: onPopupRowsChange, onSubmit: onSubmit, onHelp: onHelp, agentMode: agentMode, bordered: { color: borderColor }, onDraftChange: onDraftChange, onNotice: onNotice, scrolledLines: scrolledLines }), visible && (
|
|
77
114
|
// The chip rides the EXISTING hint row rather than adding one, so it
|
|
78
115
|
// costs zero rows on a 24-row terminal.
|
|
79
|
-
_jsxs(Box, { flexDirection: "row", flexShrink: 0, children: [_jsx(Text, { children: ' ' }), _jsx(ModeChip, { mode: agentMode, theme: theme, caps: caps }), _jsx(Text, { wrap: "truncate", color: theme.hintFg ?? theme.muted, children: hintText(
|
|
116
|
+
_jsxs(Box, { flexDirection: "row", flexShrink: 0, children: [_jsx(Text, { children: ' ' }), _jsx(ModeChip, { mode: agentMode, theme: theme, caps: caps }), _jsx(Text, { wrap: "truncate", color: theme.hintFg ?? theme.muted, children: hintText({
|
|
117
|
+
running,
|
|
118
|
+
submitCount,
|
|
119
|
+
glyphs,
|
|
120
|
+
mode: agentMode,
|
|
121
|
+
toggleKey: modeToggleKey,
|
|
122
|
+
services,
|
|
123
|
+
}) })] }))] }));
|
|
80
124
|
}
|
package/dist/ui/PromptInput.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ import type { TermCapabilities } from './capabilities.js';
|
|
|
24
24
|
import { pickGlyphs } from './glyphs.js';
|
|
25
25
|
import { type EditIntent } from '../input/keymap.js';
|
|
26
26
|
import { type Suggestion } from './AutocompletePopup.js';
|
|
27
|
+
import { type EditorState, type InputSegment } from './editor-reducer.js';
|
|
27
28
|
import type { AgentMode } from '../agent/agent-mode.js';
|
|
28
29
|
export interface CommandOption {
|
|
29
30
|
name: string;
|
|
@@ -47,6 +48,9 @@ interface PromptInputProps {
|
|
|
47
48
|
* `App` derives this from `budget.ts` so the popup shrinks instead.
|
|
48
49
|
*/
|
|
49
50
|
popupMaxRows?: number;
|
|
51
|
+
/** Total menu rows, including borders and the overflow hint. */
|
|
52
|
+
popupMaxHeight?: number;
|
|
53
|
+
onPopupRowsChange?: (rows: number) => void;
|
|
50
54
|
onSubmit: (text: string) => void;
|
|
51
55
|
onHelp?: () => void;
|
|
52
56
|
/**
|
|
@@ -64,8 +68,30 @@ interface PromptInputProps {
|
|
|
64
68
|
bordered?: {
|
|
65
69
|
color?: string;
|
|
66
70
|
};
|
|
67
|
-
/**
|
|
68
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Fires when the draft flips empty ⇄ non-empty (for the border color) OR when
|
|
73
|
+
* its ROW COUNT changes (for `layout/budget.ts`) — never per keystroke.
|
|
74
|
+
*
|
|
75
|
+
* Widened from `(hasDraft: boolean)` by tui-paste-handling section 5.5. The row
|
|
76
|
+
* count is what makes I-8 true: the composer's rendered height and the
|
|
77
|
+
* `draftRows` handed to `viewportRows` have to be the same number, and the only
|
|
78
|
+
* component that knows the first one is this one. It is reported ALREADY
|
|
79
|
+
* CLAMPED, so a 218-line draft and a 400-line draft report the same value and
|
|
80
|
+
* the transcript stops moving once the composer is full (R-12).
|
|
81
|
+
*/
|
|
82
|
+
onDraftChange?: (state: {
|
|
83
|
+
hasDraft: boolean;
|
|
84
|
+
rows: number;
|
|
85
|
+
}) => void;
|
|
86
|
+
/**
|
|
87
|
+
* Tell the user a paste was refused (D-11 / P1-4).
|
|
88
|
+
*
|
|
89
|
+
* `PASTE_DRAFT_MAX_BYTES` and `PASTE_MAX_BLOCKS` cannot be enforced in the
|
|
90
|
+
* filter (it does not know the draft) and cannot be enforced in the reducer (it
|
|
91
|
+
* is pure, so it can neither raise a toast nor say why it refused). This
|
|
92
|
+
* component is the only place that holds both facts.
|
|
93
|
+
*/
|
|
94
|
+
onNotice?: (level: 'warn' | 'error', text: string) => void;
|
|
69
95
|
/**
|
|
70
96
|
* Rows of newer output hidden BELOW the viewport — `ScrollViewport`'s offset
|
|
71
97
|
* (tui-selection-and-scroll-follow §4.2 / G7).
|
|
@@ -99,6 +125,29 @@ export interface ScrollChip {
|
|
|
99
125
|
* scan fails the build if that rule is broken here.
|
|
100
126
|
*/
|
|
101
127
|
export declare function scrollChip(scrolledLines: number, cols: number, glyphs: ReturnType<typeof pickGlyphs>): ScrollChip | null;
|
|
128
|
+
export interface OverflowChip {
|
|
129
|
+
text: string;
|
|
130
|
+
/** FIXED width, so a number gaining a digit cannot re-wrap the draft. */
|
|
131
|
+
cells: number;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Build the "there is more draft above / below this window" indicator, or `null`
|
|
135
|
+
* when the whole draft is on screen.
|
|
136
|
+
*
|
|
137
|
+
* Every glyph comes from `pickGlyphs` and none from a literal (I-5): on a legacy
|
|
138
|
+
* console this renders `^3 v7` rather than mojibake, and `glyphs.test.ts`'s
|
|
139
|
+
* static scan fails the build if that rule is broken here.
|
|
140
|
+
*/
|
|
141
|
+
export declare function overflowChip(hiddenAbove: number, hiddenBelow: number, glyphs: ReturnType<typeof pickGlyphs>): OverflowChip | null;
|
|
142
|
+
/**
|
|
143
|
+
* Would these segments push the draft past a limit? Returns the refusal text, or
|
|
144
|
+
* `null` when the paste is allowed.
|
|
145
|
+
*
|
|
146
|
+
* Checked BEFORE `dispatch`, so a refusal dispatches nothing at all and the draft
|
|
147
|
+
* is left byte-for-byte unchanged (AC-9). The one-dispatch-per-key rule is
|
|
148
|
+
* unaffected for the same reason.
|
|
149
|
+
*/
|
|
150
|
+
export declare function draftLimitRefusal(segments: InputSegment[], editor: Pick<EditorState, 'pastes'>): string | null;
|
|
102
151
|
/** Apply an edit intent as a pure buffer/cursor transform. */
|
|
103
152
|
export declare function applyEdit(buffer: string, cursor: number, intent: EditIntent): {
|
|
104
153
|
buffer: string;
|
|
@@ -127,5 +176,5 @@ export declare function fileTokenAt(buffer: string, cursor: number): {
|
|
|
127
176
|
end: number;
|
|
128
177
|
query: string;
|
|
129
178
|
} | null;
|
|
130
|
-
export declare function PromptInput({ isActive, running, history, commands, cwd, theme, caps, popupMaxRows, agentMode, onSubmit, onHelp, bordered, onDraftChange, scrolledLines, }: PromptInputProps): React.ReactElement;
|
|
179
|
+
export declare function PromptInput({ isActive, running, history, commands, cwd, theme, caps, popupMaxRows, popupMaxHeight, onPopupRowsChange, agentMode, onSubmit, onHelp, bordered, onDraftChange, onNotice, scrolledLines, }: PromptInputProps): React.ReactElement;
|
|
131
180
|
export {};
|
package/dist/ui/PromptInput.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* PromptInput (spec §3.6 / §3.10) — a controlled multiline input on Ink's
|
|
4
4
|
* `useInput` with: full line editing (Home/End, word jump/delete, kill-line via
|
|
@@ -19,13 +19,19 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
19
19
|
* stays a plain `useState` because it is written from the debounced glob effect
|
|
20
20
|
* rather than from a key.
|
|
21
21
|
*/
|
|
22
|
-
import { useEffect, useReducer, useRef, useState } from 'react';
|
|
22
|
+
import { useEffect, useLayoutEffect, useReducer, useRef, useState } from 'react';
|
|
23
23
|
import { Box, Text, useInput, useStdout } from 'ink';
|
|
24
24
|
import { glob as tinyGlob } from 'tinyglobby';
|
|
25
25
|
import { pickGlyphs } from './glyphs.js';
|
|
26
26
|
import { recognize } from '../input/keymap.js';
|
|
27
27
|
import { AutocompletePopup } from './AutocompletePopup.js';
|
|
28
|
-
import {
|
|
28
|
+
import { buildAutocompleteLayout } from './layout/autocomplete.js';
|
|
29
|
+
import { editorReducer, INITIAL_EDITOR_STATE, } from './editor-reducer.js';
|
|
30
|
+
import { hasPasteFrame, splitPasteFrames } from './paste-frames.js';
|
|
31
|
+
import { allocatePasteId, expandPastes, expandRangeOverTokens } from './paste-tokens.js';
|
|
32
|
+
import { draftMaxRows } from './composer-limits.js';
|
|
33
|
+
import { layoutComposer, splitRowAtColumn } from './composer-rows.js';
|
|
34
|
+
import { PASTE_DRAFT_MAX_BYTES, PASTE_MAX_BLOCKS, formatPasteSize, } from '../input/limits.js';
|
|
29
35
|
// ---------------------------------------------------------------------------
|
|
30
36
|
// The scroll chip (§4.2)
|
|
31
37
|
// ---------------------------------------------------------------------------
|
|
@@ -63,6 +69,63 @@ export function scrollChip(scrolledLines, cols, glyphs) {
|
|
|
63
69
|
};
|
|
64
70
|
}
|
|
65
71
|
// ---------------------------------------------------------------------------
|
|
72
|
+
// The draft overflow indicator (section 5.5)
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
/** `^3 v7` at its widest, so the cell is a CONSTANT (the P2-4 discipline). */
|
|
75
|
+
const OVERFLOW_CELLS = 12;
|
|
76
|
+
const OVERFLOW_MAX_N = 999;
|
|
77
|
+
/**
|
|
78
|
+
* Build the "there is more draft above / below this window" indicator, or `null`
|
|
79
|
+
* when the whole draft is on screen.
|
|
80
|
+
*
|
|
81
|
+
* Every glyph comes from `pickGlyphs` and none from a literal (I-5): on a legacy
|
|
82
|
+
* console this renders `^3 v7` rather than mojibake, and `glyphs.test.ts`'s
|
|
83
|
+
* static scan fails the build if that rule is broken here.
|
|
84
|
+
*/
|
|
85
|
+
export function overflowChip(hiddenAbove, hiddenBelow, glyphs) {
|
|
86
|
+
if (hiddenAbove <= 0 && hiddenBelow <= 0)
|
|
87
|
+
return null;
|
|
88
|
+
const show = (n) => (n > OVERFLOW_MAX_N ? `${OVERFLOW_MAX_N}+` : String(n));
|
|
89
|
+
const parts = [];
|
|
90
|
+
if (hiddenAbove > 0)
|
|
91
|
+
parts.push(`${glyphs.arrowUp}${show(hiddenAbove)}`);
|
|
92
|
+
if (hiddenBelow > 0)
|
|
93
|
+
parts.push(`${glyphs.arrowDown}${show(hiddenBelow)}`);
|
|
94
|
+
return { text: parts.join(' '), cells: OVERFLOW_CELLS };
|
|
95
|
+
}
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Draft-level paste limits (section 5.3, the P1-4 enforcement point)
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
/**
|
|
100
|
+
* Would these segments push the draft past a limit? Returns the refusal text, or
|
|
101
|
+
* `null` when the paste is allowed.
|
|
102
|
+
*
|
|
103
|
+
* Checked BEFORE `dispatch`, so a refusal dispatches nothing at all and the draft
|
|
104
|
+
* is left byte-for-byte unchanged (AC-9). The one-dispatch-per-key rule is
|
|
105
|
+
* unaffected for the same reason.
|
|
106
|
+
*/
|
|
107
|
+
export function draftLimitRefusal(segments, editor) {
|
|
108
|
+
let liveBytes = 0;
|
|
109
|
+
for (const record of editor.pastes.values())
|
|
110
|
+
liveBytes += Buffer.byteLength(record.text, 'utf8');
|
|
111
|
+
let blocks = editor.pastes.size;
|
|
112
|
+
for (const segment of segments) {
|
|
113
|
+
if (segment.kind !== 'paste')
|
|
114
|
+
continue;
|
|
115
|
+
liveBytes += Buffer.byteLength(segment.text, 'utf8');
|
|
116
|
+
blocks += 1;
|
|
117
|
+
}
|
|
118
|
+
if (blocks > PASTE_MAX_BLOCKS) {
|
|
119
|
+
return (`Too many pasted blocks in one message (limit is ${PASTE_MAX_BLOCKS}). ` +
|
|
120
|
+
'Nothing was inserted.');
|
|
121
|
+
}
|
|
122
|
+
if (liveBytes > PASTE_DRAFT_MAX_BYTES) {
|
|
123
|
+
return (`Pasted content in this message would reach ${formatPasteSize(liveBytes)}; ` +
|
|
124
|
+
`limit is ${formatPasteSize(PASTE_DRAFT_MAX_BYTES)}. Nothing was inserted.`);
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
66
129
|
// Pure editing helpers (unit-tested in input.test.ts)
|
|
67
130
|
// ---------------------------------------------------------------------------
|
|
68
131
|
const isSpace = (ch) => !!ch && /\s/.test(ch);
|
|
@@ -90,9 +153,25 @@ function nextWord(buffer, cursor) {
|
|
|
90
153
|
i += 1;
|
|
91
154
|
return i;
|
|
92
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Delete `[from, to)`, first growing the range over any token it clips (G5 /
|
|
158
|
+
* D-8).
|
|
159
|
+
*
|
|
160
|
+
* A no-op growth on a buffer with no tokens, so every pre-existing assertion in
|
|
161
|
+
* `input.test.ts` holds byte-for-byte. With a token present it is what stops
|
|
162
|
+
* `Ctrl+W` / `Ctrl+U` / `Ctrl+K` leaving half a label behind — which would be a
|
|
163
|
+
* label the user then sends INSTEAD of the text it stands for.
|
|
164
|
+
*/
|
|
165
|
+
function killRange(buffer, from, to) {
|
|
166
|
+
const range = expandRangeOverTokens(buffer, from, to);
|
|
167
|
+
return { buffer: buffer.slice(0, range.from) + buffer.slice(range.to), cursor: range.from };
|
|
168
|
+
}
|
|
93
169
|
/** Apply an edit intent as a pure buffer/cursor transform. */
|
|
94
170
|
export function applyEdit(buffer, cursor, intent) {
|
|
95
171
|
switch (intent) {
|
|
172
|
+
// Cursor MOVEMENT is deliberately not token-aware (D-8): arrows, Home/End
|
|
173
|
+
// and word jumps step by character, because re-deriving `moveVertical` around
|
|
174
|
+
// a synthetic unit buys a correctness the user cannot notice.
|
|
96
175
|
case 'home':
|
|
97
176
|
return { buffer, cursor: lineStart(buffer, cursor) };
|
|
98
177
|
case 'end':
|
|
@@ -101,18 +180,12 @@ export function applyEdit(buffer, cursor, intent) {
|
|
|
101
180
|
return { buffer, cursor: prevWord(buffer, cursor) };
|
|
102
181
|
case 'wordRight':
|
|
103
182
|
return { buffer, cursor: nextWord(buffer, cursor) };
|
|
104
|
-
case 'deleteWordBack':
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
case '
|
|
109
|
-
|
|
110
|
-
return { buffer: buffer.slice(0, start) + buffer.slice(cursor), cursor: start };
|
|
111
|
-
}
|
|
112
|
-
case 'killToEnd': {
|
|
113
|
-
const end = lineEnd(buffer, cursor);
|
|
114
|
-
return { buffer: buffer.slice(0, cursor) + buffer.slice(end), cursor };
|
|
115
|
-
}
|
|
183
|
+
case 'deleteWordBack':
|
|
184
|
+
return killRange(buffer, prevWord(buffer, cursor), cursor);
|
|
185
|
+
case 'killToStart':
|
|
186
|
+
return killRange(buffer, lineStart(buffer, cursor), cursor);
|
|
187
|
+
case 'killToEnd':
|
|
188
|
+
return killRange(buffer, cursor, lineEnd(buffer, cursor));
|
|
116
189
|
}
|
|
117
190
|
}
|
|
118
191
|
/**
|
|
@@ -174,7 +247,7 @@ function isControlSeq(input) {
|
|
|
174
247
|
// ---------------------------------------------------------------------------
|
|
175
248
|
// Component
|
|
176
249
|
// ---------------------------------------------------------------------------
|
|
177
|
-
export function PromptInput({ isActive, running, history, commands, cwd, theme, caps, popupMaxRows, agentMode, onSubmit, onHelp, bordered, onDraftChange, scrolledLines = 0, }) {
|
|
250
|
+
export function PromptInput({ isActive, running, history, commands, cwd, theme, caps, popupMaxRows, popupMaxHeight, onPopupRowsChange, agentMode, onSubmit, onHelp, bordered, onDraftChange, onNotice, scrolledLines = 0, }) {
|
|
178
251
|
const { stdout } = useStdout();
|
|
179
252
|
const [editor, dispatch] = useReducer(editorReducer, INITIAL_EDITOR_STATE);
|
|
180
253
|
const { buffer, cursor, historyIndex, dismissed, sel } = editor;
|
|
@@ -191,6 +264,19 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
191
264
|
? fileMatches.map((p) => ({ label: p }))
|
|
192
265
|
: [];
|
|
193
266
|
const clampedSel = popupItems.length > 0 ? Math.min(sel, popupItems.length - 1) : 0;
|
|
267
|
+
const popupLayout = buildAutocompleteLayout({
|
|
268
|
+
itemCount: isActive ? popupItems.length : 0,
|
|
269
|
+
selected: clampedSel,
|
|
270
|
+
maxRows: popupMaxRows,
|
|
271
|
+
maxHeight: popupMaxHeight,
|
|
272
|
+
});
|
|
273
|
+
const popupVisible = isActive && popupLayout.rowCount > 0;
|
|
274
|
+
// Report on every mount, including zero. Cleanup belongs to a separate
|
|
275
|
+
// effect so changing height does not publish an intermediate zero budget.
|
|
276
|
+
useLayoutEffect(() => {
|
|
277
|
+
onPopupRowsChange?.(popupLayout.rowCount);
|
|
278
|
+
}, [onPopupRowsChange, popupLayout.rowCount]);
|
|
279
|
+
useLayoutEffect(() => () => onPopupRowsChange?.(0), [onPopupRowsChange]);
|
|
194
280
|
// --- Debounced `@file` scan. ---------------------------------------------
|
|
195
281
|
useEffect(() => {
|
|
196
282
|
if (fileQuery === null || fileQuery.length < 1) {
|
|
@@ -221,18 +307,62 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
221
307
|
clearTimeout(id);
|
|
222
308
|
};
|
|
223
309
|
}, [fileQuery, cwd]);
|
|
224
|
-
// ---
|
|
225
|
-
//
|
|
310
|
+
// --- Layout (section 5.5). -----------------------------------------------
|
|
311
|
+
//
|
|
312
|
+
// COMPUTED HERE, ABOVE THE REPORTING EFFECT, because the number the effect
|
|
313
|
+
// sends upward has to be the number this component actually renders (I-8).
|
|
314
|
+
// Deriving it twice — once for the frame and once for the budget — is exactly
|
|
315
|
+
// the trap `BottomStatusRow.tsx:5-27` documents.
|
|
316
|
+
const glyphs = pickGlyphs(caps);
|
|
317
|
+
const cols = stdout?.columns ?? 80;
|
|
318
|
+
const terminalRows = stdout?.rows ?? 24;
|
|
319
|
+
// Full-screen only: inline mode has no self-drawn viewport, so `scrolledLines`
|
|
320
|
+
// is always 0 there and the branch is unreachable rather than merely unused.
|
|
321
|
+
// The guard is explicit so it stays that way.
|
|
322
|
+
const chip = bordered ? scrollChip(scrolledLines, cols, glyphs) : null;
|
|
323
|
+
// Err NARROW. Wrapping wider than the box Ink gives us would make Ink wrap a
|
|
324
|
+
// second time, and then `layout.rows.length` is a lie — which is the one thing
|
|
325
|
+
// this module may not be.
|
|
326
|
+
const chromeCols = bordered ? 2 /* border */ + 2 /* paddingX */ + 2 /* marker */ : 2;
|
|
327
|
+
const baseCols = Math.max(8, cols - chromeCols - (chip ? chip.cells + 1 : 0));
|
|
328
|
+
const measure = (wrapCols) => layoutComposer({
|
|
329
|
+
buffer,
|
|
330
|
+
cursor,
|
|
331
|
+
cols: wrapCols,
|
|
332
|
+
maxRows: draftMaxRows(terminalRows),
|
|
333
|
+
active: isActive,
|
|
334
|
+
});
|
|
335
|
+
// TWO PASSES, AND THE SECOND ONE IS LOAD-BEARING (I-8). The overflow indicator
|
|
336
|
+
// rides the last rendered row INSIDE the box the draft wraps in, so its cells
|
|
337
|
+
// have to come out of the wrap width. Measured at the full width instead, a
|
|
338
|
+
// last row that fills the line is wrapped a SECOND time by Ink, and the
|
|
339
|
+
// composer then draws one row more than `layout.rows.length` — which is the
|
|
340
|
+
// number this component hands to `viewportRows`. That is defect C again, in
|
|
341
|
+
// precisely the situation this feature exists for: a draft too tall to show.
|
|
342
|
+
//
|
|
343
|
+
// It settles in two. Narrowing can only ADD rows, so a draft that overflowed
|
|
344
|
+
// the first pass still overflows the second, and a third pass would measure
|
|
345
|
+
// the same width as the second.
|
|
346
|
+
const wide = measure(baseCols);
|
|
347
|
+
const overflows = wide.hiddenAbove > 0 || wide.hiddenBelow > 0;
|
|
348
|
+
const usableCols = overflows ? Math.max(8, baseCols - OVERFLOW_CELLS - 1) : baseCols;
|
|
349
|
+
const layout = overflows ? measure(usableCols) : wide;
|
|
350
|
+
const draftRowCount = Math.max(1, layout.rows.length);
|
|
351
|
+
// --- Draft presence and height, reported only on a transition. -----------
|
|
352
|
+
// Notifying on every keystroke would re-render the whole App tree per key. The
|
|
353
|
+
// row count changes on a line break or a wrap boundary, not per character
|
|
354
|
+
// (R-6), so widening this callback does not widen how often it fires.
|
|
226
355
|
const hasDraft = buffer.length > 0;
|
|
227
356
|
const lastReportedDraft = useRef(null);
|
|
228
357
|
useEffect(() => {
|
|
229
358
|
if (!onDraftChange)
|
|
230
359
|
return;
|
|
231
|
-
|
|
360
|
+
const last = lastReportedDraft.current;
|
|
361
|
+
if (last && last.hasDraft === hasDraft && last.rows === draftRowCount)
|
|
232
362
|
return;
|
|
233
|
-
lastReportedDraft.current = hasDraft;
|
|
234
|
-
onDraftChange(hasDraft);
|
|
235
|
-
}, [hasDraft, onDraftChange]);
|
|
363
|
+
lastReportedDraft.current = { hasDraft, rows: draftRowCount };
|
|
364
|
+
onDraftChange({ hasDraft, rows: draftRowCount });
|
|
365
|
+
}, [hasDraft, draftRowCount, onDraftChange]);
|
|
236
366
|
// --- Mutators. -----------------------------------------------------------
|
|
237
367
|
// ONE DISPATCH EACH, and that is the rule §3.4 asks a reviewer to check line
|
|
238
368
|
// by line. Two dispatches from one key would put the legacy root back to two
|
|
@@ -240,8 +370,16 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
240
370
|
const insert = (text) => {
|
|
241
371
|
dispatch({ type: 'insert', text });
|
|
242
372
|
};
|
|
373
|
+
/**
|
|
374
|
+
* Expand every token back to its payload, then send (D-7 / D-9).
|
|
375
|
+
*
|
|
376
|
+
* `onSubmit`'s signature is unchanged, so `Composer`, `App.handleSubmit`,
|
|
377
|
+
* `runSlashInput` and `submitMessage` are all untouched — and a slash command
|
|
378
|
+
* therefore sees the EXPANDED text, which is what `/skill foo <pasted body>`
|
|
379
|
+
* needs. `clear` releases every payload in the same dispatch.
|
|
380
|
+
*/
|
|
243
381
|
const submit = (text) => {
|
|
244
|
-
onSubmit(text);
|
|
382
|
+
onSubmit(expandPastes(text, editor.pastes));
|
|
245
383
|
dispatch({ type: 'clear' });
|
|
246
384
|
};
|
|
247
385
|
const recallUp = () => {
|
|
@@ -304,7 +442,7 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
304
442
|
if (key.tab && key.shift)
|
|
305
443
|
return;
|
|
306
444
|
// Popup navigation owns Up/Down/Tab/→/Enter/Esc while it is open.
|
|
307
|
-
if (
|
|
445
|
+
if (popupVisible) {
|
|
308
446
|
if (key.upArrow) {
|
|
309
447
|
dispatch({ type: 'select', sel: Math.max(0, clampedSel - 1) });
|
|
310
448
|
return;
|
|
@@ -382,11 +520,32 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
382
520
|
onHelp();
|
|
383
521
|
return;
|
|
384
522
|
}
|
|
523
|
+
// A PASTE (section 5.4). It sits AFTER every key branch so a paste can
|
|
524
|
+
// never be shadowed by a key test, and BEFORE `isControlSeq` because a
|
|
525
|
+
// framed string starts with NUL and would otherwise be dropped.
|
|
526
|
+
//
|
|
527
|
+
// The text runs inside `segments` are already sanitised (I-14): Ink drains
|
|
528
|
+
// its whole buffer in one `read()`, so this same string can carry the `\r`
|
|
529
|
+
// of a user who pasted and pressed Enter inside the burst window.
|
|
530
|
+
if (hasPasteFrame(input)) {
|
|
531
|
+
const runs = splitPasteFrames(input);
|
|
532
|
+
const segments = runs.map((run) => run.kind === 'paste' ? { kind: 'paste', text: run.text, id: allocatePasteId() } : run);
|
|
533
|
+
if (segments.length === 0)
|
|
534
|
+
return;
|
|
535
|
+
const refusal = draftLimitRefusal(segments, editor);
|
|
536
|
+
if (refusal) {
|
|
537
|
+
// A refusal dispatches NOTHING, so the draft is byte-for-byte
|
|
538
|
+
// unchanged (AC-9) — and it is never silent (D-11).
|
|
539
|
+
onNotice?.('warn', refusal);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
dispatch({ type: 'input', segments });
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
385
545
|
// Printable input — drop any unrecognized control/escape byte.
|
|
386
546
|
if (input && !key.tab && !isControlSeq(input))
|
|
387
547
|
insert(input);
|
|
388
548
|
}, { isActive });
|
|
389
|
-
const glyphs = pickGlyphs(caps);
|
|
390
549
|
const marker = running ? glyphs.steer : glyphs.caret;
|
|
391
550
|
const markerColor = running ? theme.toolRunning : theme.primary;
|
|
392
551
|
const placeholder = running
|
|
@@ -394,12 +553,22 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
394
553
|
: agentMode === 'plan'
|
|
395
554
|
? `Describe what you want to build; I'll research and plan it first${glyphs.ellipsis}`
|
|
396
555
|
: `Send a message (/ for commands, @ for files)${glyphs.ellipsis}`;
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
556
|
+
const overflow = overflowChip(layout.hiddenAbove, layout.hiddenBelow, glyphs);
|
|
557
|
+
const tokenColor = theme.hintFg ?? theme.muted;
|
|
558
|
+
const renderSegments = (segments, keyPrefix) => segments.map((segment, i) => (_jsx(Text, { color: segment.kind === 'token' ? tokenColor : undefined, children: segment.text }, `${keyPrefix}${i}`)));
|
|
559
|
+
const rowNodes = layout.rows.map((row, i) => {
|
|
560
|
+
if (i !== layout.cursorRow)
|
|
561
|
+
return _jsx(_Fragment, { children: renderSegments(row.segments, `s${i}.`) });
|
|
562
|
+
// The caret is drawn as one inverse cell, split at the column
|
|
563
|
+
// `layoutComposer` reported — the same width table it wrapped with (I-9).
|
|
564
|
+
const split = splitRowAtColumn(row, layout.cursorCol);
|
|
565
|
+
return (_jsxs(_Fragment, { children: [renderSegments(split.before, `b${i}.`), _jsx(Text, { inverse: true, children: split.at ? split.at.text : ' ' }), renderSegments(split.after, `a${i}.`)] }));
|
|
566
|
+
});
|
|
567
|
+
const inputRow = (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: markerColor, bold: true, children: [marker, ' '] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: buffer.length === 0 ? (_jsx(Text, { wrap: "truncate", color: theme.muted, children: placeholder })) : (rowNodes.map((node, i) => overflow && i === rowNodes.length - 1 ? (
|
|
568
|
+
// The overflow indicator rides the LAST rendered row rather than
|
|
569
|
+
// taking one of its own: an extra row would put the composer's
|
|
570
|
+
// real height one above the `draftRows` it just reported (I-8).
|
|
571
|
+
_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, children: _jsx(Text, { children: node }) }), _jsx(Box, { flexShrink: 0, width: overflow.cells, justifyContent: "flex-end", children: _jsx(Text, { wrap: "truncate", color: theme.hintFg ?? theme.muted, children: overflow.text }) })] }, i)) : (_jsx(Text, { children: node }, i)))) }), chip && (
|
|
403
572
|
// `alignSelf="flex-start"` pins the chip to the FIRST row of a multi-line
|
|
404
573
|
// draft. Yoga's default `stretch` sizes this box to the full height of the
|
|
405
574
|
// editor; it renders on the top row either way today, and relying on that
|
|
@@ -410,28 +579,5 @@ export function PromptInput({ isActive, running, history, commands, cwd, theme,
|
|
|
410
579
|
// the user is typing — `↓ 9` becomes `↓ 10` becomes `↓ 100` — and every
|
|
411
580
|
// change would otherwise re-wrap a draft that fills the line.
|
|
412
581
|
_jsx(Box, { flexShrink: 0, alignSelf: "flex-start", marginLeft: 1, width: chip.cells, justifyContent: "flex-end", children: _jsx(Text, { wrap: "truncate", color: theme.hintFg ?? theme.muted, children: chip.text }) }))] }));
|
|
413
|
-
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [
|
|
414
|
-
}
|
|
415
|
-
/** Split the buffer into lines and render an inverse cursor at `cursor`. */
|
|
416
|
-
function renderWithCursor(buffer, cursor, active) {
|
|
417
|
-
if (!active)
|
|
418
|
-
return buffer.split('\n');
|
|
419
|
-
const before = buffer.slice(0, cursor);
|
|
420
|
-
const at = buffer[cursor] ?? ' ';
|
|
421
|
-
const after = buffer.slice(cursor + 1);
|
|
422
|
-
const beforeLines = before.split('\n');
|
|
423
|
-
const afterLines = after.split('\n');
|
|
424
|
-
const nodes = [];
|
|
425
|
-
const lastBeforeIdx = beforeLines.length - 1;
|
|
426
|
-
for (let i = 0; i < lastBeforeIdx; i += 1) {
|
|
427
|
-
nodes.push(_jsx(Text, { children: beforeLines[i] }, `b${i}`));
|
|
428
|
-
}
|
|
429
|
-
const cursorChar = at === '\n' ? ' ' : at;
|
|
430
|
-
const firstAfter = afterLines[0] ?? '';
|
|
431
|
-
nodes.push(_jsxs(Text, { children: [beforeLines[lastBeforeIdx], _jsx(Text, { inverse: true, children: cursorChar }), at === '\n' ? '' : firstAfter] }, "cursor-line"));
|
|
432
|
-
const remainingAfter = at === '\n' ? afterLines : afterLines.slice(1);
|
|
433
|
-
for (let i = 0; i < remainingAfter.length; i += 1) {
|
|
434
|
-
nodes.push(_jsx(Text, { children: remainingAfter[i] }, `a${i}`));
|
|
435
|
-
}
|
|
436
|
-
return nodes;
|
|
582
|
+
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [popupVisible && (_jsx(AutocompletePopup, { items: popupItems, selected: clampedSel, theme: theme, caps: caps, maxRows: popupMaxRows, layout: popupLayout })), bordered ? (_jsx(Box, { flexDirection: "column", flexShrink: 0, borderStyle: glyphs.boxStyle, borderColor: bordered.color, paddingX: 1, children: inputRow })) : (inputRow)] }));
|
|
437
583
|
}
|