@timurproko/a1 0.2.5-dev.683 → 0.2.5-dev.698
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/session-shell/prompt-suggestion-controller.d.ts +2 -0
- package/dist/app/session-shell/prompt-suggestion-controller.js +10 -0
- package/dist/app/session-shell/session-shell-root.js +15 -2
- package/dist/app/session-shell/session-shell.js +71 -15
- package/dist/app/session-shell/share-operation.d.ts +3 -0
- package/dist/app/session-shell/share-operation.js +11 -0
- package/dist/composition/settings-route-host.js +1 -1
- package/dist/contracts/presentation/index.d.ts +5 -0
- package/dist/contracts/presentation/index.js +2 -0
- package/dist/features/owned-ui/project-trust-dialog.js +7 -5
- package/dist/features/owned-ui/reference-screen-app.d.ts +2 -2
- package/dist/features/owned-ui/reference-screen-app.js +10 -6
- package/dist/features/owned-ui/settings-app.js +10 -5
- package/dist/integrations/pi/components/index.d.ts +3 -2
- package/dist/integrations/pi/components/index.js +2 -1
- package/dist/integrations/pi/components/modal-frame.d.ts +11 -1
- package/dist/integrations/pi/components/modal-frame.js +29 -1
- package/dist/integrations/pi/components/models-dialog.js +17 -17
- package/dist/integrations/pi/components/share-operation-dialog.d.ts +6 -0
- package/dist/integrations/pi/components/share-operation-dialog.js +40 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +15 -6
- package/dist/integrations/pi/components/shell-extension-ui.d.ts +3 -0
- package/dist/integrations/pi/components/shell-extension-ui.js +8 -4
- package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +0 -4
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +56 -34
- package/dist/integrations/pi/components/shell-session-info-reference.js +35 -2
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +3 -1
- package/dist/integrations/pi/components/shell-shared-facade.js +13 -1
- package/dist/integrations/pi/components/skills-dialog.js +19 -6
- package/dist/integrations/pi/components/theme.d.ts +8 -1
- package/dist/integrations/pi/components/theme.js +28 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +10 -2
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +20 -6
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +2 -2
- package/dist/integrations/pi/components/upstream/components/extension-input.js +4 -5
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +2 -3
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -2
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +4 -15
- package/dist/integrations/pi/components/upstream/components/session-selector.d.ts +2 -0
- package/dist/integrations/pi/components/upstream/components/session-selector.js +136 -109
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +8 -5
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +63 -26
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +29 -20
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +245 -210
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +13 -15
- package/dist/integrations/pi/engine/adapter.js +21 -2
- package/dist/integrations/pi/engine/workflow-runner.js +2 -1
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/apps/host.d.ts +4 -3
- package/dist/ui/apps/host.js +9 -4
- package/dist/ui/components/dialog-panel.js +6 -7
- package/dist/ui/components/index.d.ts +1 -1
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/list-view.d.ts +3 -3
- package/dist/ui/components/list-view.js +8 -9
- package/dist/ui/components/shortcut-hints.d.ts +2 -0
- package/dist/ui/components/shortcut-hints.js +17 -1
- package/dist/ui/components/theme.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CancellableLoader, Spacer, Text, } from "@earendil-works/pi-tui";
|
|
2
|
+
import { BorderedLoader, DynamicBorder } from "../startup-public.js";
|
|
3
|
+
import { adoptPiModalFrame, PiModalFrame } from "./modal-frame.js";
|
|
4
|
+
import { DIALOG_CLOSE_SHORTCUT_HINT, piTheme, renderPiModalShortcutHints } from "./theme.js";
|
|
5
|
+
import { componentPort, createTuiFacade, ensureTheme, } from "./shell-shared-facade.js";
|
|
6
|
+
/** Bare A1's titled share progress surface; the pinned comparison keeps its bordered loader. */
|
|
7
|
+
class ShareOperationDialog {
|
|
8
|
+
#loader;
|
|
9
|
+
#frame;
|
|
10
|
+
constructor(runtime, message) {
|
|
11
|
+
const theme = piTheme();
|
|
12
|
+
this.#loader = new CancellableLoader(createTuiFacade(runtime), text => theme.fg("accent", text), text => theme.fg("muted", text), message);
|
|
13
|
+
this.#frame = new PiModalFrame(new DynamicBorder(text => theme.fg("border", text)), [
|
|
14
|
+
new Text(theme.fg("accent", theme.bold("Share")), 0, 0),
|
|
15
|
+
this.#loader,
|
|
16
|
+
new Spacer(1),
|
|
17
|
+
new Text(renderPiModalShortcutHints([DIALOG_CLOSE_SHORTCUT_HINT]), 0, 0),
|
|
18
|
+
], new DynamicBorder(text => theme.fg("border", text)));
|
|
19
|
+
}
|
|
20
|
+
get signal() { return this.#loader.signal; }
|
|
21
|
+
invalidate() { this.#frame.invalidate(); }
|
|
22
|
+
render(width) { return this.#frame.render(width); }
|
|
23
|
+
handleInput(data) { this.#loader.handleInput(data); }
|
|
24
|
+
dispose() { this.#loader.dispose(); }
|
|
25
|
+
}
|
|
26
|
+
export function createPiShellShareOperationDialog(runtime, message) {
|
|
27
|
+
ensureTheme();
|
|
28
|
+
const dialog = new ShareOperationDialog(runtime, message);
|
|
29
|
+
return { ...componentPort(dialog), signal: dialog.signal };
|
|
30
|
+
}
|
|
31
|
+
export function createPiShellOperationLoader(runtime, message) {
|
|
32
|
+
ensureTheme();
|
|
33
|
+
const loader = new BorderedLoader(createTuiFacade(runtime), piTheme(), message, { cancellable: true });
|
|
34
|
+
adoptPiModalFrame(loader, {
|
|
35
|
+
topIndex: 0,
|
|
36
|
+
bottomIndex: loader.children.length - 1,
|
|
37
|
+
preInsetContent: loader.children[3] === undefined ? [] : [loader.children[3]],
|
|
38
|
+
});
|
|
39
|
+
return { ...componentPort(loader), signal: loader.signal };
|
|
40
|
+
}
|
|
@@ -4,11 +4,21 @@ import { PROMPT_HISTORY_EDITOR_REPLACEMENT } from "../../../contracts/owned-ui/i
|
|
|
4
4
|
import { KeybindingsManager } from "./upstream/adjacent/core/keybindings.js";
|
|
5
5
|
import { OwnedEditor } from "./upstream/components/owned-editor.js";
|
|
6
6
|
import { OwnedEditorUxInterception, createPromptSelectionInterceptor, editorVisualLineCount, } from "./owned-editor-ux.js";
|
|
7
|
-
import { PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
|
|
7
|
+
import { PINNED_PI_LAYOUT, piTheme, renderPiListSelection, } from "./theme.js";
|
|
8
8
|
import { createTuiFacade, ensureTheme, isAutocompleteProvider, } from "./shell-shared-facade.js";
|
|
9
9
|
import { SKILLS_COMMAND_NAME, collapseSkillCommands, createSkillsTunnelProvider, } from "./skills-command.js";
|
|
10
|
-
/** A selected autocomplete row:
|
|
10
|
+
/** A selected autocomplete row: its arrow/primary column, then the aligned description. */
|
|
11
11
|
const SELECTED_DESCRIBED_ROW = /^(→ .*?\S)(\s{2,}.*)$/u;
|
|
12
|
+
function renderOwnedAutocompleteSelection(text) {
|
|
13
|
+
const theme = piTheme();
|
|
14
|
+
const row = SELECTED_DESCRIBED_ROW.exec(text);
|
|
15
|
+
const primary = row?.[1] ?? text;
|
|
16
|
+
const hasArrow = primary.startsWith("→ ");
|
|
17
|
+
const arrow = hasArrow ? theme.fg("accent", "→ ") : "";
|
|
18
|
+
const label = theme.fg("text", hasArrow ? primary.slice(2) : primary);
|
|
19
|
+
const description = row === null ? "" : theme.fg("muted", row[2]);
|
|
20
|
+
return renderPiListSelection(arrow + label + description);
|
|
21
|
+
}
|
|
12
22
|
const PINNED_THINKING_SLASH_COMMAND = { name: "thinking", description: "Set thinking level", argumentHint: "<level>" };
|
|
13
23
|
const OWNED_THINKING_SLASH_COMMAND = { name: "thinking", description: "Set thinking level" };
|
|
14
24
|
const OWNED_LOGIN_SLASH_COMMAND = { name: "login", description: "Configure provider authentication" };
|
|
@@ -74,10 +84,9 @@ export function createPiShellEditor(options) {
|
|
|
74
84
|
return selectListTheme.scrollInfo(text);
|
|
75
85
|
},
|
|
76
86
|
selectedText: text => {
|
|
77
|
-
// Rationale:
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
return row === null ? selectListTheme.selectedText(text) : selectListTheme.selectedText(row[1]) + selectListTheme.description(row[2]);
|
|
87
|
+
// Rationale: bare A1 keeps the arrow accented, the actionable label readable, and descriptions muted
|
|
88
|
+
// on the same item-bounded blue surface used by its standard modal selectors.
|
|
89
|
+
return renderOwnedAutocompleteSelection(text);
|
|
81
90
|
},
|
|
82
91
|
},
|
|
83
92
|
}, keybindings, {
|
|
@@ -28,6 +28,9 @@ export interface PiExtensionUiBridgeHost {
|
|
|
28
28
|
}
|
|
29
29
|
export interface PiExtensionUiBridge {
|
|
30
30
|
readonly context: ExtensionUIContext;
|
|
31
|
+
input(title: string, placeholder: string, options?: {
|
|
32
|
+
readonly retainSurfaceOnSettle?: boolean;
|
|
33
|
+
}): Promise<string | undefined>;
|
|
31
34
|
reset(): void;
|
|
32
35
|
dispose(): void;
|
|
33
36
|
}
|
|
@@ -5,7 +5,7 @@ import { ExtensionSelectorComponent } from "./upstream/components/extension-sele
|
|
|
5
5
|
import { setKeybindings, Text, } from "@earendil-works/pi-tui";
|
|
6
6
|
import { KeybindingsManager, } from "./upstream/adjacent/core/keybindings.js";
|
|
7
7
|
import { PINNED_PI_LAYOUT, applyPiTheme, applyPiThemeInstance, getAvailablePiThemes, loadPiTheme, piTheme, } from "./theme.js";
|
|
8
|
-
import { componentPort, createTuiFacade, ensureTheme, isRecord, } from "./shell-shared-facade.js";
|
|
8
|
+
import { componentPort, createTuiFacade, ensureTheme, isRecord, withPiDialogCancel, } from "./shell-shared-facade.js";
|
|
9
9
|
/** Public ExtensionUIContext ported from pinned InteractiveMode without constructing it. */
|
|
10
10
|
export function createPiExtensionUiBridge(host) {
|
|
11
11
|
ensureTheme();
|
|
@@ -45,7 +45,7 @@ export function createPiExtensionUiBridge(host) {
|
|
|
45
45
|
disposers.delete(dispose);
|
|
46
46
|
};
|
|
47
47
|
};
|
|
48
|
-
const showInput = (create, options) => new Promise(resolve => {
|
|
48
|
+
const showInput = (create, options, retainSurfaceOnSettle = false) => new Promise(resolve => {
|
|
49
49
|
let settled = false;
|
|
50
50
|
let surface;
|
|
51
51
|
let untrack = () => { };
|
|
@@ -56,7 +56,10 @@ export function createPiExtensionUiBridge(host) {
|
|
|
56
56
|
if (activeCancel === cancel)
|
|
57
57
|
activeCancel = undefined;
|
|
58
58
|
untrack();
|
|
59
|
-
|
|
59
|
+
if (retainSurfaceOnSettle && activeSurface === surface)
|
|
60
|
+
activeSurface = undefined;
|
|
61
|
+
else
|
|
62
|
+
closeSurface(surface);
|
|
60
63
|
resolve(value);
|
|
61
64
|
};
|
|
62
65
|
const cancel = () => finish(undefined);
|
|
@@ -173,7 +176,7 @@ export function createPiExtensionUiBridge(host) {
|
|
|
173
176
|
component.dispose();
|
|
174
177
|
return;
|
|
175
178
|
}
|
|
176
|
-
surface = componentPort(component);
|
|
179
|
+
surface = withPiDialogCancel(componentPort(component), cancelCustom);
|
|
177
180
|
activeCancel = cancelCustom;
|
|
178
181
|
if (options?.overlay) {
|
|
179
182
|
const overlayOptions = typeof options.overlayOptions === "function" ? options.overlayOptions() : options.overlayOptions;
|
|
@@ -247,6 +250,7 @@ export function createPiExtensionUiBridge(host) {
|
|
|
247
250
|
};
|
|
248
251
|
return {
|
|
249
252
|
context,
|
|
253
|
+
input: (title, placeholder, options) => showInput((resolve, cancel) => componentPort(new ExtensionInputComponent(title, placeholder, resolve, cancel, { tui })), undefined, options?.retainSurfaceOnSettle === true),
|
|
250
254
|
reset,
|
|
251
255
|
dispose() {
|
|
252
256
|
reset();
|
|
@@ -110,10 +110,6 @@ export interface PiShellLoginDialogPort extends PiShellComponentPort {
|
|
|
110
110
|
export declare function createPiShellLoginDialog(runtime: Pick<PiShellEditorOptions, "getColumns" | "getRows" | "requestRender">, providerId: string, onComplete: (success: boolean, message?: string) => void, providerName?: string, title?: string): PiShellLoginDialogPort;
|
|
111
111
|
export declare function createPiShellArmin(runtime: Pick<PiShellEditorOptions, "getColumns" | "getRows" | "requestRender">): PiShellComponentPort;
|
|
112
112
|
export declare function createPiShellEarendilAnnouncement(): PiShellComponentPort;
|
|
113
|
-
export interface PiShellOperationLoaderPort extends PiShellComponentPort {
|
|
114
|
-
readonly signal: AbortSignal;
|
|
115
|
-
}
|
|
116
|
-
export declare function createPiShellOperationLoader(runtime: Pick<PiShellEditorOptions, "getColumns" | "getRows" | "requestRender">, message: string): PiShellOperationLoaderPort;
|
|
117
113
|
export declare function createPiShellReloadBox(): PiShellComponentPort;
|
|
118
114
|
export interface PiShellAuthProviderOption extends PiShellSelectorOption {
|
|
119
115
|
readonly providerId: string;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { ArminComponent,
|
|
2
|
-
import { EarendilAnnouncementComponent
|
|
1
|
+
import { ArminComponent, DynamicBorder, getSelectListTheme, LoginDialogComponent, ModelSelectorComponent, OAuthSelectorComponent, SettingsSelectorComponent, ShowImagesSelectorComponent, ThemeSelectorComponent, UserMessageSelectorComponent, } from "../startup-public.js";
|
|
2
|
+
import { EarendilAnnouncementComponent } from "./upstream/components/earendil-announcement.js";
|
|
3
|
+
import { ExtensionSelectorComponent } from "./upstream/components/extension-selector.js";
|
|
3
4
|
import { Box, Container, SelectList, Spacer, Text, } from "@earendil-works/pi-tui";
|
|
4
|
-
import { ScopedModelsSelectorComponent
|
|
5
|
-
import { adoptPiModalFrame, adoptPiModalHeader } from "./modal-frame.js";
|
|
6
|
-
import { ModelsDialogComponent
|
|
7
|
-
import { TrustSelectorComponent
|
|
8
|
-
import { PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
|
|
5
|
+
import { ScopedModelsSelectorComponent } from "./upstream/components/scoped-models-selector.js";
|
|
6
|
+
import { addPiModalSelectFooter, adoptPiModalFrame, adoptPiModalHeader } from "./modal-frame.js";
|
|
7
|
+
import { ModelsDialogComponent } from "./models-dialog.js";
|
|
8
|
+
import { TrustSelectorComponent } from "./upstream/components/trust-selector.js";
|
|
9
|
+
import { canonicalizePiSettingsHint, DIALOG_CLOSE_SHORTCUT_HINT, PINNED_PI_LAYOUT, piTheme, renderPiModalShortcutHints, } from "./theme.js";
|
|
9
10
|
import { componentFromPort, componentPort, createTuiFacade, ensureTheme, isRecord, } from "./shell-shared-facade.js";
|
|
10
11
|
export { createPiShellSkillsSelector } from "./skills-dialog.js";
|
|
11
12
|
export function createPiShellSelector(options) {
|
|
@@ -16,11 +17,20 @@ export function createPiShellSelector(options) {
|
|
|
16
17
|
list.onSelect = item => options.onSelect?.(item.value);
|
|
17
18
|
if (options.onCancel !== undefined)
|
|
18
19
|
list.onCancel = options.onCancel;
|
|
19
|
-
if (!options.title)
|
|
20
|
+
if (!options.title && options.onCancel === undefined)
|
|
20
21
|
return componentPort(list);
|
|
21
22
|
const container = new Container();
|
|
22
|
-
|
|
23
|
+
if (options.title)
|
|
24
|
+
container.addChild(new Text(piTheme().fg("accent", piTheme().bold(options.title)), PINNED_PI_LAYOUT.contentPaddingX, 0));
|
|
23
25
|
container.addChild(list);
|
|
26
|
+
if (options.onCancel !== undefined) {
|
|
27
|
+
container.addChild(new Spacer(1));
|
|
28
|
+
container.addChild(new Text(renderPiModalShortcutHints([
|
|
29
|
+
{ key: "↑↓", action: "navigate" },
|
|
30
|
+
{ key: "enter", action: "select" },
|
|
31
|
+
DIALOG_CLOSE_SHORTCUT_HINT,
|
|
32
|
+
]), 0, 0));
|
|
33
|
+
}
|
|
24
34
|
return componentPort(container, data => list.handleInput(data));
|
|
25
35
|
}
|
|
26
36
|
export function createPiShellSettingsSelector(options) {
|
|
@@ -68,7 +78,19 @@ export function createPiShellSettingsSelector(options) {
|
|
|
68
78
|
};
|
|
69
79
|
const selector = new SettingsSelectorComponent(options.config, callbacks);
|
|
70
80
|
const settingsList = selector.getSettingsList();
|
|
71
|
-
|
|
81
|
+
const port = componentPort(selector, data => settingsList.handleInput(data));
|
|
82
|
+
return {
|
|
83
|
+
...port,
|
|
84
|
+
// Rationale: Pi's nested settings components own their hint rows and do not expose hint entries.
|
|
85
|
+
// Rebuild only the footer row so the canonical close entry survives narrow clipping.
|
|
86
|
+
render(width) {
|
|
87
|
+
const rows = [...port.render(width)];
|
|
88
|
+
const hintIndex = rows.length - 2;
|
|
89
|
+
if (rows[hintIndex] !== undefined)
|
|
90
|
+
rows[hintIndex] = canonicalizePiSettingsHint(rows[hintIndex], width);
|
|
91
|
+
return rows;
|
|
92
|
+
},
|
|
93
|
+
};
|
|
72
94
|
}
|
|
73
95
|
export function createPiShellModelSelector(options) {
|
|
74
96
|
ensureTheme();
|
|
@@ -139,6 +161,7 @@ export function createPiShellUserMessageSelector(messages, onSelect, onCancel, i
|
|
|
139
161
|
ensureTheme();
|
|
140
162
|
const selector = new UserMessageSelectorComponent(messages.map(message => ({ id: message.id, text: message.label })), onSelect, onCancel, initialSelectedId);
|
|
141
163
|
const list = selector.getMessageList();
|
|
164
|
+
addPiModalSelectFooter(selector);
|
|
142
165
|
adoptPiModalFrame(selector, { topIndex: 4, bottomIndex: selector.children.length - 1 });
|
|
143
166
|
return componentPort(selector, data => list.handleInput(data));
|
|
144
167
|
}
|
|
@@ -146,6 +169,9 @@ export function createPiShellLoginDialog(runtime, providerId, onComplete, provid
|
|
|
146
169
|
ensureTheme();
|
|
147
170
|
const dialog = new LoginDialogComponent(createTuiFacade(runtime), providerId, onComplete, providerName, title);
|
|
148
171
|
const preInsetTitle = dialog.children[1];
|
|
172
|
+
const content = dialog.children[2];
|
|
173
|
+
dialog.children.splice(dialog.children.length - 1, 0, new Spacer(1), new Text(renderPiModalShortcutHints([DIALOG_CLOSE_SHORTCUT_HINT]), 0, 0));
|
|
174
|
+
const removeDependencyHint = () => { content.children.pop(); };
|
|
149
175
|
const header = adoptPiModalHeader(dialog, 0, 1);
|
|
150
176
|
adoptPiModalFrame(dialog, {
|
|
151
177
|
topIndex: 0,
|
|
@@ -157,11 +183,23 @@ export function createPiShellLoginDialog(runtime, providerId, onComplete, provid
|
|
|
157
183
|
...componentPort(dialog),
|
|
158
184
|
showAuth: (url, instructions) => dialog.showAuth(url, instructions),
|
|
159
185
|
showDeviceCode: info => dialog.showDeviceCode(info),
|
|
160
|
-
showManualInput
|
|
161
|
-
|
|
186
|
+
showManualInput(message) {
|
|
187
|
+
const result = dialog.showManualInput(message);
|
|
188
|
+
removeDependencyHint();
|
|
189
|
+
return result;
|
|
190
|
+
},
|
|
191
|
+
showPrompt(message, placeholder) {
|
|
192
|
+
const result = dialog.showPrompt(message, placeholder);
|
|
193
|
+
removeDependencyHint();
|
|
194
|
+
return result;
|
|
195
|
+
},
|
|
162
196
|
showDetails: lines => dialog.showDetails([...lines]),
|
|
163
|
-
|
|
164
|
-
|
|
197
|
+
// Rationale: The persistent canonical footer replaces the dependency's optional close hint.
|
|
198
|
+
showInfo: (message, links = []) => dialog.showInfo(message, [...links], false),
|
|
199
|
+
showWaiting(message) {
|
|
200
|
+
dialog.showWaiting(message);
|
|
201
|
+
removeDependencyHint();
|
|
202
|
+
},
|
|
165
203
|
showProgress: message => dialog.showProgress(message),
|
|
166
204
|
};
|
|
167
205
|
}
|
|
@@ -173,16 +211,6 @@ export function createPiShellEarendilAnnouncement() {
|
|
|
173
211
|
ensureTheme();
|
|
174
212
|
return componentPort(new EarendilAnnouncementComponent());
|
|
175
213
|
}
|
|
176
|
-
export function createPiShellOperationLoader(runtime, message) {
|
|
177
|
-
ensureTheme();
|
|
178
|
-
const loader = new BorderedLoader(createTuiFacade(runtime), piTheme(), message, { cancellable: true });
|
|
179
|
-
adoptPiModalFrame(loader, {
|
|
180
|
-
topIndex: 0,
|
|
181
|
-
bottomIndex: loader.children.length - 1,
|
|
182
|
-
preInsetContent: loader.children[3] === undefined ? [] : [loader.children[3]],
|
|
183
|
-
});
|
|
184
|
-
return { ...componentPort(loader), signal: loader.signal };
|
|
185
|
-
}
|
|
186
214
|
export function createPiShellReloadBox() {
|
|
187
215
|
ensureTheme();
|
|
188
216
|
const container = new Container();
|
|
@@ -207,6 +235,7 @@ export function createPiShellAuthProviderSelector(mode, providers, onSelect, onC
|
|
|
207
235
|
if (selected)
|
|
208
236
|
onSelect(selected.id);
|
|
209
237
|
}, onCancel, initialSearchInput);
|
|
238
|
+
addPiModalSelectFooter(selector);
|
|
210
239
|
const preInsetContent = [selector.children[2], selector.children[6]];
|
|
211
240
|
const header = adoptPiModalHeader(selector, 0, 2);
|
|
212
241
|
adoptPiModalFrame(selector, {
|
|
@@ -219,21 +248,13 @@ export function createPiShellAuthProviderSelector(mode, providers, onSelect, onC
|
|
|
219
248
|
}
|
|
220
249
|
export function createPiShellExtensionSelector(title, options, onSelect, onCancel) {
|
|
221
250
|
ensureTheme();
|
|
222
|
-
|
|
223
|
-
const preInsetContent = [selector.children[2], selector.children[4], selector.children[6]];
|
|
224
|
-
const header = adoptPiModalHeader(selector, 0, 2);
|
|
225
|
-
adoptPiModalFrame(selector, {
|
|
226
|
-
topIndex: 0,
|
|
227
|
-
bottomIndex: selector.children.length - 1,
|
|
228
|
-
header,
|
|
229
|
-
preInsetContent,
|
|
230
|
-
});
|
|
231
|
-
return componentPort(selector);
|
|
251
|
+
return componentPort(new ExtensionSelectorComponent(title, [...options], onSelect, onCancel));
|
|
232
252
|
}
|
|
233
253
|
export function createPiShellThemeSelector(currentTheme, onSelect, onCancel, onPreview) {
|
|
234
254
|
ensureTheme();
|
|
235
255
|
const selector = new ThemeSelectorComponent(currentTheme, onSelect, onCancel, onPreview);
|
|
236
256
|
const list = selector.getSelectList();
|
|
257
|
+
addPiModalSelectFooter(selector);
|
|
237
258
|
adoptPiModalFrame(selector, { topIndex: 0, bottomIndex: selector.children.length - 1 });
|
|
238
259
|
return componentPort(selector, data => list.handleInput(data));
|
|
239
260
|
}
|
|
@@ -241,6 +262,7 @@ export function createPiShellShowImagesSelector(currentValue, onSelect, onCancel
|
|
|
241
262
|
ensureTheme();
|
|
242
263
|
const selector = new ShowImagesSelectorComponent(currentValue, onSelect, onCancel);
|
|
243
264
|
const list = selector.getSelectList();
|
|
265
|
+
addPiModalSelectFooter(selector);
|
|
244
266
|
adoptPiModalFrame(selector, { topIndex: 0, bottomIndex: selector.children.length - 1 });
|
|
245
267
|
return componentPort(selector, data => list.handleInput(data));
|
|
246
268
|
}
|
|
@@ -1,7 +1,40 @@
|
|
|
1
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
1
|
+
import { sliceByColumn, Text, visibleWidth } from "@earendil-works/pi-tui";
|
|
2
2
|
import { formatCacheWarmingStatus } from "./shell-presenters-info.js";
|
|
3
3
|
import { ensureTheme, formatSessionTokens } from "./shell-shared-facade.js";
|
|
4
4
|
import { piTheme } from "./theme.js";
|
|
5
|
+
/** Splits a plain path by visible columns, preserving every source grapheme in order. */
|
|
6
|
+
function splitSessionFile(sessionFile, firstWidth, continuationWidth) {
|
|
7
|
+
const rows = [];
|
|
8
|
+
let remaining = sessionFile;
|
|
9
|
+
let rowWidth = firstWidth;
|
|
10
|
+
while (remaining) {
|
|
11
|
+
let row = sliceByColumn(remaining, 0, rowWidth, true);
|
|
12
|
+
if (!row && rowWidth !== continuationWidth) {
|
|
13
|
+
rows.push("");
|
|
14
|
+
rowWidth = continuationWidth;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
// Compatibility: a one-column frame cannot fit a wide grapheme; preserve it for outer truncation.
|
|
18
|
+
if (!row)
|
|
19
|
+
row = sliceByColumn(remaining, 0, rowWidth);
|
|
20
|
+
rows.push(row);
|
|
21
|
+
remaining = remaining.slice(row.length);
|
|
22
|
+
rowWidth = continuationWidth;
|
|
23
|
+
}
|
|
24
|
+
return rows.length > 0 ? rows : [""];
|
|
25
|
+
}
|
|
26
|
+
/** Keeps the file value on its labelled row before using full continuation rows. */
|
|
27
|
+
function sessionFileRows(sessionFile, width) {
|
|
28
|
+
// Compatibility: match Text's reduced-padding rule so these explicit breaks survive its renderer.
|
|
29
|
+
const paddingX = Math.min(1, Math.max(0, Math.floor((width - 1) / 2)));
|
|
30
|
+
const contentWidth = Math.max(1, width - paddingX * 2);
|
|
31
|
+
const label = piTheme().fg("dim", "File:");
|
|
32
|
+
const firstValueWidth = Math.max(0, contentWidth - visibleWidth("File: "));
|
|
33
|
+
const [first = "", ...continuation] = splitSessionFile(sessionFile, firstValueWidth, contentWidth);
|
|
34
|
+
return firstValueWidth > 0
|
|
35
|
+
? [`${label} ${first}`, ...continuation]
|
|
36
|
+
: [label, ...continuation];
|
|
37
|
+
}
|
|
5
38
|
/** Renders the pinned report values while leaving group names semantic for the owned screen. */
|
|
6
39
|
export function renderPiShellSessionInfoReferenceDocument(presentation, width) {
|
|
7
40
|
ensureTheme();
|
|
@@ -9,7 +42,7 @@ export function renderPiShellSessionInfoReferenceDocument(presentation, width) {
|
|
|
9
42
|
const render = (rows) => new Text(rows.join("\n"), 1, 0).render(width);
|
|
10
43
|
const preamble = [
|
|
11
44
|
...(sessionName ? [`${piTheme().fg("dim", "Name:")} ${sessionName}`] : []),
|
|
12
|
-
|
|
45
|
+
...sessionFileRows(stats.sessionFile ?? "In-memory", width),
|
|
13
46
|
`${piTheme().fg("dim", "ID:")} ${stats.sessionId}`,
|
|
14
47
|
];
|
|
15
48
|
const messages = [
|
|
@@ -46,7 +46,7 @@ export interface PiShellEditorPort extends PiShellComponentPort {
|
|
|
46
46
|
keybindingConfig(): KeybindingsConfig;
|
|
47
47
|
reloadKeybindings(): void;
|
|
48
48
|
/** Uses Pi's terminal decoder rather than assuming one terminal escape spelling. */
|
|
49
|
-
matchesTerminalKey(data: string, key: "home" | "end" | "ctrl+home" | "ctrl+end" | "alt+home" | "ctrl+v"): boolean;
|
|
49
|
+
matchesTerminalKey(data: string, key: "home" | "end" | "pageUp" | "pageDown" | "ctrl+home" | "ctrl+end" | "alt+home" | "ctrl+v"): boolean;
|
|
50
50
|
getText(): string;
|
|
51
51
|
setText(text: string): void;
|
|
52
52
|
insertText(text: string): void;
|
|
@@ -227,6 +227,8 @@ export declare function createTuiFacade(options: Pick<PiShellEditorOptions, "get
|
|
|
227
227
|
readonly onSubmit?: (text: string) => void;
|
|
228
228
|
}): TUI;
|
|
229
229
|
export declare function componentPort(component: Component, handleInput?: (data: string) => void): PiShellComponentPort;
|
|
230
|
+
/** Gives an extension-hosted dialog the shell's implicit close alias without rewriting its component. */
|
|
231
|
+
export declare function withPiDialogCancel(port: PiShellComponentPort, onCancel: () => void): PiShellComponentPort;
|
|
230
232
|
export declare function componentFromPort(port: PiShellComponentPort): Component;
|
|
231
233
|
export declare function formatSessionTokens(count: number): string;
|
|
232
234
|
export declare function isAutocompleteProvider(value: unknown): value is AutocompleteProvider;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { hyperlink, setKeybindings, visibleWidth, truncateToWidth, } from "@earendil-works/pi-tui";
|
|
1
|
+
import { hyperlink, matchesKey, setKeybindings, visibleWidth, truncateToWidth, } from "@earendil-works/pi-tui";
|
|
2
2
|
import { KeybindingsManager, } from "./upstream/adjacent/core/keybindings.js";
|
|
3
3
|
import { ensurePiTheme, } from "./theme.js";
|
|
4
4
|
/** Terminal-cell width authority used by Pi-rendered component rows. */
|
|
@@ -61,6 +61,18 @@ export function componentPort(component, handleInput) {
|
|
|
61
61
|
...(typeof disposable.dispose === "function" ? { dispose: () => disposable.dispose?.() } : {}),
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
+
/** Gives an extension-hosted dialog the shell's implicit close alias without rewriting its component. */
|
|
65
|
+
export function withPiDialogCancel(port, onCancel) {
|
|
66
|
+
return {
|
|
67
|
+
...port,
|
|
68
|
+
handleInput: data => {
|
|
69
|
+
if (matchesKey(data, "ctrl+c"))
|
|
70
|
+
onCancel();
|
|
71
|
+
else
|
|
72
|
+
port.handleInput?.(data);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
64
76
|
export function componentFromPort(port) {
|
|
65
77
|
return {
|
|
66
78
|
render: width => [...port.render(width)],
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { DynamicBorder } from "../startup-public.js";
|
|
2
2
|
import { Container, getKeybindings, Input, Spacer, Text } from "@earendil-works/pi-tui";
|
|
3
3
|
import { addPiModalHeader, adoptPiModalFrame } from "./modal-frame.js";
|
|
4
|
-
import { PINNED_PI_LAYOUT, piTheme, renderPiModalShortcutHints } from "./theme.js";
|
|
4
|
+
import { DIALOG_CLOSE_SHORTCUT_HINT, PINNED_PI_LAYOUT, piTheme, renderPiModalListRow, renderPiModalShortcutHints, } from "./theme.js";
|
|
5
5
|
import { componentPort, ensureTheme, piShellTruncateToWidth, piShellVisibleWidth } from "./shell-shared-facade.js";
|
|
6
|
-
import {
|
|
6
|
+
import { skillMatchesQuery } from "./skills-command.js";
|
|
7
7
|
/** A single line clipped at the viewport width: no wrap, no ellipsis, padded to the full width. */
|
|
8
8
|
class ClippedLine {
|
|
9
9
|
#text;
|
|
@@ -14,10 +14,22 @@ class ClippedLine {
|
|
|
14
14
|
return [line + " ".repeat(Math.max(0, width - piShellVisibleWidth(line)))];
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
class SkillRow {
|
|
18
|
+
#content;
|
|
19
|
+
#selected;
|
|
20
|
+
constructor(content, selected) {
|
|
21
|
+
this.#content = content;
|
|
22
|
+
this.#selected = selected;
|
|
23
|
+
}
|
|
24
|
+
invalidate() { }
|
|
25
|
+
render(width) {
|
|
26
|
+
return [renderPiModalListRow(this.#content, width, this.#selected)];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
17
29
|
/**
|
|
18
30
|
* The A1-owned searchable Skills dialog. Its composition is the model selector's (border, spacer,
|
|
19
31
|
* search input, spacer, list, spacer, hint footer, border) built from public pi-tui components; its
|
|
20
|
-
* content follows the v2 skills extension: an accent bold title,
|
|
32
|
+
* content follows the v2 skills extension: an accent bold title, bare skill-name rows, the selected
|
|
21
33
|
* skill's description cut to one line under the rows, the pinned scroll counter on overflow,
|
|
22
34
|
* and the two empty states. Enter applies the selected skill with no arguments; the query is never appended.
|
|
23
35
|
*/
|
|
@@ -47,7 +59,7 @@ class SkillsSelectorComponent extends Container {
|
|
|
47
59
|
this.addChild(new Text(renderPiModalShortcutHints([
|
|
48
60
|
{ key: "↑↓", action: "navigate" },
|
|
49
61
|
{ key: bindings.getKeys("tui.select.confirm").join("/"), action: "select" },
|
|
50
|
-
|
|
62
|
+
DIALOG_CLOSE_SHORTCUT_HINT,
|
|
51
63
|
]), 0, 0));
|
|
52
64
|
this.addChild(new DynamicBorder());
|
|
53
65
|
adoptPiModalFrame(this, { topIndex: 0, bottomIndex: this.children.length - 1, header });
|
|
@@ -108,8 +120,9 @@ class SkillsSelectorComponent extends Container {
|
|
|
108
120
|
for (let index = start; index < end; index++) {
|
|
109
121
|
const skill = this.#filtered[index];
|
|
110
122
|
const selected = index === this.#selectedIndex;
|
|
111
|
-
const label =
|
|
112
|
-
|
|
123
|
+
const label = skill.name;
|
|
124
|
+
const content = `${selected ? theme.fg("accent", "→ ") : " "}${selected ? theme.fg("text", label) : label}`;
|
|
125
|
+
this.#listContainer.addChild(new SkillRow(content, selected));
|
|
113
126
|
}
|
|
114
127
|
if (start > 0 || end < this.#filtered.length) {
|
|
115
128
|
this.#listContainer.addChild(new Text(theme.fg("muted", ` (${this.#selectedIndex + 1}/${this.#filtered.length})`), 0, 0));
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import { type SemanticShortcutHint } from "../../../contracts/presentation/index.js";
|
|
1
|
+
import { DIALOG_CLOSE_SHORTCUT_HINT, displayShortcutKeyLabel, type SemanticShortcutHint } from "../../../contracts/presentation/index.js";
|
|
2
2
|
export * from "./upstream/theme/theme.js";
|
|
3
3
|
export type PiModalShortcutHint = SemanticShortcutHint;
|
|
4
|
+
export { DIALOG_CLOSE_SHORTCUT_HINT, displayShortcutKeyLabel };
|
|
5
|
+
/** Paint one selected bare-A1 list item with the standard blue selection surface. */
|
|
6
|
+
export declare function renderPiListSelection(content: string): string;
|
|
7
|
+
/** One standard bare-A1 modal row, clipped before its item-bounded selection is painted. */
|
|
8
|
+
export declare function renderPiModalListRow(content: string, width: number, selected: boolean): string;
|
|
4
9
|
/** The common bare-A1 modal footer: quiet display-cased keys, muted names, and whitespace-only gaps. */
|
|
5
10
|
export declare function renderPiModalShortcutHints(entries: readonly PiModalShortcutHint[], indent?: number): string;
|
|
11
|
+
/** Adapts Pi's closed settings footer while reserving the canonical close suffix. */
|
|
12
|
+
export declare function canonicalizePiSettingsHint(row: string, width: number): string;
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import { renderSemanticShortcutHints, } from "../../../contracts/presentation/index.js";
|
|
1
|
+
import { DIALOG_CLOSE_SHORTCUT_HINT, displayShortcutKeyLabel, renderSemanticShortcutHints, } from "../../../contracts/presentation/index.js";
|
|
2
|
+
import { stripTerminalSequences, truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
3
|
import { piTheme } from "./upstream/theme/theme.js";
|
|
3
4
|
export * from "./upstream/theme/theme.js";
|
|
5
|
+
export { DIALOG_CLOSE_SHORTCUT_HINT, displayShortcutKeyLabel };
|
|
6
|
+
/** Paint one selected bare-A1 list item with the standard blue selection surface. */
|
|
7
|
+
export function renderPiListSelection(content) {
|
|
8
|
+
return piTheme().bg("selectedBg", content);
|
|
9
|
+
}
|
|
10
|
+
/** One standard bare-A1 modal row, clipped before its item-bounded selection is painted. */
|
|
11
|
+
export function renderPiModalListRow(content, width, selected) {
|
|
12
|
+
const clipped = truncateToWidth(content, Math.max(0, width), "");
|
|
13
|
+
return selected ? renderPiListSelection(clipped) : clipped;
|
|
14
|
+
}
|
|
4
15
|
/** The common bare-A1 modal footer: quiet display-cased keys, muted names, and whitespace-only gaps. */
|
|
5
16
|
export function renderPiModalShortcutHints(entries, indent = 0) {
|
|
6
17
|
const theme = piTheme();
|
|
@@ -9,3 +20,19 @@ export function renderPiModalShortcutHints(entries, indent = 0) {
|
|
|
9
20
|
action: text => theme.fg("muted", text),
|
|
10
21
|
}, indent);
|
|
11
22
|
}
|
|
23
|
+
/** Adapts Pi's closed settings footer while reserving the canonical close suffix. */
|
|
24
|
+
export function canonicalizePiSettingsHint(row, width) {
|
|
25
|
+
const plain = stripTerminalSequences(row);
|
|
26
|
+
const body = plain.includes("Type to search") ? " Type to search · Enter/Space to change"
|
|
27
|
+
: plain.includes("Enter/Space to change") ? " Enter/Space to change"
|
|
28
|
+
: plain.includes("Type to filter") ? " Type to filter · Enter to select"
|
|
29
|
+
: plain.includes("Enter to select") && plain.includes("Esc") ? " Enter to select"
|
|
30
|
+
: /^\s*(?:Type to|Enter(?:\/Space)? to)/u.test(plain) ? plain.replace(/\.{3}$/u, "") : null;
|
|
31
|
+
if (body === null)
|
|
32
|
+
return row;
|
|
33
|
+
const close = `${displayShortcutKeyLabel(DIALOG_CLOSE_SHORTCUT_HINT.key)} ${DIALOG_CLOSE_SHORTCUT_HINT.action}`;
|
|
34
|
+
if (width <= close.length)
|
|
35
|
+
return piTheme().fg("dim", truncateToWidth(close, width, ""));
|
|
36
|
+
const bodyWidth = width - close.length - 3;
|
|
37
|
+
return piTheme().fg("dim", bodyWidth <= 0 ? close : `${truncateToWidth(body, bodyWidth, "…")} · ${close}`);
|
|
38
|
+
}
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
* packages/coding-agent/src/core/keybindings.ts.
|
|
4
4
|
* Modifications: Mechanical source port with Node import prefixes, public package-root agent-directory
|
|
5
5
|
* resolution, and an opt-in bare-A1 input profile including Ctrl+L level cycling, unbound model
|
|
6
|
-
* selection,
|
|
6
|
+
* selection, Tab tree-filter cycling, tree-local navigation aliases, cross-platform Alt+Up
|
|
7
|
+
* queued-message restoration, and an implicit Ctrl+C selection-cancel alias omitted from shortcut
|
|
8
|
+
* labels.
|
|
7
9
|
* Deviations: keybindings-public-config-boundary, owned-level-cycle-shortcut,
|
|
8
|
-
* owned-input-keybinding-aliases.
|
|
10
|
+
* owned-input-keybinding-aliases, owned-session-tree-dialog, owned-dialog-ctrl-c-cancel.
|
|
9
11
|
*/
|
|
10
12
|
import { type Keybinding, type KeybindingDefinitions, type KeybindingsConfig, type KeyId, KeybindingsManager as TuiKeybindingsManager } from "@earendil-works/pi-tui";
|
|
11
13
|
export interface AppKeybindings {
|
|
@@ -58,6 +60,10 @@ export interface AppKeybindings {
|
|
|
58
60
|
"owned.editor.redo": true;
|
|
59
61
|
"owned.editor.extendLeft": true;
|
|
60
62
|
"owned.editor.extendRight": true;
|
|
63
|
+
"owned.tree.collapse": true;
|
|
64
|
+
"owned.tree.expand": true;
|
|
65
|
+
"owned.tree.first": true;
|
|
66
|
+
"owned.tree.last": true;
|
|
61
67
|
}
|
|
62
68
|
export type AppKeybinding = keyof AppKeybindings;
|
|
63
69
|
export declare function useWindowsKeybindings(platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): boolean;
|
|
@@ -437,6 +443,8 @@ export declare class KeybindingsManager extends TuiKeybindingsManager {
|
|
|
437
443
|
static create(agentDir?: string): KeybindingsManager;
|
|
438
444
|
static fromOwnedBindings(bindings?: KeybindingsConfig): KeybindingsManager;
|
|
439
445
|
static createForOwnedInput(agentDir?: string): KeybindingsManager;
|
|
446
|
+
matches(data: string, keybinding: Keybinding): boolean;
|
|
447
|
+
getKeys(keybinding: Keybinding): KeyId[];
|
|
440
448
|
private static createWithDefinitions;
|
|
441
449
|
reload(): void;
|
|
442
450
|
getEffectiveConfig(): KeybindingsConfig;
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
* packages/coding-agent/src/core/keybindings.ts.
|
|
4
4
|
* Modifications: Mechanical source port with Node import prefixes, public package-root agent-directory
|
|
5
5
|
* resolution, and an opt-in bare-A1 input profile including Ctrl+L level cycling, unbound model
|
|
6
|
-
* selection,
|
|
6
|
+
* selection, Tab tree-filter cycling, tree-local navigation aliases, cross-platform Alt+Up
|
|
7
|
+
* queued-message restoration, and an implicit Ctrl+C selection-cancel alias omitted from shortcut
|
|
8
|
+
* labels.
|
|
7
9
|
* Deviations: keybindings-public-config-boundary, owned-level-cycle-shortcut,
|
|
8
|
-
* owned-input-keybinding-aliases.
|
|
10
|
+
* owned-input-keybinding-aliases, owned-session-tree-dialog, owned-dialog-ctrl-c-cancel.
|
|
9
11
|
*/
|
|
10
|
-
import { TUI_KEYBINDINGS, KeybindingsManager as TuiKeybindingsManager, } from "@earendil-works/pi-tui";
|
|
12
|
+
import { matchesKey, TUI_KEYBINDINGS, KeybindingsManager as TuiKeybindingsManager, } from "@earendil-works/pi-tui";
|
|
11
13
|
import { existsSync, readFileSync } from "node:fs";
|
|
12
14
|
import { join } from "node:path";
|
|
13
15
|
import { getAgentDir } from "../../../../startup-public.js";
|
|
@@ -185,6 +187,7 @@ const OWNED_INPUT_KEYBINDINGS = {
|
|
|
185
187
|
"app.thinking.cycle": { ...KEYBINDINGS["app.thinking.cycle"], defaultKeys: "ctrl+l" },
|
|
186
188
|
"app.model.select": { ...KEYBINDINGS["app.model.select"], defaultKeys: [] },
|
|
187
189
|
"app.message.dequeue": { ...KEYBINDINGS["app.message.dequeue"], defaultKeys: "alt+up" },
|
|
190
|
+
"app.tree.filter.cycleForward": { ...KEYBINDINGS["app.tree.filter.cycleForward"], defaultKeys: "tab" },
|
|
188
191
|
"tui.editor.deleteWordBackward": {
|
|
189
192
|
...KEYBINDINGS["tui.editor.deleteWordBackward"],
|
|
190
193
|
defaultKeys: [...KEYBINDINGS["tui.editor.deleteWordBackward"].defaultKeys, "ctrl+backspace"],
|
|
@@ -199,15 +202,17 @@ const OWNED_INPUT_KEYBINDINGS = {
|
|
|
199
202
|
},
|
|
200
203
|
// Ctrl+Z edits the prompt in bare A1 instead of suspending the process.
|
|
201
204
|
"app.suspend": { ...KEYBINDINGS["app.suspend"], defaultKeys: [] },
|
|
202
|
-
// Owned prompt-selection actions
|
|
203
|
-
// actions. Keeping them in the keybinding manager makes the UX declarative,
|
|
204
|
-
// configurable, and independent of terminal escape-sequence spellings.
|
|
205
|
+
// Owned prompt-selection actions remain declarative and take priority.
|
|
205
206
|
"owned.editor.selectAll": { defaultKeys: "ctrl+a", description: "Select all prompt text" },
|
|
206
207
|
"owned.editor.cut": { defaultKeys: "ctrl+x", description: "Cut selected prompt text" },
|
|
207
208
|
"owned.editor.paste": { defaultKeys: "ctrl+v", description: "Paste clipboard text" },
|
|
208
209
|
"owned.editor.redo": { defaultKeys: "ctrl+y", description: "Redo prompt edit" },
|
|
209
210
|
"owned.editor.extendLeft": { defaultKeys: "shift+left", description: "Extend prompt selection left" },
|
|
210
211
|
"owned.editor.extendRight": { defaultKeys: "shift+right", description: "Extend prompt selection right" },
|
|
212
|
+
"owned.tree.collapse": { defaultKeys: "left", description: "Collapse selected tree branch" },
|
|
213
|
+
"owned.tree.expand": { defaultKeys: "right", description: "Expand selected tree branch" },
|
|
214
|
+
"owned.tree.first": { defaultKeys: "home", description: "Select first tree entry" },
|
|
215
|
+
"owned.tree.last": { defaultKeys: "end", description: "Select last tree entry" },
|
|
211
216
|
};
|
|
212
217
|
const KEYBINDING_NAME_MIGRATIONS = {
|
|
213
218
|
cursorUp: "tui.editor.cursorUp",
|
|
@@ -349,6 +354,15 @@ export class KeybindingsManager extends TuiKeybindingsManager {
|
|
|
349
354
|
static createForOwnedInput(agentDir = getAgentDir()) {
|
|
350
355
|
return KeybindingsManager.createWithDefinitions(agentDir, OWNED_INPUT_KEYBINDINGS);
|
|
351
356
|
}
|
|
357
|
+
// Protocol: Ctrl+C is a conventional dialog escape hatch, not advertised keybinding chrome.
|
|
358
|
+
matches(data, keybinding) {
|
|
359
|
+
return keybinding === "tui.select.cancel" && matchesKey(data, "ctrl+c")
|
|
360
|
+
|| super.matches(data, keybinding);
|
|
361
|
+
}
|
|
362
|
+
getKeys(keybinding) {
|
|
363
|
+
const keys = super.getKeys(keybinding);
|
|
364
|
+
return keybinding === "tui.select.cancel" ? keys.filter(key => key !== "ctrl+c") : keys;
|
|
365
|
+
}
|
|
352
366
|
static createWithDefinitions(agentDir, definitions) {
|
|
353
367
|
const configPath = join(agentDir, "keybindings.json");
|
|
354
368
|
const userBindings = KeybindingsManager.loadFromFile(configPath);
|
|
@@ -11,7 +11,7 @@ import { Container, Editor, Spacer, Text, } from "@earendil-works/pi-tui";
|
|
|
11
11
|
import { DynamicBorder, getSelectListTheme } from "../../../startup-public.js";
|
|
12
12
|
import { editInExternalEditor } from "../external-editor.js";
|
|
13
13
|
import { addPiModalHeader, adoptPiModalFrame } from "../../modal-frame.js";
|
|
14
|
-
import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
|
|
14
|
+
import { DIALOG_CLOSE_SHORTCUT_HINT, piTheme, renderPiModalShortcutHints } from "../../theme.js";
|
|
15
15
|
export class ExtensionEditorComponent extends Container {
|
|
16
16
|
#editor;
|
|
17
17
|
#tui;
|
|
@@ -54,8 +54,8 @@ export class ExtensionEditorComponent extends Container {
|
|
|
54
54
|
const hint = renderPiModalShortcutHints([
|
|
55
55
|
{ key: this.#keybindings.getKeys("tui.select.confirm").join("/"), action: "submit" },
|
|
56
56
|
{ key: this.#keybindings.getKeys("tui.input.newLine").join("/"), action: "newline" },
|
|
57
|
-
{ key: this.#keybindings.getKeys("tui.select.cancel").join("/"), action: "cancel" },
|
|
58
57
|
{ key: this.#keybindings.getKeys("app.editor.external").join("/"), action: "external editor" },
|
|
58
|
+
DIALOG_CLOSE_SHORTCUT_HINT,
|
|
59
59
|
]);
|
|
60
60
|
this.addChild(new Text(hint, 0, 0));
|
|
61
61
|
this.addChild(new Spacer(1));
|