@aragon-agent/cli 0.6.1 → 0.6.3
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 +216 -0
- package/README.md +241 -18
- package/dist/agent/agent-mode.d.ts +39 -0
- package/dist/agent/agent-mode.js +36 -0
- package/dist/agent/controller.d.ts +74 -1
- package/dist/agent/controller.js +200 -1
- package/dist/agent/headless.d.ts +8 -0
- package/dist/agent/headless.js +47 -0
- package/dist/agent/reducer.d.ts +120 -0
- package/dist/agent/reducer.js +201 -2
- package/dist/cli.js +297 -6
- package/dist/commands/builtins.js +73 -23
- package/dist/commands/registry.d.ts +27 -0
- package/dist/compaction/archive.d.ts +175 -0
- package/dist/compaction/archive.js +267 -0
- package/dist/compaction/child.d.ts +97 -0
- package/dist/compaction/child.js +113 -0
- package/dist/compaction/command.d.ts +41 -0
- package/dist/compaction/command.js +342 -0
- package/dist/compaction/compactor.d.ts +268 -0
- package/dist/compaction/compactor.js +689 -0
- package/dist/compaction/digest.d.ts +62 -0
- package/dist/compaction/digest.js +164 -0
- package/dist/compaction/limits.d.ts +165 -0
- package/dist/compaction/limits.js +172 -0
- package/dist/compaction/pressure.d.ts +135 -0
- package/dist/compaction/pressure.js +167 -0
- package/dist/compaction/summarize-call.d.ts +70 -0
- package/dist/compaction/summarize-call.js +86 -0
- package/dist/compaction/summary-prompt.d.ts +85 -0
- package/dist/compaction/summary-prompt.js +197 -0
- package/dist/compaction/tail-budget.d.ts +52 -0
- package/dist/compaction/tail-budget.js +67 -0
- package/dist/compaction/types.d.ts +179 -0
- package/dist/compaction/types.js +22 -0
- package/dist/compaction/wiring.d.ts +255 -0
- package/dist/compaction/wiring.js +556 -0
- package/dist/config/app-paths.d.ts +14 -0
- package/dist/config/app-paths.js +16 -0
- package/dist/config/cli-commands.d.ts +41 -0
- package/dist/config/cli-commands.js +77 -1
- package/dist/config/env.d.ts +4 -1
- package/dist/config/env.js +92 -2
- package/dist/config/load.d.ts +31 -0
- package/dist/config/load.js +63 -1
- package/dist/config/schema.d.ts +159 -0
- package/dist/config/schema.js +137 -0
- package/dist/config/store.js +17 -1
- package/dist/config/ui-state.d.ts +44 -6
- package/dist/config/ui-state.js +21 -12
- package/dist/diagnostics/doctor.js +41 -1
- package/dist/exec/events.d.ts +50 -1
- package/dist/exec/index.js +8 -4
- package/dist/exec/runner.js +44 -2
- package/dist/input/mouse-events.d.ts +31 -5
- package/dist/input/mouse-events.js +86 -27
- package/dist/input/stdin-mouse-filter.d.ts +8 -3
- package/dist/logging/install.d.ts +51 -1
- package/dist/logging/install.js +28 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/session/cli-commands.js +23 -1
- package/dist/session/persist.js +19 -0
- package/dist/session/store.d.ts +18 -0
- package/dist/session/store.js +59 -2
- package/dist/team/report.js +7 -1
- package/dist/team/runtime.d.ts +10 -0
- package/dist/team/runtime.js +7 -0
- package/dist/team/subagent.d.ts +31 -1
- package/dist/team/subagent.js +42 -15
- package/dist/team/types.d.ts +8 -0
- package/dist/ui/ActivityLine.d.ts +19 -1
- package/dist/ui/ActivityLine.js +10 -2
- package/dist/ui/App.d.ts +48 -1
- package/dist/ui/App.js +317 -17
- package/dist/ui/Composer.d.ts +26 -1
- package/dist/ui/Composer.js +6 -6
- package/dist/ui/PromptInput.d.ts +34 -1
- package/dist/ui/PromptInput.js +54 -3
- package/dist/ui/StatusBar.d.ts +40 -0
- package/dist/ui/StatusBar.js +13 -4
- package/dist/ui/TeamPanel.js +7 -1
- package/dist/ui/Transcript.d.ts +21 -1
- package/dist/ui/Transcript.js +32 -2
- package/dist/ui/clipboard.d.ts +52 -0
- package/dist/ui/clipboard.js +91 -0
- package/dist/ui/entries/CompactionCard.d.ts +75 -0
- package/dist/ui/entries/CompactionCard.js +158 -0
- package/dist/ui/frame-differ.d.ts +46 -0
- package/dist/ui/frame-differ.js +94 -10
- package/dist/ui/gauge.d.ts +16 -1
- package/dist/ui/gauge.js +25 -6
- package/dist/ui/glyphs.d.ts +11 -0
- package/dist/ui/glyphs.js +2 -0
- package/dist/ui/layout/ScrollViewport.d.ts +43 -6
- package/dist/ui/layout/ScrollViewport.js +128 -21
- package/dist/ui/layout/budget.d.ts +10 -3
- package/dist/ui/layout/budget.js +10 -3
- package/dist/ui/layout/follow-state.d.ts +137 -0
- package/dist/ui/layout/follow-state.js +150 -0
- package/dist/ui/layout/virtual-window.d.ts +11 -0
- package/dist/ui/layout/virtual-window.js +34 -0
- package/dist/ui/overlays/HelpOverlay.js +18 -2
- package/dist/ui/overlays/SettingsScreen.d.ts +47 -1
- package/dist/ui/overlays/SettingsScreen.js +60 -1
- package/dist/ui/palettes.d.ts +16 -0
- package/dist/ui/palettes.js +6 -0
- package/dist/ui/screen.d.ts +22 -0
- package/dist/ui/screen.js +70 -8
- package/dist/ui/selection/highlight.d.ts +39 -0
- package/dist/ui/selection/highlight.js +64 -0
- package/dist/ui/selection/screen-mirror.d.ts +24 -0
- package/dist/ui/selection/screen-mirror.js +35 -0
- package/dist/ui/selection/selection-controller.d.ts +88 -0
- package/dist/ui/selection/selection-controller.js +282 -0
- package/dist/ui/selection/selection.d.ts +90 -0
- package/dist/ui/selection/selection.js +223 -0
- package/dist/ui/stdout-frame-writer.d.ts +33 -0
- package/dist/ui/stdout-frame-writer.js +15 -0
- package/dist/ui/theme.d.ts +11 -0
- package/dist/ui/theme.js +2 -0
- package/dist/ui/transcript-text.js +23 -0
- package/dist/ui/use-startup-notices.d.ts +62 -5
- package/dist/ui/use-startup-notices.js +81 -17
- package/dist/ui/use-wheel-routing.js +8 -0
- package/dist/ui/win-vt-force.d.ts +136 -0
- package/dist/ui/win-vt-force.js +237 -0
- package/package.json +4 -2
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { type ThinkingLevel } from '@aragon-agent/core';
|
|
12
|
-
import { type FastConfig } from '../../config/schema.js';
|
|
12
|
+
import { type CompactionConfig, type FastConfig } from '../../config/schema.js';
|
|
13
13
|
import { type LogLevelName } from '../../logging/levels.js';
|
|
14
14
|
import type { Theme } from '../theme.js';
|
|
15
15
|
import type { TermCapabilities } from '../capabilities.js';
|
|
@@ -57,6 +57,33 @@ export interface SettingsValues {
|
|
|
57
57
|
fastReview: string;
|
|
58
58
|
/** The session review budget. `fastReview` is the PACE, this is the TOTAL. */
|
|
59
59
|
fastReviewBudget: string;
|
|
60
|
+
/**
|
|
61
|
+
* Context compaction (context-auto-compaction §4.5).
|
|
62
|
+
*
|
|
63
|
+
* THREE ROWS, NOT SIX. `onFailure` and `useFastTier` are file- and
|
|
64
|
+
* `/compact`-only on purpose: this screen is for the settings a user CHANGES,
|
|
65
|
+
* not for every setting that exists, and "what should it do if the summarizer
|
|
66
|
+
* 500s" is not a question a user reaches for with arrow keys.
|
|
67
|
+
*
|
|
68
|
+
* `string` rather than their config types because this screen edits TEXT, and
|
|
69
|
+
* a typo must change NOTHING rather than resolve to a default — the discipline
|
|
70
|
+
* `maxTokens` and `fastReview` already follow here.
|
|
71
|
+
*/
|
|
72
|
+
compactionEnabled: 'on' | 'off';
|
|
73
|
+
/** A percentage the user types, e.g. `90`. Parsed on save; a typo is ignored. */
|
|
74
|
+
compactionThreshold: string;
|
|
75
|
+
compactionKeepTurns: string;
|
|
76
|
+
/**
|
|
77
|
+
* The two hardening toggles (context-auto-compaction-hardening §4.6).
|
|
78
|
+
*
|
|
79
|
+
* FIVE ROWS NOW, NOT THREE, and these two earn their place where `onFailure`
|
|
80
|
+
* did not: each one names a THING THE SESSION WILL DO - spend money inside a
|
|
81
|
+
* fan-out, and write conversation content to disk - rather than a policy for a
|
|
82
|
+
* failure the user has never seen. Both are questions a user reaches for
|
|
83
|
+
* deliberately.
|
|
84
|
+
*/
|
|
85
|
+
compactionSubagents: 'on' | 'off';
|
|
86
|
+
compactionArchive: 'on' | 'off';
|
|
60
87
|
}
|
|
61
88
|
interface SettingsScreenProps {
|
|
62
89
|
initial: SettingsValues;
|
|
@@ -121,6 +148,25 @@ export declare function readFastSettings(values: Pick<SettingsValues, 'fastEnabl
|
|
|
121
148
|
* save writes the clamp's default over whatever the user had.
|
|
122
149
|
*/
|
|
123
150
|
export declare function fastSettingsFrom(fast: FastConfig): Pick<SettingsValues, 'fastEnabled' | 'fastModel' | 'fastProvider' | 'fastReview' | 'fastReviewBudget'>;
|
|
151
|
+
/**
|
|
152
|
+
* Read the three compaction rows back into a config patch.
|
|
153
|
+
*
|
|
154
|
+
* A TYPO CHANGES NOTHING — the discipline `readFastSettings` states one function
|
|
155
|
+
* up, and the reason is the same: a threshold that silently resolved to the
|
|
156
|
+
* default on a mistyped keystroke would be a setting the user believes they
|
|
157
|
+
* changed and did not. `parseThresholdInput` returns `null` for anything that is
|
|
158
|
+
* not a ratio or a percentage, and the key is then simply absent from the patch.
|
|
159
|
+
*/
|
|
160
|
+
export declare function readCompactionSettings(values: Pick<SettingsValues, 'compactionEnabled' | 'compactionThreshold' | 'compactionKeepTurns' | 'compactionSubagents' | 'compactionArchive'>): Partial<CompactionConfig>;
|
|
161
|
+
/**
|
|
162
|
+
* The inverse: seed the three rows from a resolved config.
|
|
163
|
+
*
|
|
164
|
+
* OMITTING A KEY HERE IS THE SILENT HALF OF ADDING A ROW, exactly as it is for
|
|
165
|
+
* the fast rows above: the row renders empty, `readCompactionSettings` sees an
|
|
166
|
+
* unparseable value, and an untouched save writes the clamp's default over
|
|
167
|
+
* whatever the user had.
|
|
168
|
+
*/
|
|
169
|
+
export declare function compactionSettingsFrom(compaction: CompactionConfig): Pick<SettingsValues, 'compactionEnabled' | 'compactionThreshold' | 'compactionKeepTurns' | 'compactionSubagents' | 'compactionArchive'>;
|
|
124
170
|
/**
|
|
125
171
|
* The cap this setting will ACTUALLY produce against the selected model.
|
|
126
172
|
*
|
|
@@ -11,13 +11,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
11
11
|
import { useState } from 'react';
|
|
12
12
|
import { Text, useInput } from 'ink';
|
|
13
13
|
import { DEFAULT_MAX_OUTPUT_TOKENS, resolveOutputTokens, } from '@aragon-agent/core';
|
|
14
|
-
import { ADAPTER_PROVIDERS, THINKING_LEVELS, maskSecret, parseMaxTokensInput, } from '../../config/schema.js';
|
|
14
|
+
import { ADAPTER_PROVIDERS, THINKING_LEVELS, maskSecret, parseMaxTokensInput, parseThresholdInput, } from '../../config/schema.js';
|
|
15
15
|
import { LOG_LEVEL_NAMES } from '../../logging/levels.js';
|
|
16
16
|
import { pickGlyphs } from '../glyphs.js';
|
|
17
17
|
import { OverlayFrame } from '../layout/OverlayFrame.js';
|
|
18
18
|
const SHOW_THINKING_OPTIONS = ['off', 'on'];
|
|
19
19
|
const LIVE_OUTPUT_OPTIONS = ['off', 'on'];
|
|
20
20
|
const FAST_ENABLED_OPTIONS = ['off', 'on'];
|
|
21
|
+
const COMPACTION_ENABLED_OPTIONS = ['off', 'on'];
|
|
21
22
|
/**
|
|
22
23
|
* `(inherit)` IS THE EMPTY STRING, rendered as a word.
|
|
23
24
|
*
|
|
@@ -107,6 +108,48 @@ export function fastSettingsFrom(fast) {
|
|
|
107
108
|
fastReviewBudget: String(fast.reviewMaxPerSession),
|
|
108
109
|
};
|
|
109
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Read the three compaction rows back into a config patch.
|
|
113
|
+
*
|
|
114
|
+
* A TYPO CHANGES NOTHING — the discipline `readFastSettings` states one function
|
|
115
|
+
* up, and the reason is the same: a threshold that silently resolved to the
|
|
116
|
+
* default on a mistyped keystroke would be a setting the user believes they
|
|
117
|
+
* changed and did not. `parseThresholdInput` returns `null` for anything that is
|
|
118
|
+
* not a ratio or a percentage, and the key is then simply absent from the patch.
|
|
119
|
+
*/
|
|
120
|
+
export function readCompactionSettings(values) {
|
|
121
|
+
const threshold = parseThresholdInput(values.compactionThreshold);
|
|
122
|
+
const keep = values.compactionKeepTurns.trim();
|
|
123
|
+
const parsedKeep = /^\d+$/.test(keep) ? Number.parseInt(keep, 10) : Number.NaN;
|
|
124
|
+
return {
|
|
125
|
+
enabled: values.compactionEnabled === 'on',
|
|
126
|
+
...(threshold !== null ? { threshold } : {}),
|
|
127
|
+
...(Number.isFinite(parsedKeep) && parsedKeep > 0 ? { keepRecentTurns: parsedKeep } : {}),
|
|
128
|
+
// AN ENUM ROW CANNOT BE MISTYPED, so unlike the two text rows above these are
|
|
129
|
+
// written unconditionally.
|
|
130
|
+
subagents: values.compactionSubagents === 'on',
|
|
131
|
+
archive: values.compactionArchive === 'on',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The inverse: seed the three rows from a resolved config.
|
|
136
|
+
*
|
|
137
|
+
* OMITTING A KEY HERE IS THE SILENT HALF OF ADDING A ROW, exactly as it is for
|
|
138
|
+
* the fast rows above: the row renders empty, `readCompactionSettings` sees an
|
|
139
|
+
* unparseable value, and an untouched save writes the clamp's default over
|
|
140
|
+
* whatever the user had.
|
|
141
|
+
*/
|
|
142
|
+
export function compactionSettingsFrom(compaction) {
|
|
143
|
+
return {
|
|
144
|
+
compactionEnabled: compaction.enabled ? 'on' : 'off',
|
|
145
|
+
// A PERCENTAGE, because that is the unit the status bar shows and the unit a
|
|
146
|
+
// user thinks in. `parseThresholdInput` accepts it back.
|
|
147
|
+
compactionThreshold: String(Math.round(compaction.threshold * 100)),
|
|
148
|
+
compactionKeepTurns: String(compaction.keepRecentTurns),
|
|
149
|
+
compactionSubagents: compaction.subagents ? 'on' : 'off',
|
|
150
|
+
compactionArchive: compaction.archive ? 'on' : 'off',
|
|
151
|
+
};
|
|
152
|
+
}
|
|
110
153
|
const FIELDS = [
|
|
111
154
|
{ key: 'provider', label: 'Provider', kind: 'enum', options: ADAPTER_PROVIDERS },
|
|
112
155
|
{ key: 'model', label: 'Model', kind: 'text' },
|
|
@@ -146,6 +189,22 @@ const FIELDS = [
|
|
|
146
189
|
{ key: 'fastProvider', label: 'Fast provider', kind: 'enum', options: FAST_PROVIDER_OPTIONS },
|
|
147
190
|
{ key: 'fastReview', label: 'Fast review', kind: 'text' },
|
|
148
191
|
{ key: 'fastReviewBudget', label: 'Fast budget', kind: 'text' },
|
|
192
|
+
// Context compaction sits after the fast rows and still BEFORE `API key`, for
|
|
193
|
+
// the reason recorded above that row: the key is the one a user scrolls to
|
|
194
|
+
// deliberately, and burying it further would be worse than the ordering is
|
|
195
|
+
// better. Three rows, and every label is inside the 18-column budget
|
|
196
|
+
// `.padEnd(18)` enforces below.
|
|
197
|
+
//
|
|
198
|
+
// `Compact at` is a PERCENTAGE and carries no `(50-95)` hint, for the reason
|
|
199
|
+
// `Fast budget` records: no other bounded row carries its range either,
|
|
200
|
+
// `clampCompactionConfig` is the enforcement, and `/compact threshold` states
|
|
201
|
+
// the range in the one place a user meets it — when it refuses a bad one.
|
|
202
|
+
{ key: 'compactionEnabled', label: 'Auto-compaction', kind: 'enum', options: COMPACTION_ENABLED_OPTIONS },
|
|
203
|
+
{ key: 'compactionThreshold', label: 'Compact at %', kind: 'text' },
|
|
204
|
+
{ key: 'compactionKeepTurns', label: 'Keep turns', kind: 'text' },
|
|
205
|
+
// Both labels are inside the 18-column budget `.padEnd(18)` enforces below.
|
|
206
|
+
{ key: 'compactionSubagents', label: 'Compact children', kind: 'enum', options: COMPACTION_ENABLED_OPTIONS },
|
|
207
|
+
{ key: 'compactionArchive', label: 'Archive dropped', kind: 'enum', options: COMPACTION_ENABLED_OPTIONS },
|
|
149
208
|
{ key: 'apiKey', label: 'API key', kind: 'secret' },
|
|
150
209
|
{ key: 'logLevel', label: 'Log level', kind: 'enum', options: LOG_LEVEL_NAMES },
|
|
151
210
|
];
|
package/dist/ui/palettes.d.ts
CHANGED
|
@@ -32,6 +32,22 @@ export interface Palette {
|
|
|
32
32
|
idleBorder: string;
|
|
33
33
|
hintFg: string;
|
|
34
34
|
logoShadow: string;
|
|
35
|
+
/**
|
|
36
|
+
* Drag-selection highlight (tui-selection-and-scroll-follow §4.4.4).
|
|
37
|
+
*
|
|
38
|
+
* A PAIR, not a background alone: the selected run has its inner SGR stripped
|
|
39
|
+
* before it is repainted, so whatever foreground the row had is gone by the
|
|
40
|
+
* time the highlight is applied and a background on its own would leave the
|
|
41
|
+
* text at the terminal's default colour over an arbitrary hue. The two are
|
|
42
|
+
* contrast-pinned against EACH OTHER in `palettes.test.ts` — the surface a
|
|
43
|
+
* selected glyph sits on is `selectionBg`, not `DARK_SURFACE`.
|
|
44
|
+
*
|
|
45
|
+
* Below ansi-256 these never reach the terminal at all: `highlight.ts` emits
|
|
46
|
+
* plain reverse video there, because `getTheme` has degraded them to chalk
|
|
47
|
+
* colour NAMES by then and a name is not an escape sequence.
|
|
48
|
+
*/
|
|
49
|
+
selectionBg: string;
|
|
50
|
+
selectionFg: string;
|
|
35
51
|
gradient: [string, string, string];
|
|
36
52
|
gauge: {
|
|
37
53
|
track: string;
|
package/dist/ui/palettes.js
CHANGED
|
@@ -39,6 +39,8 @@ export const WARM = {
|
|
|
39
39
|
idleBorder: '#4a423b',
|
|
40
40
|
hintFg: '#9c9086',
|
|
41
41
|
logoShadow: '#4a423b',
|
|
42
|
+
selectionBg: '#e0b088',
|
|
43
|
+
selectionFg: '#1c1917',
|
|
42
44
|
gradient: ['#d08c60', '#c9a227', '#b0916a'],
|
|
43
45
|
gauge: { track: '#4a423b', low: '#7fb08a', mid: '#c9a227', high: '#cf6b5c' },
|
|
44
46
|
diff: { add: '#7fb08a', remove: '#cf6b5c', meta: '#d08c60', context: '#a2968a' },
|
|
@@ -66,6 +68,8 @@ export const COOL = {
|
|
|
66
68
|
idleBorder: '#3b4261',
|
|
67
69
|
hintFg: '#565f89',
|
|
68
70
|
logoShadow: '#3b4261',
|
|
71
|
+
selectionBg: '#7aa2f7',
|
|
72
|
+
selectionFg: '#1a1b26',
|
|
69
73
|
gradient: ['#7aa2f7', '#bb9af7', '#7dcfff'],
|
|
70
74
|
gauge: { track: '#3b4261', low: '#9ece6a', mid: '#e0af68', high: '#f7768e' },
|
|
71
75
|
diff: { add: '#9ece6a', remove: '#f7768e', meta: '#7aa2f7', context: '#7a86b8' },
|
|
@@ -92,6 +96,8 @@ export const LIGHT = {
|
|
|
92
96
|
idleBorder: '#c8cdd6',
|
|
93
97
|
hintFg: '#6b7280',
|
|
94
98
|
logoShadow: '#c8cdd6',
|
|
99
|
+
selectionBg: '#2959aa',
|
|
100
|
+
selectionFg: '#ffffff',
|
|
95
101
|
gradient: ['#2959aa', '#8c4bc9', '#0f7490'],
|
|
96
102
|
gauge: { track: '#c8cdd6', low: '#2e7d32', mid: '#b5730f', high: '#c62828' },
|
|
97
103
|
diff: { add: '#2e7d32', remove: '#c62828', meta: '#2959aa', context: '#6b7280' },
|
package/dist/ui/screen.d.ts
CHANGED
|
@@ -14,6 +14,16 @@
|
|
|
14
14
|
export interface ScreenHandle {
|
|
15
15
|
/** Leave the alternate screen. Idempotent — safe from every exit path. */
|
|
16
16
|
restore(): void;
|
|
17
|
+
/**
|
|
18
|
+
* Hand the mouse back to the terminal for this session, or take it again
|
|
19
|
+
* (tui-selection-and-scroll-follow §4.4.6 / G3). A no-op when this session
|
|
20
|
+
* never had a filter, and idempotent in both directions.
|
|
21
|
+
*
|
|
22
|
+
* This is the honest answer to "a user who wants their terminal's own
|
|
23
|
+
* selection", and it is reversible in one command instead of a restart plus a
|
|
24
|
+
* config edit (D-9).
|
|
25
|
+
*/
|
|
26
|
+
setMouseCapture(on: boolean): void;
|
|
17
27
|
}
|
|
18
28
|
export interface AltScreenOptions {
|
|
19
29
|
/**
|
|
@@ -27,6 +37,18 @@ export interface AltScreenOptions {
|
|
|
27
37
|
* only caller is `cli.tsx::runInteractive`, and it passes `filter !== null`.
|
|
28
38
|
*/
|
|
29
39
|
readonly mouse: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Whether to request motion reporting (`?1002h`) alongside the two modes
|
|
42
|
+
* above — i.e. whether drag-select is on for this session.
|
|
43
|
+
*
|
|
44
|
+
* SAME "ALREADY DECIDED ELSEWHERE" DISCIPLINE AS `mouse`: this module must
|
|
45
|
+
* never derive it from config. `cli.tsx` passes
|
|
46
|
+
* `mouseFilter !== null && cfg.mouseSelect`.
|
|
47
|
+
*
|
|
48
|
+
* Optional, defaulting to `false`, so every pre-existing caller and every
|
|
49
|
+
* pre-existing test emits exactly the bytes it emitted before (AC-8 / T-34).
|
|
50
|
+
*/
|
|
51
|
+
readonly motion?: boolean;
|
|
30
52
|
}
|
|
31
53
|
/**
|
|
32
54
|
* Enter the alternate screen. A non-TTY stdout is a hard no-op (tests, pipes,
|
package/dist/ui/screen.js
CHANGED
|
@@ -20,12 +20,24 @@ const SHOW_CURSOR = '\x1b[?25h';
|
|
|
20
20
|
/**
|
|
21
21
|
* `?1000` is NORMAL TRACKING (press / release), the minimum mode that reports
|
|
22
22
|
* wheel buttons; `?1006` selects SGR encoding so coordinates arrive as decimal
|
|
23
|
-
* text rather than raw bytes above 0x7F that `setEncoding('utf8')` would
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* text rather than raw bytes above 0x7F that `setEncoding('utf8')` would mangle.
|
|
24
|
+
*
|
|
25
|
+
* `?1002` is BUTTON-EVENT TRACKING: motion is reported ONLY WHILE A BUTTON IS
|
|
26
|
+
* HELD, which is what drag-select needs and costs exactly zero reports while the
|
|
27
|
+
* pointer is idle. This comment used to lump it together with `?1003` and
|
|
28
|
+
* dismiss both as "floods stdin on every pointer move" — true of `?1003`
|
|
29
|
+
* (any-event), false of `?1002`, and the sentence had to go in the same commit
|
|
30
|
+
* that started asking for it or it would stand as a documented reason not to
|
|
31
|
+
* (AC-11 / D-10).
|
|
32
|
+
*
|
|
33
|
+
* IT IS GATED ON `motion`, i.e. on the resolved `mouseSelect` capability, and
|
|
34
|
+
* that is what makes "S3 is revertible" a byte-level fact rather than a claim:
|
|
35
|
+
* with drag-select off the sequences below are byte-identical to the constants
|
|
36
|
+
* they replaced (AC-8 / D-15). `disableMouse` is the exact reverse of
|
|
37
|
+
* `enableMouse` for both values (I-1 / AC-7).
|
|
26
38
|
*/
|
|
27
|
-
const
|
|
28
|
-
const
|
|
39
|
+
const enableMouse = (motion) => `\x1b[?1000h${motion ? '\x1b[?1002h' : ''}\x1b[?1006h`;
|
|
40
|
+
const disableMouse = (motion) => `\x1b[?1006l${motion ? '\x1b[?1002l' : ''}\x1b[?1000l`;
|
|
29
41
|
/**
|
|
30
42
|
* Alternate scroll (DEC private mode 1007) is what turns a wheel notch into a
|
|
31
43
|
* burst of arrow keys, which `PromptInput` reads as prompt-history recall. With
|
|
@@ -43,7 +55,7 @@ const DISABLE_MOUSE = '\x1b[?1006l\x1b[?1000l';
|
|
|
43
55
|
*/
|
|
44
56
|
const SAVE_ALT_SCROLL_OFF = '\x1b[?1007s\x1b[?1007l';
|
|
45
57
|
const RESTORE_ALT_SCROLL = '\x1b[?1007r';
|
|
46
|
-
const NOOP_HANDLE = { restore() { } };
|
|
58
|
+
const NOOP_HANDLE = { restore() { }, setMouseCapture() { } };
|
|
47
59
|
function safeWrite(stdout, data) {
|
|
48
60
|
try {
|
|
49
61
|
stdout.write(data);
|
|
@@ -60,7 +72,50 @@ export function enterAltScreen(stdout, options = { mouse: false }) {
|
|
|
60
72
|
if (!stdout || !stdout.isTTY || typeof stdout.write !== 'function')
|
|
61
73
|
return NOOP_HANDLE;
|
|
62
74
|
const mouse = options.mouse === true;
|
|
63
|
-
|
|
75
|
+
const motion = options.motion === true;
|
|
76
|
+
safeWrite(stdout, ENTER_ALT + CLEAR_VISIBLE + CURSOR_HOME + (mouse ? enableMouse(motion) : SAVE_ALT_SCROLL_OFF));
|
|
77
|
+
/**
|
|
78
|
+
* THE CURRENT capture state, not the one the session started in (I-10).
|
|
79
|
+
*
|
|
80
|
+
* `restore()` used to close over the immutable `mouse` boolean. After
|
|
81
|
+
* `/mouse` exists that boolean is no longer the truth, and the exit path is
|
|
82
|
+
* the one place where being wrong leaves the user's shell broken until they
|
|
83
|
+
* run `reset`.
|
|
84
|
+
*/
|
|
85
|
+
let captured = mouse;
|
|
86
|
+
/**
|
|
87
|
+
* Whether OUR `XTSAVE` of DEC 1007 is outstanding.
|
|
88
|
+
*
|
|
89
|
+
* `XTSAVE` HAS ONE SLOT PER MODE, so the save and the restore must stay
|
|
90
|
+
* STRICTLY PAIRED: an `off` that saved while our own disable was already in
|
|
91
|
+
* effect would save OUR value, and the following restore would hand the user
|
|
92
|
+
* back a preference that was never theirs — permanently.
|
|
93
|
+
*/
|
|
94
|
+
let savedAltScroll = mouse ? false : true;
|
|
95
|
+
const setMouseCapture = (on) => {
|
|
96
|
+
// A session that never installed a filter has nothing to release, and
|
|
97
|
+
// enabling reporting without one types `[<0;12;5M` into the user's message
|
|
98
|
+
// on every click (I-8). It stays a no-op in both directions.
|
|
99
|
+
if (!mouse || on === captured)
|
|
100
|
+
return;
|
|
101
|
+
captured = on;
|
|
102
|
+
if (on) {
|
|
103
|
+
// Alternate scroll goes back to the user's own preference BEFORE reporting
|
|
104
|
+
// resumes, so the two never overlap in the "both disabled" state.
|
|
105
|
+
safeWrite(stdout, (savedAltScroll ? RESTORE_ALT_SCROLL : '') + enableMouse(motion));
|
|
106
|
+
savedAltScroll = false;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// DEC 1007 IS THE HALF THAT MUST NOT BE DROPPED (P1-1). With reporting off,
|
|
110
|
+
// alternate scroll turns every wheel notch back into a burst of arrow keys,
|
|
111
|
+
// which `PromptInput` reads as prompt-history recall — replacing the user's
|
|
112
|
+
// draft with an old prompt. A `/mouse off` that wrote only the mouse
|
|
113
|
+
// sequences would hand that defect back on request, in the one command whose
|
|
114
|
+
// entire purpose is to make things better. Saved rather than force-set, so a
|
|
115
|
+
// user who deliberately disabled alternate scroll keeps their choice.
|
|
116
|
+
safeWrite(stdout, disableMouse(motion) + SAVE_ALT_SCROLL_OFF);
|
|
117
|
+
savedAltScroll = true;
|
|
118
|
+
};
|
|
64
119
|
let restored = false;
|
|
65
120
|
return {
|
|
66
121
|
restore() {
|
|
@@ -74,8 +129,15 @@ export function enterAltScreen(stdout, options = { mouse: false }) {
|
|
|
74
129
|
// here and not in `App.tsx`, a component that is on none of them.
|
|
75
130
|
// Leaving reporting on prints `[<0;12;5M` in the user's shell on every
|
|
76
131
|
// click, forever, until they run `reset`.
|
|
77
|
-
|
|
132
|
+
//
|
|
133
|
+
// IT UNWINDS THE CURRENT STATE (I-10): `captured` and `savedAltScroll` are
|
|
134
|
+
// read here rather than the startup `mouse` flag, so a session that ran
|
|
135
|
+
// `/mouse off` exits with exactly one outstanding save restored and
|
|
136
|
+
// nothing left reporting.
|
|
137
|
+
const tail = (captured ? disableMouse(motion) : '') + (savedAltScroll ? RESTORE_ALT_SCROLL : '');
|
|
138
|
+
safeWrite(stdout, tail + SHOW_CURSOR + LEAVE_ALT);
|
|
78
139
|
},
|
|
140
|
+
setMouseCapture,
|
|
79
141
|
};
|
|
80
142
|
}
|
|
81
143
|
/**
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The row painter (tui-selection-and-scroll-follow §4.4.4). PURE — strings in,
|
|
3
|
+
* strings out.
|
|
4
|
+
*
|
|
5
|
+
* It runs inside the frame differ's `decorate` hook rather than in React (D-6):
|
|
6
|
+
* a re-render per drag event goes through the render governor and the whole
|
|
7
|
+
* component tree, while this repaints one to three rows with no commit at all.
|
|
8
|
+
*/
|
|
9
|
+
import type { Theme } from '../theme.js';
|
|
10
|
+
import type { TermCapabilities } from '../capabilities.js';
|
|
11
|
+
/**
|
|
12
|
+
* The SGR sequence that opens a highlighted run.
|
|
13
|
+
*
|
|
14
|
+
* TRUECOLOR OR REVERSE VIDEO, WITH NOTHING IN BETWEEN, and that is deliberate.
|
|
15
|
+
* `getTheme` degrades every palette colour to the terminal's depth, so below
|
|
16
|
+
* ansi-256 `theme.selectionBg` is a CHALK COLOUR NAME (`'blue'`) rather than a
|
|
17
|
+
* hex triple — and a name cannot be turned into an escape sequence here without
|
|
18
|
+
* duplicating chalk's own table. Reverse video is what a terminal that shallow
|
|
19
|
+
* would render anyway, it needs no colour support at all, and it is exactly what
|
|
20
|
+
* the emulator's own selection looks like. The `HEX` test is therefore both the
|
|
21
|
+
* colour-depth check and the type check.
|
|
22
|
+
*/
|
|
23
|
+
export declare function selectionOpen(theme: Theme, caps: TermCapabilities): string;
|
|
24
|
+
/**
|
|
25
|
+
* Paint `[from, to)` of one frame line.
|
|
26
|
+
*
|
|
27
|
+
* `to` is already clamped to the terminal width by the caller, so `Infinity`
|
|
28
|
+
* never reaches here.
|
|
29
|
+
*
|
|
30
|
+
* STRIPPING SGR INSIDE THE SELECTION IS NOT LAZINESS. An inner `\x1b[0m` — which
|
|
31
|
+
* `Markdown` and `cli-highlight` emit constantly — would cancel the highlight
|
|
32
|
+
* attribute mid-run and leave holes in it. Terminals repaint selected text in
|
|
33
|
+
* the selection's own colours for exactly this reason.
|
|
34
|
+
*
|
|
35
|
+
* `pad` is what gives a multi-row selection a straight right edge over short
|
|
36
|
+
* lines, and it measures with `columnsOf` — the same oracle `sliceColumns`
|
|
37
|
+
* charges with — rather than reaching for a third width function (I-12).
|
|
38
|
+
*/
|
|
39
|
+
export declare function paintRow(line: string, from: number, to: number, open: string): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The row painter (tui-selection-and-scroll-follow §4.4.4). PURE — strings in,
|
|
3
|
+
* strings out.
|
|
4
|
+
*
|
|
5
|
+
* It runs inside the frame differ's `decorate` hook rather than in React (D-6):
|
|
6
|
+
* a re-render per drag event goes through the render governor and the whole
|
|
7
|
+
* component tree, while this repaints one to three rows with no commit at all.
|
|
8
|
+
*/
|
|
9
|
+
import stripAnsi from 'strip-ansi';
|
|
10
|
+
import { columnsOf, sliceColumns } from './selection.js';
|
|
11
|
+
const CSI = '\x1b[';
|
|
12
|
+
const SGR_RESET = `${CSI}0m`;
|
|
13
|
+
/** Reverse video — the universal fallback, and what a monochrome terminal gets. */
|
|
14
|
+
const REVERSE = `${CSI}7m`;
|
|
15
|
+
const HEX = /^#[0-9a-f]{6}$/i;
|
|
16
|
+
function rgb(hex) {
|
|
17
|
+
const n = Number.parseInt(hex.slice(1), 16);
|
|
18
|
+
return `${(n >> 16) & 0xff};${(n >> 8) & 0xff};${n & 0xff}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The SGR sequence that opens a highlighted run.
|
|
22
|
+
*
|
|
23
|
+
* TRUECOLOR OR REVERSE VIDEO, WITH NOTHING IN BETWEEN, and that is deliberate.
|
|
24
|
+
* `getTheme` degrades every palette colour to the terminal's depth, so below
|
|
25
|
+
* ansi-256 `theme.selectionBg` is a CHALK COLOUR NAME (`'blue'`) rather than a
|
|
26
|
+
* hex triple — and a name cannot be turned into an escape sequence here without
|
|
27
|
+
* duplicating chalk's own table. Reverse video is what a terminal that shallow
|
|
28
|
+
* would render anyway, it needs no colour support at all, and it is exactly what
|
|
29
|
+
* the emulator's own selection looks like. The `HEX` test is therefore both the
|
|
30
|
+
* colour-depth check and the type check.
|
|
31
|
+
*/
|
|
32
|
+
export function selectionOpen(theme, caps) {
|
|
33
|
+
if (caps.colorLevel < 2)
|
|
34
|
+
return REVERSE;
|
|
35
|
+
const bg = theme.selectionBg;
|
|
36
|
+
const fg = theme.selectionFg;
|
|
37
|
+
if (!bg || !fg || !HEX.test(bg) || !HEX.test(fg))
|
|
38
|
+
return REVERSE;
|
|
39
|
+
return `${CSI}48;2;${rgb(bg)}m${CSI}38;2;${rgb(fg)}m`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Paint `[from, to)` of one frame line.
|
|
43
|
+
*
|
|
44
|
+
* `to` is already clamped to the terminal width by the caller, so `Infinity`
|
|
45
|
+
* never reaches here.
|
|
46
|
+
*
|
|
47
|
+
* STRIPPING SGR INSIDE THE SELECTION IS NOT LAZINESS. An inner `\x1b[0m` — which
|
|
48
|
+
* `Markdown` and `cli-highlight` emit constantly — would cancel the highlight
|
|
49
|
+
* attribute mid-run and leave holes in it. Terminals repaint selected text in
|
|
50
|
+
* the selection's own colours for exactly this reason.
|
|
51
|
+
*
|
|
52
|
+
* `pad` is what gives a multi-row selection a straight right edge over short
|
|
53
|
+
* lines, and it measures with `columnsOf` — the same oracle `sliceColumns`
|
|
54
|
+
* charges with — rather than reaching for a third width function (I-12).
|
|
55
|
+
*/
|
|
56
|
+
export function paintRow(line, from, to, open) {
|
|
57
|
+
if (!(to > from))
|
|
58
|
+
return line;
|
|
59
|
+
const head = sliceColumns(line, 0, from);
|
|
60
|
+
const mid = stripAnsi(sliceColumns(line, from, to));
|
|
61
|
+
const pad = ' '.repeat(Math.max(0, to - from - columnsOf(mid)));
|
|
62
|
+
const tail = sliceColumns(line, to);
|
|
63
|
+
return `${head}${open}${mid}${pad}${SGR_RESET}${tail}`;
|
|
64
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The screen mirror (tui-selection-and-scroll-follow §7) — the single source of
|
|
3
|
+
* truth for what text is on which row.
|
|
4
|
+
*
|
|
5
|
+
* I-4 RIDES ON THIS OBJECT. The highlight and the clipboard both read the rows
|
|
6
|
+
* stored here, written by the same `decorate` call, so whatever changes under a
|
|
7
|
+
* selection is simultaneously what is highlighted and what would be copied. What
|
|
8
|
+
* you see is what you get BY CONSTRUCTION, rather than by a staleness check that
|
|
9
|
+
* can be wrong.
|
|
10
|
+
*
|
|
11
|
+
* `plain` is computed LAZILY. `decorate` runs on every frame Ink produces —
|
|
12
|
+
* 15-30 times a second during a run — while a copy happens when a human releases
|
|
13
|
+
* a mouse button, so stripping every row eagerly would spend H regex passes per
|
|
14
|
+
* frame to serve an event that may never come. The cache is dropped by `set`, so
|
|
15
|
+
* the laziness is invisible to callers and cannot go stale.
|
|
16
|
+
*/
|
|
17
|
+
export interface ScreenMirror {
|
|
18
|
+
/** Last frame, ANSI intact — what `decorate` paints over. */
|
|
19
|
+
readonly raw: readonly string[];
|
|
20
|
+
/** The same rows, ANSI stripped — what `selectedText` reads. */
|
|
21
|
+
readonly plain: readonly string[];
|
|
22
|
+
set(lines: readonly string[]): void;
|
|
23
|
+
}
|
|
24
|
+
export declare function createScreenMirror(): ScreenMirror;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The screen mirror (tui-selection-and-scroll-follow §7) — the single source of
|
|
3
|
+
* truth for what text is on which row.
|
|
4
|
+
*
|
|
5
|
+
* I-4 RIDES ON THIS OBJECT. The highlight and the clipboard both read the rows
|
|
6
|
+
* stored here, written by the same `decorate` call, so whatever changes under a
|
|
7
|
+
* selection is simultaneously what is highlighted and what would be copied. What
|
|
8
|
+
* you see is what you get BY CONSTRUCTION, rather than by a staleness check that
|
|
9
|
+
* can be wrong.
|
|
10
|
+
*
|
|
11
|
+
* `plain` is computed LAZILY. `decorate` runs on every frame Ink produces —
|
|
12
|
+
* 15-30 times a second during a run — while a copy happens when a human releases
|
|
13
|
+
* a mouse button, so stripping every row eagerly would spend H regex passes per
|
|
14
|
+
* frame to serve an event that may never come. The cache is dropped by `set`, so
|
|
15
|
+
* the laziness is invisible to callers and cannot go stale.
|
|
16
|
+
*/
|
|
17
|
+
import stripAnsi from 'strip-ansi';
|
|
18
|
+
export function createScreenMirror() {
|
|
19
|
+
let raw = [];
|
|
20
|
+
let plain = null;
|
|
21
|
+
return {
|
|
22
|
+
get raw() {
|
|
23
|
+
return raw;
|
|
24
|
+
},
|
|
25
|
+
get plain() {
|
|
26
|
+
if (plain === null)
|
|
27
|
+
plain = raw.map((line) => stripAnsi(line));
|
|
28
|
+
return plain;
|
|
29
|
+
},
|
|
30
|
+
set(lines) {
|
|
31
|
+
raw = lines;
|
|
32
|
+
plain = null;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The selection controller (tui-selection-and-scroll-follow §4.4). NON-REACT: it
|
|
3
|
+
* owns the mouse lifecycle, the 16 ms drag coalescer, the `decorate` hook and the
|
|
4
|
+
* copy-on-release, and it reaches the React tree only through the two callbacks
|
|
5
|
+
* `App` registers on mount.
|
|
6
|
+
*
|
|
7
|
+
* WHY IT IS NOT A COMPONENT (D-6). A React re-render per motion report goes
|
|
8
|
+
* through the render governor and the whole component tree; the frame pipeline
|
|
9
|
+
* repaints one to three rows with no commit at all. A fast drag across 40 columns
|
|
10
|
+
* therefore costs about three repaints of a few rows each, not forty full frames.
|
|
11
|
+
*
|
|
12
|
+
* THREE THINGS IN HERE FAIL SILENTLY AND PERMANENTLY IF DROPPED:
|
|
13
|
+
*
|
|
14
|
+
* - **I-11 · `hold` must always have a way out.** A press with no matching
|
|
15
|
+
* release freezes the viewport for the rest of the session: content anchoring
|
|
16
|
+
* never lets go, the resume timer's third condition never passes, and nothing
|
|
17
|
+
* is raised anywhere. Three ways to never get a release — the button comes up
|
|
18
|
+
* while the terminal does not have focus, the emulator swallows it, or the
|
|
19
|
+
* terminal fell back to X10, whose reports cannot express a release at all.
|
|
20
|
+
* So `hold` is released by a `release` report, by `clear()`, OR by the
|
|
21
|
+
* `holdMaxMs` watchdog, and the parser refuses to emit buttons from X10.
|
|
22
|
+
* - **I-9 · a highlight that survives the rows moving is a lie about what would
|
|
23
|
+
* be copied.** `App` clears on `shiftUp` change — the DERIVED form, which a
|
|
24
|
+
* new way to move the viewport cannot forget to join — and additionally on
|
|
25
|
+
* key, resize, overlay and `differ.invalidate()`.
|
|
26
|
+
* - **I-4 · the highlight and the copy read the SAME mirror**, written by the
|
|
27
|
+
* same `decorate` call. What you see is what you get by construction, rather
|
|
28
|
+
* than by a staleness check that can be wrong.
|
|
29
|
+
*/
|
|
30
|
+
import type { MouseSource } from '../../input/stdin-mouse-filter.js';
|
|
31
|
+
import type { Theme } from '../theme.js';
|
|
32
|
+
import type { TermCapabilities } from '../capabilities.js';
|
|
33
|
+
import type { CopyVia } from '../clipboard.js';
|
|
34
|
+
/**
|
|
35
|
+
* How long a nominally-held button may sit MOTIONLESS before the watchdog lets
|
|
36
|
+
* go of it (I-11). Reset by every motion report, so it only fires when the
|
|
37
|
+
* pointer has not moved for the whole window — which is indistinguishable from a
|
|
38
|
+
* lost release, and the failure it prevents is unrecoverable while the one it
|
|
39
|
+
* risks costs a re-drag (R-12).
|
|
40
|
+
*/
|
|
41
|
+
export declare const DEFAULT_HOLD_MAX_MS = 30000;
|
|
42
|
+
export interface SelectionControllerOptions {
|
|
43
|
+
source: MouseSource;
|
|
44
|
+
/**
|
|
45
|
+
* Ask the frame writer to re-paint. Returns whether anything reached the
|
|
46
|
+
* screen; `false` means the differ had no cache to diff against, and the
|
|
47
|
+
* caller falls back to `requestRedraw`.
|
|
48
|
+
*/
|
|
49
|
+
repaint: () => boolean;
|
|
50
|
+
/** `App`'s `redrawNonce`, for the invalidated case (§4.4.3). */
|
|
51
|
+
requestRedraw: () => void;
|
|
52
|
+
copy: (text: string) => CopyVia;
|
|
53
|
+
/** Toast sink, registered by `App` on mount (§6.3). */
|
|
54
|
+
onCopied: (via: CopyVia, lines: number, chars: number) => void;
|
|
55
|
+
theme: () => Theme;
|
|
56
|
+
caps: TermCapabilities;
|
|
57
|
+
/** Terminal width, for the right edge of a multi-row highlight. */
|
|
58
|
+
cols: () => number;
|
|
59
|
+
/** Static half of the gate: full-screen and `mouseSelect`. */
|
|
60
|
+
isSelectable?: () => boolean;
|
|
61
|
+
/** I-11. Default 30_000; `0` disables the watchdog (tests). */
|
|
62
|
+
holdMaxMs?: number;
|
|
63
|
+
}
|
|
64
|
+
export interface SelectionController {
|
|
65
|
+
/** Feed to `createFrameDiffer({ decorate })`. Mirrors, then paints. */
|
|
66
|
+
decorate(lines: string[]): string[];
|
|
67
|
+
/** Subscribe to `hold` transitions; `App` mirrors this into React state. */
|
|
68
|
+
onHoldChange(listener: (hold: boolean) => void): () => void;
|
|
69
|
+
/** Called by `App` for key / resize / overlay / clear, and by `onInvalidate`. */
|
|
70
|
+
clear(): void;
|
|
71
|
+
/** The dynamic half of the gate: `App` closes it while an overlay is open. */
|
|
72
|
+
setEnabled(enabled: boolean): void;
|
|
73
|
+
dispose(): void;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The seam between the non-React controller and the React tree.
|
|
77
|
+
*
|
|
78
|
+
* SAME SHAPE AS `UpdateBridge` (`cli.tsx`), and for the same reason: the
|
|
79
|
+
* controller is constructed before `render()` and must not hold React state,
|
|
80
|
+
* while the toast sink and the redraw nonce only exist once `App` has mounted.
|
|
81
|
+
* `App` assigns both fields in an effect and clears them on unmount.
|
|
82
|
+
*/
|
|
83
|
+
export interface SelectionBridge {
|
|
84
|
+
controller: SelectionController | null;
|
|
85
|
+
onCopied: ((via: CopyVia, lines: number, chars: number) => void) | null;
|
|
86
|
+
requestRedraw: (() => void) | null;
|
|
87
|
+
}
|
|
88
|
+
export declare function createSelectionController(options: SelectionControllerOptions): SelectionController;
|