@timurproko/a1 0.2.7-dev.1221 → 0.2.7-dev.1222
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/dock-chrome.js +1 -1
- package/dist/app/session-shell/session-shell-root.js +3 -2
- package/dist/app/session-shell/session-shell.js +4 -1
- package/dist/app/session-shell/session-viewport-controller.d.ts +2 -1
- package/dist/app/session-shell/session-viewport-controller.js +26 -37
- package/dist/app/session-shell/shell-editor-factory.js +4 -1
- package/dist/composition/owned-ui.js +4 -2
- package/dist/composition/reference-selection-options.d.ts +3 -0
- package/dist/composition/reference-selection-options.js +16 -0
- package/dist/composition/settings-route-host.d.ts +1 -2
- package/dist/composition/settings-route-host.js +19 -13
- package/dist/composition/settings-search-input.js +4 -1
- package/dist/composition/utility-tab.js +4 -2
- package/dist/features/owned-ui/plus-menu-dialog.js +28 -20
- package/dist/features/owned-ui/reference-document-selection.d.ts +30 -0
- package/dist/features/owned-ui/reference-document-selection.js +179 -0
- package/dist/features/owned-ui/reference-screen-app.d.ts +4 -1
- package/dist/features/owned-ui/reference-screen-app.js +39 -8
- package/dist/features/owned-ui/settings-app.d.ts +1 -0
- package/dist/features/owned-ui/settings-app.js +23 -19
- package/dist/integrations/pi/components/owned-editor-ux.js +48 -5
- package/dist/integrations/pi/engine/workflow-runner.js +1 -1
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
- package/dist/native/darwin-arm64/terminal-host.node +0 -0
- package/dist/native/darwin-x64/manifest.json +1 -1
- package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
- package/dist/native/darwin-x64/terminal-host.node +0 -0
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
- package/dist/native/linux-x64/terminal-host.node +0 -0
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.node +0 -0
- package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
- package/dist/native/win32-x64/terminal-host.node +0 -0
- package/dist/ui/components/dialog-panel.d.ts +17 -8
- package/dist/ui/components/dialog-panel.js +97 -26
- package/dist/ui/components/index.d.ts +3 -3
- package/dist/ui/components/index.js +3 -3
- package/dist/ui/components/scrollbar.d.ts +1 -1
- package/dist/ui/components/scrollbar.js +1 -1
- package/dist/ui/components/selection-copy.d.ts +2 -4
- package/dist/ui/components/selection-copy.js +4 -2
- package/dist/ui/components/text-selection.d.ts +1 -2
- package/dist/ui/components/text-selection.js +5 -2
- package/dist/ui/components/transcript-viewport.js +3 -3
- package/docs/architecture/resident-tabs.md +5 -4
- package/package.json +3 -3
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { COPY_ACKNOWLEDGEMENT_DURATION_MS } from "../../ui/components/selection-copy.js";
|
|
1
2
|
import { renderPiShellCommandMessage, renderPiShellReleaseUpdateBanner, renderPiShellStatusText, } from "../../integrations/pi/components/shell-presenters-info.js";
|
|
2
3
|
import { PINNED_PI_LAYOUT, piTheme } from "../../integrations/pi/components/upstream/theme/theme.js";
|
|
3
4
|
import { piShellTruncateToWidth } from "../../integrations/pi/components/shell-shared-facade.js";
|
|
4
5
|
import { parseReleaseUpdateDiagnostic } from "./transcript-document.js";
|
|
5
|
-
const COPY_ACKNOWLEDGEMENT_DURATION_MS = 1_000;
|
|
6
6
|
/**
|
|
7
7
|
* The transient chrome around bare A1's dock: the one command notice above the editor, the release
|
|
8
8
|
* banner above Working with its close control, and the short copy acknowledgement.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ImageAttachmentError } from "../../contracts/owned-ui/index.js";
|
|
2
2
|
import { promptArrow } from "../../ui/components/prompt-input.js";
|
|
3
3
|
import { composeSubmittedPromptRows, submittedPromptLayout, } from "../../ui/components/submitted-prompt.js";
|
|
4
|
-
import {
|
|
4
|
+
import { nativeHyperlinkStyle } from "../../ui/components/spans.js";
|
|
5
|
+
import { paintTextSelection } from "../../ui/components/text-selection.js";
|
|
5
6
|
import { progressStatusFrame, progressStatusText } from "../../ui/components/progress-status.js";
|
|
6
7
|
import { createPiQueuedInputStatus, createPiShellFooter, createPiShellHeader, createPiShellLoadedResources, createPiShellStatus, } from "../../integrations/pi/components/shell-footer-status.js";
|
|
7
8
|
import { createPiShellTranscriptComponent, } from "../../integrations/pi/components/shell-presenters-transcript.js";
|
|
@@ -847,5 +848,5 @@ const VIEWPORT_THEME = {
|
|
|
847
848
|
sticky: (text, hovered) => piTheme().bg(hovered ? "selectedBg" : "toolPendingBg", piTheme().fg("text", withoutTerminalBackground(text))),
|
|
848
849
|
quietSticky: text => `\u001b[2m${text.replace(/\u001b\[(?:0|22)m/g, "$&\u001b[2m")}\u001b[22m`,
|
|
849
850
|
bottomControl: (text, hovered) => piTheme().bg(hovered ? "selectedBg" : "toolPendingBg", piTheme().fg("text", text)),
|
|
850
|
-
selection:
|
|
851
|
+
selection: paintTextSelection,
|
|
851
852
|
};
|
|
@@ -14,6 +14,7 @@ import { promptSuggestionSurface, routeResponseToSuggestions, suggestionModelKey
|
|
|
14
14
|
import { CLEAR_EXIT_INTERVAL_MS, boundedCleanup } from "../../foundation/terminal-cleanup/terminal-reset.js";
|
|
15
15
|
import { OWNED_UI_HIDDEN_COMMAND_NAMES } from "../../contracts/owned-ui/index.js";
|
|
16
16
|
import { ImageAttachmentError } from "../../contracts/owned-ui/index.js";
|
|
17
|
+
import { copyAcknowledgementText } from "../../ui/components/selection-copy.js";
|
|
17
18
|
import { ContextualPromptSuggestionController } from "./prompt-suggestion-controller.js";
|
|
18
19
|
import { workflowCommandNames } from "../../integrations/pi/engine/workflows.js";
|
|
19
20
|
import { createPiExtensionUiBridge } from "../../integrations/pi/components/shell-extension-ui.js";
|
|
@@ -499,6 +500,8 @@ export class OwnedUiSessionPresenter {
|
|
|
499
500
|
const snapshot = routed.copySelection;
|
|
500
501
|
this.#trackCopy(this.#responseCopy?.submit(snapshot, this.#pendingClipboardWrite), copyCharacters(snapshot));
|
|
501
502
|
}
|
|
503
|
+
if (routed.copyLastMessage)
|
|
504
|
+
void this.runWorkflow({ command: "copy", argument: "" });
|
|
502
505
|
if (!routed.consumed)
|
|
503
506
|
return routed.data === data ? undefined : { data: routed.data };
|
|
504
507
|
return routed.data.length === 0 ? { consume: true } : { data: routed.data };
|
|
@@ -509,7 +512,7 @@ export class OwnedUiSessionPresenter {
|
|
|
509
512
|
if (count === undefined || this.#disposed || !this.#terminal.running || intent !== this.#copyIntentSequence
|
|
510
513
|
|| outcome !== "delivered" && outcome !== "submitted-unverified")
|
|
511
514
|
return;
|
|
512
|
-
this.root.showCopyAcknowledgement(
|
|
515
|
+
this.root.showCopyAcknowledgement(copyAcknowledgementText(count));
|
|
513
516
|
});
|
|
514
517
|
}
|
|
515
518
|
#promptSuggestionPresentationBlockReason(identity) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PiTuiPointerSurface } from "../../integrations/pi/tui-runtime/contracts.js";
|
|
2
2
|
import type { OwnedUiViewportSettings } from "../../contracts/owned-ui/index.js";
|
|
3
3
|
import { type TranscriptViewportFrame, type TranscriptViewportFrameInput } from "../../ui/components/transcript-viewport.js";
|
|
4
|
-
import {
|
|
4
|
+
import type { SelectionCopySnapshot } from "../../ui/components/selection-copy.js";
|
|
5
5
|
import type { PiShellEditorPort } from "../../integrations/pi/components/shell-shared-facade.js";
|
|
6
6
|
import type { PasteEvent } from "./paste-protocol.js";
|
|
7
7
|
export interface SessionViewportControllerOptions {
|
|
@@ -26,6 +26,7 @@ export interface SessionViewportInputResult {
|
|
|
26
26
|
readonly data: string;
|
|
27
27
|
readonly consumed: boolean;
|
|
28
28
|
readonly copySelection?: SelectionCopySnapshot;
|
|
29
|
+
readonly copyLastMessage?: true;
|
|
29
30
|
}
|
|
30
31
|
/**
|
|
31
32
|
* Owns the stateful interaction policy of the bare-A1 session viewport. The
|
|
@@ -3,7 +3,6 @@ import { TranscriptViewport } from "../../ui/components/transcript-viewport.js";
|
|
|
3
3
|
import { readVisibleHyperlinks } from "../../ui/components/visible-hyperlinks.js";
|
|
4
4
|
import { routeMouseInput } from "../../ui/components/mouse.js";
|
|
5
5
|
import { scrollForTrackPage, scrollForThumbRow, scrollbarSelectionRows, scrollbarWheelRows } from "../../ui/components/scrollbar.js";
|
|
6
|
-
import { literalSelectionCopySnapshot } from "../../ui/components/selection-copy.js";
|
|
7
6
|
function withinTailControl(region, column, row) {
|
|
8
7
|
return row >= region.rowStart && row <= region.rowEnd && column >= region.columnStart && column <= region.columnEnd;
|
|
9
8
|
}
|
|
@@ -254,15 +253,13 @@ export class SessionViewportController {
|
|
|
254
253
|
pasteRequest = this.#nextPasteDiagnosticRequest();
|
|
255
254
|
this.#emitPasteRoute(pasteRequest, "shortcut-received");
|
|
256
255
|
}
|
|
257
|
-
//
|
|
258
|
-
// before the focused vanilla editor handles this input.
|
|
256
|
+
// Invariant: activate A1 aliases before editor input.
|
|
259
257
|
if (editorActive)
|
|
260
258
|
this.#editor.activateKeybindings();
|
|
261
259
|
// Protocol: bracketed paste is opaque input, even if its text resembles mouse reports.
|
|
262
260
|
if (data.startsWith("\u001b[200~"))
|
|
263
261
|
return { data, consumed: false };
|
|
264
|
-
// Platform:
|
|
265
|
-
// terminals vary between forwarding Ctrl+V and terminal-owned bracketed paste.
|
|
262
|
+
// Platform: Windows terminals may forward Ctrl+V or terminal-owned paste.
|
|
266
263
|
if (editorActive && this.#editor.matchesTerminalKey(data, "ctrl+v")) {
|
|
267
264
|
pasteRequest ??= this.#nextPasteDiagnosticRequest();
|
|
268
265
|
if (data !== "\u0016")
|
|
@@ -278,8 +275,7 @@ export class SessionViewportController {
|
|
|
278
275
|
this.#requestHyperlinkCleanup();
|
|
279
276
|
this.#requestRender();
|
|
280
277
|
}
|
|
281
|
-
// Invariant:
|
|
282
|
-
// or when scrolling is a no-op. Plain Home/End remain editor line navigation.
|
|
278
|
+
// Invariant: viewport shortcuts skip the prompt; Home/End do not.
|
|
283
279
|
if (editorActive && allowWheel && this.#editor.matchesTerminalKey(data, "ctrl+home")) {
|
|
284
280
|
if (this.#viewport.scrollTo(0, now)) {
|
|
285
281
|
this.#scheduleActivityExpiry();
|
|
@@ -295,6 +291,9 @@ export class SessionViewportController {
|
|
|
295
291
|
}
|
|
296
292
|
return { data: "", consumed: true };
|
|
297
293
|
}
|
|
294
|
+
if (data === "\u0018" && editorActive && !this.#viewport.hasSelection && !this.#editor.hasSelection()) {
|
|
295
|
+
return { data: "", consumed: true, copyLastMessage: true };
|
|
296
|
+
}
|
|
298
297
|
if (this.#viewport.frame === null) {
|
|
299
298
|
// Invariant: a not-yet-painted A1 screen is not Pi's selection surface.
|
|
300
299
|
return routeMouseInput(data, event => {
|
|
@@ -305,22 +304,15 @@ export class SessionViewportController {
|
|
|
305
304
|
return event.kind === "motion" || event.kind === "release" || (event.kind === "press" && event.button === 0);
|
|
306
305
|
});
|
|
307
306
|
}
|
|
308
|
-
if (data === "\u0003") {
|
|
309
|
-
// Invariant: the newest
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
? { data: "", consumed: true }
|
|
318
|
-
: { data: "", consumed: true, copySelection };
|
|
319
|
-
}
|
|
320
|
-
if (editorActive && this.#editor.hasSelection()) {
|
|
321
|
-
if (this.#viewport.clearSelection())
|
|
322
|
-
this.#requestRender();
|
|
323
|
-
}
|
|
307
|
+
if ((data === "\u0003" || data === "\u0018") && this.#viewport.hasSelection) {
|
|
308
|
+
// Invariant: the newest frame selection wins over an older editor selection.
|
|
309
|
+
const copySelection = this.#viewport.captureSelectedText();
|
|
310
|
+
this.#viewport.clearSelection();
|
|
311
|
+
this.#stopSelectionAutoScroll();
|
|
312
|
+
this.#requestRender();
|
|
313
|
+
return copySelection === null
|
|
314
|
+
? { data: "", consumed: true }
|
|
315
|
+
: { data: "", consumed: true, copySelection };
|
|
324
316
|
}
|
|
325
317
|
const previousPrompt = PROMPT_UP_INPUTS.has(data) || this.#editor.matchesTerminalKey(data, "alt+home");
|
|
326
318
|
if (editorActive && allowWheel && (previousPrompt || PROMPT_DOWN_INPUTS.has(data))) {
|
|
@@ -345,8 +337,7 @@ export class SessionViewportController {
|
|
|
345
337
|
const hits = frame.hits;
|
|
346
338
|
const overModal = this.#modalAt(event.column, event.row);
|
|
347
339
|
const wheel = event.kind === "wheel-up" || event.kind === "wheel-down";
|
|
348
|
-
// Invariant:
|
|
349
|
-
// fullscreen selection layer without ever invoking the hidden ordinary editor.
|
|
340
|
+
// Invariant: latch each gesture to one surface.
|
|
350
341
|
if (this.#pointerOwner === "drain" || !this.inputGeometryReady) {
|
|
351
342
|
if (event.kind === "release")
|
|
352
343
|
this.#pointerOwner = undefined;
|
|
@@ -387,8 +378,7 @@ export class SessionViewportController {
|
|
|
387
378
|
const wasOverBottom = hits.bottom !== null && previousPointer !== undefined
|
|
388
379
|
&& previousPointer.row === hits.bottom.row
|
|
389
380
|
&& previousPointer.column >= hits.bottom.columnStart && previousPointer.column <= hits.bottom.columnEnd;
|
|
390
|
-
// Invariant: every report
|
|
391
|
-
// Keep this location while the control is hidden; composition resolves its next hit region.
|
|
381
|
+
// Invariant: every report updates the pointer position.
|
|
392
382
|
this.#pointerPosition = { column: event.column, row: event.row };
|
|
393
383
|
repaint ||= wasOverBottom !== overBottom;
|
|
394
384
|
const wasOverTailControl = tailControl !== undefined && previousPointer !== undefined
|
|
@@ -407,7 +397,7 @@ export class SessionViewportController {
|
|
|
407
397
|
this.#viewport.setRailHovered(overRail);
|
|
408
398
|
this.#viewport.setStickyHovered(overSticky);
|
|
409
399
|
repaint = true;
|
|
410
|
-
if (editorActive && this.#
|
|
400
|
+
if (editorActive && this.#editorPointerSelecting && this.#editorPointerFrame !== undefined) {
|
|
411
401
|
this.#editor.handlePointer({
|
|
412
402
|
kind: "motion",
|
|
413
403
|
button: event.button,
|
|
@@ -423,6 +413,12 @@ export class SessionViewportController {
|
|
|
423
413
|
if (event.column === pendingEditorClick.column && event.row === pendingEditorClick.row)
|
|
424
414
|
return true;
|
|
425
415
|
this.#pendingEditorClick = undefined;
|
|
416
|
+
const editorFrame = editorActive ? this.#editorPointerFrame : undefined;
|
|
417
|
+
if (editorFrame !== undefined && this.#editor.handlePointer({ kind: "press", button: 0, column: pendingEditorClick.column, row: pendingEditorClick.row - editorFrame.rowStart + 1 })) {
|
|
418
|
+
this.#editorPointerSelecting = true;
|
|
419
|
+
this.#editor.handlePointer({ kind: "motion", button: event.button, column: event.column, row: event.row - editorFrame.rowStart + 1 });
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
426
422
|
this.#viewport.pressSelection(pendingEditorClick.column, pendingEditorClick.row, now, { continueMultiClick: false });
|
|
427
423
|
this.#viewport.extendSelection(event.column, event.row, now, false);
|
|
428
424
|
this.#updateSelectionAutoScroll(event.column, event.row, hits.viewportHeight, frame.rows.length);
|
|
@@ -513,8 +509,7 @@ export class SessionViewportController {
|
|
|
513
509
|
}
|
|
514
510
|
if (event.row >= 1 && event.row <= frame.rows.length && event.column <= frame.descriptor.width) {
|
|
515
511
|
if (editorFrame !== undefined && event.row >= editorFrame.rowStart && event.row <= editorFrame.rowEnd) {
|
|
516
|
-
// Invariant: no-drag
|
|
517
|
-
// motion promotes from this exact origin into complete-frame selection.
|
|
512
|
+
// Invariant: no-drag clicks remain editor-owned; first motion latches an editor drag.
|
|
518
513
|
this.#pendingEditorClick = { column: event.column, row: event.row };
|
|
519
514
|
}
|
|
520
515
|
else {
|
|
@@ -527,7 +522,7 @@ export class SessionViewportController {
|
|
|
527
522
|
return true;
|
|
528
523
|
}
|
|
529
524
|
if (event.kind === "release") {
|
|
530
|
-
if (editorActive && this.#
|
|
525
|
+
if (editorActive && this.#editorPointerSelecting && this.#editorPointerFrame !== undefined) {
|
|
531
526
|
const wasEditorSelecting = this.#editorPointerSelecting;
|
|
532
527
|
this.#editor.handlePointer({
|
|
533
528
|
kind: "release",
|
|
@@ -547,12 +542,6 @@ export class SessionViewportController {
|
|
|
547
542
|
const editorRow = pendingEditorClick.row - this.#editorPointerFrame.rowStart + 1;
|
|
548
543
|
this.#editor.handlePointer({ kind: "press", button: 0, column: pendingEditorClick.column, row: editorRow });
|
|
549
544
|
this.#editor.handlePointer({ kind: "release", button: 0, column: pendingEditorClick.column, row: editorRow });
|
|
550
|
-
if (this.#copyOnSelect) {
|
|
551
|
-
const selected = this.#editor.selectedText?.();
|
|
552
|
-
if (selected !== undefined) {
|
|
553
|
-
completedCopy = literalSelectionCopySnapshot(selected, this.#viewport.selectionRevision) ?? undefined;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
545
|
}
|
|
557
546
|
forceRepaint = true;
|
|
558
547
|
repaint = true;
|
|
@@ -2,6 +2,7 @@ import { caretCell } from "../../ui/components/line-input.js";
|
|
|
2
2
|
import { PromptInput } from "../../ui/components/prompt-input.js";
|
|
3
3
|
import { backgroundSgrSpan, hyperlinkSgrSpan } from "../../ui/components/spans.js";
|
|
4
4
|
import { faint, stripAnsi } from "../../ui/components/text.js";
|
|
5
|
+
import { paintTextSelection } from "../../ui/components/text-selection.js";
|
|
5
6
|
import { createPiShellEditor } from "../../integrations/pi/components/shell-editor-autocomplete.js";
|
|
6
7
|
import { piTheme } from "../../integrations/pi/components/upstream/theme/theme.js";
|
|
7
8
|
import { piShellTruncateToWidth, piShellVisibleWidth, } from "../../integrations/pi/components/shell-shared-facade.js";
|
|
@@ -18,7 +19,9 @@ export function createShellEditor(ports) {
|
|
|
18
19
|
...handlers,
|
|
19
20
|
keybindingProfile: customViewport ? "a1" : "pi",
|
|
20
21
|
...(handlers.keybindings === undefined ? {} : { keybindings: handlers.keybindings }),
|
|
21
|
-
paintEditorSelection: (line, from, to, atomic) =>
|
|
22
|
+
paintEditorSelection: (line, from, to, atomic) => atomic
|
|
23
|
+
? backgroundSgrSpan(line, from, to, "\u001b[7m", "\u001b[27m", piShellVisibleWidth)
|
|
24
|
+
: paintTextSelection(line, from, to, piShellVisibleWidth, true),
|
|
22
25
|
transformPastedContent: content => {
|
|
23
26
|
try {
|
|
24
27
|
return promptChips.transformPastedContent(content, editor.getText());
|
|
@@ -383,8 +383,10 @@ function settingsRouteOptions(tabBridge, keybindings, agentDir, cwd) {
|
|
|
383
383
|
agentDir,
|
|
384
384
|
cwd,
|
|
385
385
|
copyText: text => {
|
|
386
|
-
if (Buffer.byteLength(text)
|
|
387
|
-
|
|
386
|
+
if (Buffer.byteLength(text) > MAX_CLIPBOARD_TEXT_BYTES)
|
|
387
|
+
return false;
|
|
388
|
+
void writeSystemClipboardText(text).catch(() => { });
|
|
389
|
+
return true;
|
|
388
390
|
},
|
|
389
391
|
};
|
|
390
392
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ReferenceDocumentSelectionOptions } from "../features/owned-ui/reference-document-selection.js";
|
|
2
|
+
/** Builds the Changelog selection clipboard policy without loading Pi's engine in a utility tab. */
|
|
3
|
+
export declare function createReferenceSelectionOptions(settingsFile: string, copyText: ((text: string) => boolean | void) | undefined): ReferenceDocumentSelectionOptions;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
/** Builds the Changelog selection clipboard policy without loading Pi's engine in a utility tab. */
|
|
3
|
+
export function createReferenceSelectionOptions(settingsFile, copyText) {
|
|
4
|
+
return {
|
|
5
|
+
copyOnSelect: () => {
|
|
6
|
+
try {
|
|
7
|
+
const document = JSON.parse(readFileSync(settingsFile, "utf8"));
|
|
8
|
+
return typeof document.fullscreenCopyOnSelect === "boolean" ? document.fullscreenCopyOnSelect : true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
copyText: copyText ?? (() => false),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -2,7 +2,6 @@ import type { ReferenceDocumentProvider } from "../features/owned-ui/reference-s
|
|
|
2
2
|
import type { OwnedSettingsManager } from "../ui/settings/manager.js";
|
|
3
3
|
import type { UiRouteHost, UiRouteInput } from "../ui/apps/contracts.js";
|
|
4
4
|
import type { PiKeybindingsHost } from "../integrations/pi/components/shell-shared-facade.js";
|
|
5
|
-
/** Styled document rows for one content width. */
|
|
6
5
|
export type OwnedReferenceRows = NonNullable<ReferenceDocumentProvider["rows"]>;
|
|
7
6
|
/** Documents supplied by composition so the route host never reads shell data itself. */
|
|
8
7
|
export interface OwnedReferenceProviders {
|
|
@@ -22,7 +21,7 @@ export interface OwnedRouteHostOptions {
|
|
|
22
21
|
readonly keybindings?: PiKeybindingsHost;
|
|
23
22
|
readonly agentDir?: string;
|
|
24
23
|
readonly cwd?: string;
|
|
25
|
-
readonly copyText?: (text: string) => void;
|
|
24
|
+
readonly copyText?: (text: string) => boolean | void;
|
|
26
25
|
}
|
|
27
26
|
/** Declares settings and lazy document routes. */
|
|
28
27
|
export declare function createOwnedRouteHost(settings: OwnedSettingsManager, references?: OwnedReferenceProviders, options?: OwnedRouteHostOptions): UiRouteHost;
|
|
@@ -18,7 +18,7 @@ export function createOwnedRouteHost(settings, references, options = {}) {
|
|
|
18
18
|
const title = session ? "Session Info" : changelog ? input ? "What's New" : "Changelog" : HOTKEYS_TITLE;
|
|
19
19
|
const document = session ? references.session() : changelog ? references.changelog(input) : references.hotkeys();
|
|
20
20
|
const pinned = options.tabRoute === route;
|
|
21
|
-
return deferredSurface(route, title, () => loadReferenceSurface(settings, route, title, document, pinned));
|
|
21
|
+
return deferredSurface(route, title, () => loadReferenceSurface(settings, route, title, document, pinned, options));
|
|
22
22
|
},
|
|
23
23
|
};
|
|
24
24
|
}
|
|
@@ -63,9 +63,10 @@ function deferredSurface(id, label, load) {
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
async function loadSettingsSurface(settings, options) {
|
|
66
|
-
const [{ SettingsApp }, { createSettingsSearchInputFactory }] = await Promise.all([
|
|
66
|
+
const [{ SettingsApp }, { createSettingsSearchInputFactory }, { renderFloatingDialogSurface, renderFloatingMenuSurface }] = await Promise.all([
|
|
67
67
|
import("../features/owned-ui/settings-app.js"),
|
|
68
68
|
import("./settings-search-input.js"),
|
|
69
|
+
import("../ui/components/dialog-panel.js"),
|
|
69
70
|
]);
|
|
70
71
|
const pinned = options.tabRoute === SETTINGS_ROUTE;
|
|
71
72
|
const searchInputFactory = createSettingsSearchInputFactory({
|
|
@@ -78,14 +79,19 @@ async function loadSettingsSurface(settings, options) {
|
|
|
78
79
|
pinned,
|
|
79
80
|
searchInputFactory,
|
|
80
81
|
configurePlusMenu: options.configurePlusMenu ?? null,
|
|
82
|
+
dialogPanel: text => renderFloatingDialogSurface(text, piTheme().getColorMode()),
|
|
81
83
|
...(options.copyText === undefined ? {} : { copyText: options.copyText }),
|
|
82
|
-
}), !pinned, true);
|
|
84
|
+
}), !pinned, true, text => renderFloatingMenuSurface(text, piTheme().getColorMode()));
|
|
83
85
|
}
|
|
84
|
-
async function loadReferenceSurface(settings, route, title, document, pinned) {
|
|
86
|
+
async function loadReferenceSurface(settings, route, title, document, pinned, options) {
|
|
85
87
|
const id = route;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
const [{ ReferenceScreenApp }, content, selection] = await Promise.all([
|
|
89
|
+
import("../features/owned-ui/reference-screen-app.js"),
|
|
90
|
+
document,
|
|
91
|
+
route === CHANGELOG_ROUTE
|
|
92
|
+
? import("./reference-selection-options.js").then(module => module.createReferenceSelectionOptions(settings.file, options.copyText))
|
|
93
|
+
: undefined,
|
|
94
|
+
]);
|
|
89
95
|
return hostApp(id, route, () => new ReferenceScreenApp({
|
|
90
96
|
id,
|
|
91
97
|
title,
|
|
@@ -96,9 +102,10 @@ async function loadReferenceSurface(settings, route, title, document, pinned) {
|
|
|
96
102
|
scrollbarSpeed: settings.value("scrollbarSpeed"),
|
|
97
103
|
}),
|
|
98
104
|
pinned,
|
|
99
|
-
|
|
105
|
+
...(selection === undefined ? {} : { selection }),
|
|
106
|
+
}), !pinned, selection !== undefined);
|
|
100
107
|
}
|
|
101
|
-
async function hostApp(id, route, create, closeOnInterrupt = true, appInputBeforeInterrupt = false) {
|
|
108
|
+
async function hostApp(id, route, create, closeOnInterrupt = true, appInputBeforeInterrupt = false, panel = text => text) {
|
|
102
109
|
const [{ UiAppHost }, { UiAppRegistry }] = await Promise.all([
|
|
103
110
|
import("../ui/apps/host.js"),
|
|
104
111
|
import("../ui/apps/registry.js"),
|
|
@@ -114,7 +121,7 @@ async function hostApp(id, route, create, closeOnInterrupt = true, appInputBefor
|
|
|
114
121
|
registry,
|
|
115
122
|
closeOnInterrupt,
|
|
116
123
|
appInputBeforeInterrupt,
|
|
117
|
-
theme: pinnedTheme(),
|
|
124
|
+
theme: pinnedTheme(panel),
|
|
118
125
|
surface: {
|
|
119
126
|
size: () => size,
|
|
120
127
|
requestRender: () => onRender(),
|
|
@@ -143,8 +150,7 @@ async function hostApp(id, route, create, closeOnInterrupt = true, appInputBefor
|
|
|
143
150
|
onExitRequested: listener => { onExit = listener; },
|
|
144
151
|
};
|
|
145
152
|
}
|
|
146
|
-
|
|
147
|
-
function pinnedTheme() {
|
|
153
|
+
function pinnedTheme(panel) {
|
|
148
154
|
return {
|
|
149
155
|
fg: (token, text) => piTheme().fg(token, text),
|
|
150
156
|
bold: (text) => piTheme().bold(text),
|
|
@@ -152,6 +158,6 @@ function pinnedTheme() {
|
|
|
152
158
|
accentPreview: (color, text) => renderPiAccentPreview(color, text),
|
|
153
159
|
disabled: (text) => faint(piTheme().fg("dim", text)),
|
|
154
160
|
highlight: (text) => piTheme().bg("selectedBg", text),
|
|
155
|
-
panel
|
|
161
|
+
panel,
|
|
156
162
|
};
|
|
157
163
|
}
|
|
@@ -3,6 +3,7 @@ import { createPiShellEditor } from "../integrations/pi/components/shell-editor-
|
|
|
3
3
|
import { piShellTruncateToWidth, piShellVisibleWidth, } from "../integrations/pi/components/shell-shared-facade.js";
|
|
4
4
|
import { backgroundSgrSpan } from "../ui/components/spans.js";
|
|
5
5
|
import { caretCell } from "../ui/components/line-input.js";
|
|
6
|
+
import { paintTextSelection } from "../ui/components/text-selection.js";
|
|
6
7
|
import { PromptInput } from "../ui/components/prompt-input.js";
|
|
7
8
|
/** Builds Settings search from the same configurable owned editor used by the agent prompt. */
|
|
8
9
|
export function createSettingsSearchInputFactory(options = {}) {
|
|
@@ -32,7 +33,9 @@ function createSettingsSearchInput(surface, options) {
|
|
|
32
33
|
requestRender: surface.requestRender,
|
|
33
34
|
onSubmit: () => { },
|
|
34
35
|
onCopyText: text => surface.copyText(text),
|
|
35
|
-
paintEditorSelection: (line, from, to, atomic) =>
|
|
36
|
+
paintEditorSelection: (line, from, to, atomic) => atomic
|
|
37
|
+
? backgroundSgrSpan(line, from, to, "\u001b[7m", "\u001b[27m", piShellVisibleWidth)
|
|
38
|
+
: paintTextSelection(line, from, to, piShellVisibleWidth, true),
|
|
36
39
|
promptPresentation: {
|
|
37
40
|
input: new PromptInput({ fg: (token, text) => surface.theme.fg(token, text) }, {
|
|
38
41
|
measure: piShellVisibleWidth,
|
|
@@ -73,8 +73,10 @@ function createUtilityRouteHost(options, settings, keybindings, cwd, agentDir) {
|
|
|
73
73
|
agentDir,
|
|
74
74
|
cwd,
|
|
75
75
|
copyText: text => {
|
|
76
|
-
if (Buffer.byteLength(text)
|
|
77
|
-
|
|
76
|
+
if (Buffer.byteLength(text) > MAX_CLIPBOARD_TEXT_BYTES)
|
|
77
|
+
return false;
|
|
78
|
+
void writeSystemClipboardText(text).catch(() => { });
|
|
79
|
+
return true;
|
|
78
80
|
},
|
|
79
81
|
}));
|
|
80
82
|
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { LineInput,
|
|
1
|
+
import { LineInput, handleLineInputKey, padToWidth, renderInputRow, renderShortcutHintsWithClose, truncateToWidth, } from "../../ui/components/index.js";
|
|
2
2
|
/** The A1 setting holding the tab kinds the strip's `+` offers. */
|
|
3
3
|
export const PLUS_MENU_SETTING_ID = "tabBarPlusMenu";
|
|
4
4
|
const TITLE = "Plus Menu";
|
|
5
5
|
const DESCRIPTION = "Choose what the + button in the tab bar can add.";
|
|
6
|
-
const
|
|
6
|
+
const HINTS = [
|
|
7
|
+
{ key: "Type", action: "search" },
|
|
8
|
+
{ key: "↑↓", action: "navigate" },
|
|
9
|
+
{ key: "Space", action: "toggle" },
|
|
10
|
+
{ key: "esc", action: "close" },
|
|
11
|
+
];
|
|
12
|
+
const PADDING_X = 2;
|
|
7
13
|
/**
|
|
8
14
|
* The plus-menu dialog: every tab kind with `●` when `+` offers it and `○` when not, filtered by typed
|
|
9
15
|
* text. It owns only its filter and the row in hand; the enabled record and its saving stay with the
|
|
@@ -41,36 +47,38 @@ export class PlusMenuDialog {
|
|
|
41
47
|
return "handled";
|
|
42
48
|
}
|
|
43
49
|
lines(enabled, width, theme) {
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
const frameWidth = Math.max(0, width);
|
|
51
|
+
const left = Math.min(PADDING_X, frameWidth);
|
|
52
|
+
const right = Math.min(PADDING_X, frameWidth - left);
|
|
53
|
+
const contentWidth = Math.max(0, frameWidth - left - right);
|
|
54
|
+
const blank = " ".repeat(frameWidth);
|
|
55
|
+
const row = (painted, selected = false) => {
|
|
56
|
+
const content = padToWidth(truncateToWidth(painted, contentWidth), contentWidth);
|
|
57
|
+
const rightPadding = " ".repeat(right);
|
|
58
|
+
return `${" ".repeat(left)}${selected ? theme.highlight(content) : content}${selected ? theme.panel(rightPadding) : rightPadding}`;
|
|
50
59
|
};
|
|
51
60
|
const shown = this.shown();
|
|
52
61
|
const index = Math.min(this.#index, Math.max(0, shown.length - 1));
|
|
53
62
|
const rows = shown.length === 0
|
|
54
|
-
? [row(theme.fg("dim", "No tabs match")
|
|
63
|
+
? [row(theme.fg("dim", "No tabs match"))]
|
|
55
64
|
: shown.map((kind, position) => {
|
|
56
65
|
const selected = position === index;
|
|
57
66
|
const cursor = selected ? "→ " : " ";
|
|
58
67
|
const marker = enabled[kind.key] === true ? "●" : "○";
|
|
59
|
-
const raw = `${cursor}${marker} ${kind.label}`;
|
|
60
68
|
const content = `${selected ? theme.fg("accent", cursor) : cursor}${theme.fg("text", marker)} ${theme.fg("text", kind.label)}`;
|
|
61
|
-
return row(
|
|
69
|
+
return row(content, selected);
|
|
62
70
|
});
|
|
63
71
|
return [
|
|
64
|
-
|
|
65
|
-
row(theme.bold(theme.fg("accent", TITLE))
|
|
66
|
-
row(theme.fg("dim", DESCRIPTION)
|
|
67
|
-
|
|
68
|
-
...renderInputRow(this.#filter,
|
|
69
|
-
|
|
72
|
+
blank,
|
|
73
|
+
row(theme.bold(theme.fg("accent", TITLE))),
|
|
74
|
+
row(theme.fg("dim", DESCRIPTION)),
|
|
75
|
+
blank,
|
|
76
|
+
...renderInputRow(this.#filter, contentWidth, { ruled: false, promptGlyph: "> ", theme }).lines.map(line => row(line)),
|
|
77
|
+
blank,
|
|
70
78
|
...rows,
|
|
71
|
-
|
|
72
|
-
row(
|
|
73
|
-
|
|
79
|
+
blank,
|
|
80
|
+
row(renderShortcutHintsWithClose(HINTS, theme, contentWidth)),
|
|
81
|
+
blank,
|
|
74
82
|
];
|
|
75
83
|
}
|
|
76
84
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type PaneMouseEvent, type UiTheme } from "../../ui/components/index.js";
|
|
2
|
+
export interface ReferenceDocumentSelectionOptions {
|
|
3
|
+
readonly copyOnSelect: () => boolean;
|
|
4
|
+
readonly copyText: (text: string) => boolean | void;
|
|
5
|
+
}
|
|
6
|
+
interface ReferenceSelectionFrame {
|
|
7
|
+
readonly bodyRowStart: number;
|
|
8
|
+
readonly contentWidth: number;
|
|
9
|
+
readonly documentRows: readonly string[];
|
|
10
|
+
readonly visibleDocumentRows: readonly (number | undefined)[];
|
|
11
|
+
}
|
|
12
|
+
/** Selection state for an opted-in read-only reference document. */
|
|
13
|
+
export declare class ReferenceDocumentSelection {
|
|
14
|
+
#private;
|
|
15
|
+
constructor(options: ReferenceDocumentSelectionOptions);
|
|
16
|
+
get selecting(): boolean;
|
|
17
|
+
get hasSelection(): boolean;
|
|
18
|
+
updateFrame(frame: ReferenceSelectionFrame): void;
|
|
19
|
+
paintVisibleRow(visibleRow: number, line: string, theme: UiTheme): string;
|
|
20
|
+
handleInput(data: string): {
|
|
21
|
+
readonly consumed: boolean;
|
|
22
|
+
readonly cleared: boolean;
|
|
23
|
+
};
|
|
24
|
+
handleMouse(event: PaneMouseEvent, requestRender?: () => void): {
|
|
25
|
+
readonly consumed: boolean;
|
|
26
|
+
readonly changed: boolean;
|
|
27
|
+
};
|
|
28
|
+
clear(): void;
|
|
29
|
+
}
|
|
30
|
+
export {};
|