@timurproko/a1 0.2.7-dev.810 → 0.2.7-dev.831
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/README.md +7 -1
- package/bin/automatic-update.js +7 -0
- package/bin/ui.js +1 -0
- package/dist/app/session-shell/resident-tab-coordinator.d.ts +6 -9
- package/dist/app/session-shell/resident-tab-coordinator.js +9 -52
- package/dist/app/session-shell/session-shell-root.d.ts +9 -3
- package/dist/app/session-shell/session-shell.js +70 -20
- package/dist/app/session-shell/session-viewport-controller.js +6 -4
- package/dist/app/session-shell/transcript-document.js +1 -1
- package/dist/composition/owned-ui.d.ts +3 -11
- package/dist/composition/owned-ui.js +93 -43
- package/dist/composition/resident-tab-bridge.d.ts +10 -1
- package/dist/composition/resident-tab-bridge.js +21 -0
- package/dist/composition/settings-route-host.d.ts +12 -2
- package/dist/composition/settings-route-host.js +27 -9
- package/dist/composition/settings-search-input.d.ts +9 -0
- package/dist/composition/settings-search-input.js +58 -0
- package/dist/contracts/owned-ui/session-backend.d.ts +2 -0
- package/dist/contracts/owned-ui/session-workflows.d.ts +7 -3
- package/dist/contracts/presentation/index.d.ts +11 -0
- package/dist/contracts/presentation/index.js +12 -0
- package/dist/features/launch/development-launch.js +6 -0
- package/dist/features/launch/profile-paths.js +4 -0
- package/dist/features/owned-ui/plus-menu-dialog.d.ts +22 -0
- package/dist/features/owned-ui/plus-menu-dialog.js +76 -0
- package/dist/features/owned-ui/reference-screen-app.d.ts +2 -0
- package/dist/features/owned-ui/reference-screen-app.js +12 -2
- package/dist/features/owned-ui/settings-app.d.ts +10 -3
- package/dist/features/owned-ui/settings-app.js +98 -45
- package/dist/features/owned-ui/settings-search-input.d.ts +29 -0
- package/dist/features/owned-ui/settings-search-input.js +1 -0
- package/dist/features/prompt-history/index.d.ts +1 -1
- package/dist/features/prompt-history/paths.d.ts +2 -1
- package/dist/features/prompt-history/paths.js +10 -2
- package/dist/features/prompt-history/service.d.ts +5 -0
- package/dist/features/prompt-history/service.js +1 -1
- package/dist/features/resident-tab/shortcut-declarations.d.ts +1 -1
- package/dist/features/resident-tab/shortcut-declarations.js +2 -0
- package/dist/foundation/launch-context/index.d.ts +2 -0
- package/dist/foundation/launch-context/index.js +1 -0
- package/dist/foundation/release/automatic-update.d.ts +37 -0
- package/dist/foundation/release/automatic-update.js +265 -0
- package/dist/foundation/release/bootstrap.d.ts +1 -9
- package/dist/foundation/release/bootstrap.js +7 -3
- package/dist/foundation/release/index.d.ts +1 -1
- package/dist/foundation/release/index.js +1 -1
- package/dist/foundation/release/latest-release.d.ts +3 -2
- package/dist/foundation/release/latest-release.js +1 -1
- package/dist/foundation/release/update-execution-lease.d.ts +36 -0
- package/dist/foundation/release/update-execution-lease.js +145 -0
- package/dist/foundation/release/update.d.ts +8 -6
- package/dist/foundation/release/update.js +232 -167
- package/dist/foundation/resident-tabs/bridge-client.d.ts +9 -3
- package/dist/foundation/resident-tabs/bridge-client.js +14 -7
- package/dist/foundation/resident-tabs/routes.d.ts +5 -0
- package/dist/foundation/resident-tabs/routes.js +5 -0
- package/dist/foundation/resident-tabs/settings.d.ts +0 -1
- package/dist/foundation/resident-tabs/settings.js +0 -2
- package/dist/integrations/pi/components/editor-interaction.d.ts +2 -0
- package/dist/integrations/pi/components/modal-frame.d.ts +17 -1
- package/dist/integrations/pi/components/modal-frame.js +48 -9
- package/dist/integrations/pi/components/models-dialog.js +4 -52
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +3 -0
- package/dist/integrations/pi/components/owned-editor-ux.js +75 -4
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +57 -37
- package/dist/integrations/pi/components/shell-footer-status.js +19 -3
- package/dist/integrations/pi/components/shell-presenters-transcript.js +4 -1
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +4 -1
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -2
- package/dist/integrations/pi/components/skills-dialog.js +2 -2
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +7 -5
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +15 -8
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -3
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +2 -2
- package/dist/integrations/pi/components/upstream/components/session-footer.js +26 -23
- package/dist/integrations/pi/components/upstream/components/session-selector.js +7 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +7 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +9 -8
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +6 -4
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +8 -8
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +10 -10
- package/dist/integrations/pi/components/upstream/history/editor-core.js +2 -0
- package/dist/integrations/pi/engine/adapter.js +7 -2
- package/dist/integrations/pi/engine/ports.d.ts +1 -1
- package/dist/integrations/pi/engine/ports.js +4 -0
- package/dist/integrations/pi/engine/workflow-runner.js +9 -2
- 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/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +1 -0
- package/dist/ui/apps/host.d.ts +2 -0
- package/dist/ui/apps/host.js +7 -1
- package/dist/ui/components/dialog-panel.js +35 -30
- package/dist/ui/components/line-input.d.ts +3 -0
- package/dist/ui/components/line-input.js +3 -1
- package/dist/ui/components/prompt-input.d.ts +5 -4
- package/dist/ui/components/prompt-input.js +8 -6
- package/dist/ui/components/submitted-prompt.d.ts +2 -2
- package/dist/ui/components/submitted-prompt.js +3 -3
- package/dist/ui/components/surface.js +4 -2
- package/dist/ui/components/transcript-viewport.d.ts +1 -1
- package/dist/ui/components/transcript-viewport.js +141 -24
- package/dist/ui/settings/declarations.d.ts +65 -15
- package/dist/ui/settings/declarations.js +45 -13
- package/dist/ui/settings/manager.js +7 -3
- package/dist/ui/settings/migrations.js +22 -0
- package/dist/ui/settings/sections.d.ts +4 -0
- package/dist/ui/settings/sections.js +9 -3
- package/docs/architecture/resident-tabs.md +39 -11
- package/docs/architecture/tool-image-presentation.md +2 -1
- package/docs/features/prompt-history.md +41 -20
- package/package.json +1 -1
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "x64",
|
|
10
10
|
"target": "x86_64-pc-windows-msvc",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-10-
|
|
12
|
+
"builtAt": "2026-10-24T19:46:43.778Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "7eb4d836b8d9a4c13593921f329065bd3b706c0593d8d4e992b56ee461006ac5",
|
|
16
|
+
"size": 6288896
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const ENVIRONMENT_KEYS: readonly ["clipboardDiagnostics", "configDir", "dataDir", "databasePath", "developmentInstanceId", "developmentRoot", "endpoint", "exitNoticePath", "paneId", "probeTrace", "processGuardianPath", "prFooterPreview", "profileHome", "residentFixtureControlDir", "residentProgressObservationDir", "residentProgressRunId", "residentProgressTabId", "residentProgressTabIncarnation", "runtimeDir", "sessionRuntimeId", "skipVersionCheck", "startupTrace", "suggestionDiagnostics", "tabEndpoint", "tabId", "tabToken", "tabsEndpoint", "terminalHostPath", "terminalSessionId"];
|
|
1
|
+
declare const ENVIRONMENT_KEYS: readonly ["clipboardDiagnostics", "configDir", "dataDir", "databasePath", "developmentHistoryDataDir", "developmentInstanceId", "developmentRoot", "endpoint", "exitNoticePath", "paneId", "probeTrace", "processGuardianPath", "prFooterPreview", "profileHome", "residentFixtureControlDir", "residentProgressObservationDir", "residentProgressRunId", "residentProgressTabId", "residentProgressTabIncarnation", "runtimeDir", "sessionRuntimeId", "skipVersionCheck", "startupTrace", "suggestionDiagnostics", "tabEndpoint", "tabId", "tabToken", "tabsEndpoint", "terminalHostPath", "terminalSessionId"];
|
|
2
2
|
declare const FILESYSTEM_KEYS: readonly ["slug", "windowsDirectory", "unixDirectory", "temporaryPrefix"];
|
|
3
3
|
declare const STATE_KEYS: readonly ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile", "sessionRepositoryDirectory", "sessionWorktreeClaimDirectory"];
|
|
4
4
|
declare const ENDPOINT_KEYS: readonly ["windowsPipeStem", "unixSocketFilename", "metadataFilename", "supervisorLogFilename", "databaseFilename"];
|
package/dist/product-identity.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import rawIdentity from "./product-identity.json" with { type: "json" };
|
|
2
2
|
const ENVIRONMENT_KEYS = [
|
|
3
|
-
"clipboardDiagnostics", "configDir", "dataDir", "databasePath", "developmentInstanceId", "developmentRoot", "endpoint", "exitNoticePath",
|
|
3
|
+
"clipboardDiagnostics", "configDir", "dataDir", "databasePath", "developmentHistoryDataDir", "developmentInstanceId", "developmentRoot", "endpoint", "exitNoticePath",
|
|
4
4
|
"paneId", "probeTrace", "processGuardianPath", "prFooterPreview", "profileHome", "residentFixtureControlDir", "residentProgressObservationDir", "residentProgressRunId", "residentProgressTabId", "residentProgressTabIncarnation", "runtimeDir", "sessionRuntimeId", "skipVersionCheck", "startupTrace", "suggestionDiagnostics", "tabEndpoint", "tabId", "tabToken", "tabsEndpoint", "terminalHostPath", "terminalSessionId",
|
|
5
5
|
];
|
|
6
6
|
const FILESYSTEM_KEYS = ["slug", "windowsDirectory", "unixDirectory", "temporaryPrefix"];
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"configDir": "A1_CONFIG_DIR",
|
|
15
15
|
"dataDir": "A1_DATA_DIR",
|
|
16
16
|
"databasePath": "A1_DATABASE_PATH",
|
|
17
|
+
"developmentHistoryDataDir": "A1_DEV_HISTORY_DATA_DIR",
|
|
17
18
|
"developmentInstanceId": "A1_DEV_INSTANCE_ID",
|
|
18
19
|
"developmentRoot": "A1_DEV_ROOT",
|
|
19
20
|
"endpoint": "A1_ENDPOINT",
|
package/dist/ui/apps/host.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface AppHostOptions {
|
|
|
20
20
|
readonly closeOnInterrupt: boolean;
|
|
21
21
|
/** Colours handed to every presented app. Absent renders plain text. */
|
|
22
22
|
readonly theme?: UiTheme;
|
|
23
|
+
/** Lets a focused app claim the first interrupt for an editor selection before host close/exit policy. */
|
|
24
|
+
readonly appInputBeforeInterrupt?: boolean;
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
27
|
* Presents at most one app. An opted-in interrupt closes at the host boundary;
|
package/dist/ui/apps/host.js
CHANGED
|
@@ -15,6 +15,7 @@ export class UiAppHost {
|
|
|
15
15
|
#surface;
|
|
16
16
|
#closeOnInterrupt;
|
|
17
17
|
#theme;
|
|
18
|
+
#appInputBeforeInterrupt;
|
|
18
19
|
// Invariant: the first interrupt timestamp is null whenever the chord is disarmed.
|
|
19
20
|
#interruptArmedAt = null;
|
|
20
21
|
#renderedInterruptArmed;
|
|
@@ -25,6 +26,7 @@ export class UiAppHost {
|
|
|
25
26
|
this.#surface = options.surface;
|
|
26
27
|
this.#closeOnInterrupt = options.closeOnInterrupt;
|
|
27
28
|
this.#theme = options.theme;
|
|
29
|
+
this.#appInputBeforeInterrupt = options.appInputBeforeInterrupt === true;
|
|
28
30
|
}
|
|
29
31
|
get presented() {
|
|
30
32
|
return this.#app;
|
|
@@ -71,7 +73,7 @@ export class UiAppHost {
|
|
|
71
73
|
const app = this.#app;
|
|
72
74
|
if (app === null)
|
|
73
75
|
return { consumed: false };
|
|
74
|
-
if (data === INTERRUPT && this.#closeOnInterrupt) {
|
|
76
|
+
if (data === INTERRUPT && this.#closeOnInterrupt && !this.#appInputBeforeInterrupt) {
|
|
75
77
|
this.close();
|
|
76
78
|
return { consumed: true, render: true };
|
|
77
79
|
}
|
|
@@ -83,6 +85,10 @@ export class UiAppHost {
|
|
|
83
85
|
this.render();
|
|
84
86
|
return result;
|
|
85
87
|
}
|
|
88
|
+
if (data === INTERRUPT && this.#closeOnInterrupt) {
|
|
89
|
+
this.close();
|
|
90
|
+
return { consumed: true, render: true };
|
|
91
|
+
}
|
|
86
92
|
if (data === INTERRUPT)
|
|
87
93
|
return this.#interrupt();
|
|
88
94
|
return { consumed: false };
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
import { dialogFrameGeometry, insetDialogContent } from "../../contracts/presentation/index.js";
|
|
1
2
|
import { LineInput, renderInputRow } from "./line-input.js";
|
|
2
|
-
import {
|
|
3
|
+
import { renderShortcutHintsWithClose } from "./shortcut-hints.js";
|
|
3
4
|
import { displayWidth, truncateToWidth } from "./text.js";
|
|
4
5
|
const LABEL_COLUMN_CAP = 30;
|
|
5
6
|
const STEPPED_LABEL_COLUMN_CAP = 46;
|
|
7
|
+
/** One frame path for every semantic row, regardless of which dialog supplies the content. */
|
|
8
|
+
function dialogPanelLayout(width, theme) {
|
|
9
|
+
const geometry = dialogFrameGeometry(width);
|
|
10
|
+
return {
|
|
11
|
+
rule: theme.fg("border", "─".repeat(geometry.frameWidth)),
|
|
12
|
+
contentWidth: geometry.contentWidth,
|
|
13
|
+
contentRow: (painted, raw = painted) => insetDialogContent(pad(truncateToWidth(painted, geometry.contentWidth), geometry.contentWidth, raw), geometry),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
6
16
|
/** The column values start at: past the widest label, capped so one long name cannot push them off. */
|
|
7
17
|
export function dialogValueColumn(rows) {
|
|
8
18
|
const widest = Math.min(LABEL_COLUMN_CAP, Math.max(0, ...rows.map(row => displayWidth(row.label))));
|
|
@@ -14,11 +24,7 @@ export function dialogValueColumn(rows) {
|
|
|
14
24
|
*/
|
|
15
25
|
export function renderDialogPanel(state, width, theme) {
|
|
16
26
|
const labelColumn = Math.min(LABEL_COLUMN_CAP, Math.max(0, ...state.rows.map(row => displayWidth(row.label))));
|
|
17
|
-
const
|
|
18
|
-
const contentPadding = Math.min(1, Math.max(0, width));
|
|
19
|
-
const contentWidth = Math.max(0, width - contentPadding);
|
|
20
|
-
const inset = " ".repeat(contentPadding);
|
|
21
|
-
const contentRow = (painted, raw) => `${inset}${pad(truncateToWidth(painted, contentWidth), contentWidth, raw)}`;
|
|
27
|
+
const layout = dialogPanelLayout(width, theme);
|
|
22
28
|
const rows = state.rows.map((row, index) => {
|
|
23
29
|
const selected = index === state.index;
|
|
24
30
|
const padded = `${row.label}${" ".repeat(Math.max(0, labelColumn - displayWidth(row.label)))}`;
|
|
@@ -28,31 +34,27 @@ export function renderDialogPanel(state, width, theme) {
|
|
|
28
34
|
? `${theme.fg("accent", cursor)}${theme.fg("text", padded)} ${theme.fg("muted", row.value)}`
|
|
29
35
|
: `${cursor}${padded} ${theme.fg("muted", row.value)}`;
|
|
30
36
|
const painted = selected ? theme.highlight(content) : content;
|
|
31
|
-
return contentRow(painted, raw);
|
|
37
|
+
return layout.contentRow(painted, raw);
|
|
32
38
|
});
|
|
33
39
|
const description = state.rows[state.index]?.description ?? "";
|
|
34
40
|
const hint = typeof state.hint === "string"
|
|
35
41
|
? theme.fg("dim", state.hint)
|
|
36
|
-
: renderShortcutHintsWithClose(state.hint, theme, contentWidth, 0);
|
|
42
|
+
: renderShortcutHintsWithClose(state.hint, theme, layout.contentWidth, 0);
|
|
37
43
|
const plainHint = typeof state.hint === "string" ? state.hint : " ".repeat(displayWidth(hint));
|
|
38
44
|
return [
|
|
39
|
-
rule,
|
|
40
|
-
contentRow(theme.bold(theme.fg("accent", state.title)), state.title),
|
|
41
|
-
contentRow(theme.fg("dim", description), description),
|
|
45
|
+
layout.rule,
|
|
46
|
+
layout.contentRow(theme.bold(theme.fg("accent", state.title)), state.title),
|
|
47
|
+
layout.contentRow(theme.fg("dim", description), description),
|
|
42
48
|
"",
|
|
43
49
|
...rows,
|
|
44
50
|
"",
|
|
45
|
-
contentRow(hint, plainHint),
|
|
46
|
-
rule,
|
|
51
|
+
layout.contentRow(hint, plainHint),
|
|
52
|
+
layout.rule,
|
|
47
53
|
];
|
|
48
54
|
}
|
|
49
55
|
/** Owned stepped-selector composition for structured settings with nested choices. */
|
|
50
56
|
export function renderSteppedDialogPanel(state, width, theme) {
|
|
51
|
-
const
|
|
52
|
-
const contentPadding = Math.min(1, Math.max(0, width));
|
|
53
|
-
const contentWidth = Math.max(0, width - contentPadding);
|
|
54
|
-
const inset = " ".repeat(contentPadding);
|
|
55
|
-
const contentRow = (painted, raw) => `${inset}${pad(truncateToWidth(painted, contentWidth), contentWidth, raw)}`;
|
|
57
|
+
const layout = dialogPanelLayout(width, theme);
|
|
56
58
|
const labelColumn = Math.min(STEPPED_LABEL_COLUMN_CAP, Math.max(0, ...state.rows.map(row => displayWidth(`${row.label}${row.labelSuffix ?? ""}`))));
|
|
57
59
|
const rows = state.rows.map((row, index) => {
|
|
58
60
|
const selected = index === state.index;
|
|
@@ -65,31 +67,34 @@ export function renderSteppedDialogPanel(state, width, theme) {
|
|
|
65
67
|
const content = selected
|
|
66
68
|
? `${theme.fg("accent", cursor)}${label}${theme.fg("muted", suffix)}`
|
|
67
69
|
: `${cursor}${label}${theme.fg("muted", suffix)}`;
|
|
68
|
-
return contentRow(selected ? theme.highlight(content) : content, raw);
|
|
70
|
+
return layout.contentRow(selected ? theme.highlight(content) : content, raw);
|
|
69
71
|
});
|
|
70
72
|
const input = state.input === undefined
|
|
71
73
|
? []
|
|
72
|
-
: [
|
|
74
|
+
: [
|
|
75
|
+
...renderInputRow(state.input, layout.contentWidth, {
|
|
76
|
+
ruled: false,
|
|
77
|
+
promptGlyph: "> ",
|
|
78
|
+
promptRole: "accent",
|
|
79
|
+
theme,
|
|
80
|
+
}).lines.map(row => layout.contentRow(row)),
|
|
81
|
+
"",
|
|
82
|
+
];
|
|
73
83
|
const step = `(step ${state.step}/${state.steps})`;
|
|
74
84
|
const title = `${state.title} ${step}`;
|
|
75
85
|
const paintedTitle = `${theme.bold(theme.fg("accent", state.title))} ${theme.fg("dim", step)}`;
|
|
76
86
|
return [
|
|
77
|
-
rule,
|
|
78
|
-
contentRow(paintedTitle, title),
|
|
79
|
-
contentRow(theme.fg("dim", state.description), state.description),
|
|
87
|
+
layout.rule,
|
|
88
|
+
layout.contentRow(paintedTitle, title),
|
|
89
|
+
layout.contentRow(theme.fg("dim", state.description), state.description),
|
|
80
90
|
"",
|
|
81
91
|
...input,
|
|
82
92
|
...rows,
|
|
83
93
|
"",
|
|
84
|
-
|
|
85
|
-
rule,
|
|
94
|
+
layout.contentRow(renderShortcutHintsWithClose(state.hint, theme, layout.contentWidth)),
|
|
95
|
+
layout.rule,
|
|
86
96
|
];
|
|
87
97
|
}
|
|
88
|
-
function renderSteppedHint(entries, width, theme) {
|
|
89
|
-
const raw = shortcutHintsText(entries, 1, " · ");
|
|
90
|
-
const painted = renderShortcutHints(entries, theme, 1, theme.fg("dim", " · "));
|
|
91
|
-
return `${truncateToWidth(painted, width)}${" ".repeat(Math.max(0, width - displayWidth(raw)))}`;
|
|
92
|
-
}
|
|
93
98
|
/** The row a pointer report lands on, or null when it is not on one. */
|
|
94
99
|
export function dialogRowAt(frame, row, column, valueWidth) {
|
|
95
100
|
const at = row - frame.firstRow;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PromptMarkerRole } from "./prompt-input.js";
|
|
1
2
|
import type { UiTheme } from "./theme.js";
|
|
2
3
|
export type LineInputOutcome = {
|
|
3
4
|
readonly kind: "editing";
|
|
@@ -60,6 +61,8 @@ export interface InputRowOptions {
|
|
|
60
61
|
readonly ruled?: boolean;
|
|
61
62
|
/** Prompt marker including any following spacing. Defaults to the shared prompt glyph. */
|
|
62
63
|
readonly promptGlyph?: string;
|
|
64
|
+
/** Semantic foreground for a configured prompt marker. */
|
|
65
|
+
readonly promptRole?: PromptMarkerRole;
|
|
63
66
|
readonly theme?: Pick<UiTheme, "fg">;
|
|
64
67
|
}
|
|
65
68
|
export interface InputRow {
|
|
@@ -220,5 +220,7 @@ export function renderInputRow(input, width, options = {}) {
|
|
|
220
220
|
const body = empty
|
|
221
221
|
? `${caretCell(placeholder.slice(0, 1))}${faint(placeholder.slice(1))}`
|
|
222
222
|
: `${before}${caretCell(under)}${after}`;
|
|
223
|
-
return {
|
|
223
|
+
return {
|
|
224
|
+
lines: presentation.render(width, () => ({ rows: [body] }), options.ruled, 0, options.promptGlyph, options.promptRole),
|
|
225
|
+
};
|
|
224
226
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { type UiTheme } from "./theme.js";
|
|
2
|
-
export declare const PROMPT_GLYPH = "
|
|
3
|
-
export declare const PROMPT_PREFIX_WIDTH =
|
|
2
|
+
export declare const PROMPT_GLYPH = "";
|
|
3
|
+
export declare const PROMPT_PREFIX_WIDTH = 0;
|
|
4
4
|
type RuleTheme = Pick<UiTheme, "fg">;
|
|
5
5
|
export declare function promptRuleText(text: string, theme: RuleTheme): string;
|
|
6
6
|
export declare function promptRule(width: number, theme: RuleTheme): string;
|
|
7
|
-
export
|
|
7
|
+
export type PromptMarkerRole = "muted" | "accent";
|
|
8
|
+
export declare function promptArrow(text: string, theme: RuleTheme, role?: PromptMarkerRole): string;
|
|
8
9
|
export interface PromptInputBody {
|
|
9
10
|
readonly rows: readonly string[];
|
|
10
11
|
/** Semantic border fragments retain editor history and scroll annotations. */
|
|
@@ -31,6 +32,6 @@ export declare class PromptInput {
|
|
|
31
32
|
contentWidth: number;
|
|
32
33
|
layoutWidth: number;
|
|
33
34
|
};
|
|
34
|
-
render(width: number, renderBody: (innerWidth: number) => PromptInputBody, ruled?: boolean, padding?: number, promptGlyph?: string): string[];
|
|
35
|
+
render(width: number, renderBody: (innerWidth: number) => PromptInputBody, ruled?: boolean, padding?: number, promptGlyph?: string, promptRole?: PromptMarkerRole): string[];
|
|
35
36
|
}
|
|
36
37
|
export {};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { displayWidth, truncateToWidth } from "./text.js";
|
|
2
2
|
import { PLAIN_THEME } from "./theme.js";
|
|
3
|
-
export const PROMPT_GLYPH = "
|
|
4
|
-
export const PROMPT_PREFIX_WIDTH =
|
|
3
|
+
export const PROMPT_GLYPH = "";
|
|
4
|
+
export const PROMPT_PREFIX_WIDTH = 0;
|
|
5
5
|
export function promptRuleText(text, theme) {
|
|
6
6
|
return theme.fg("border", text);
|
|
7
7
|
}
|
|
8
8
|
export function promptRule(width, theme) {
|
|
9
9
|
return promptRuleText("─".repeat(Math.max(0, width)), theme);
|
|
10
10
|
}
|
|
11
|
-
export function promptArrow(text, theme) {
|
|
12
|
-
|
|
11
|
+
export function promptArrow(text, theme, role = "muted") {
|
|
12
|
+
if (role === "accent" && text.startsWith(">"))
|
|
13
|
+
return `${theme.fg(role, ">")}${text.slice(1)}`;
|
|
14
|
+
return theme.fg(role, text);
|
|
13
15
|
}
|
|
14
16
|
/** Composes shared input chrome around controller-owned body rows without owning editing state. */
|
|
15
17
|
export class PromptInput {
|
|
@@ -26,7 +28,7 @@ export class PromptInput {
|
|
|
26
28
|
const contentWidth = Math.max(1, innerWidth - paddingX * 2);
|
|
27
29
|
return { prefixWidth, innerWidth, paddingX, contentWidth, layoutWidth: Math.max(1, contentWidth - (paddingX ? 0 : 1)) };
|
|
28
30
|
}
|
|
29
|
-
render(width, renderBody, ruled = true, padding = 0, promptGlyph = PROMPT_GLYPH) {
|
|
31
|
+
render(width, renderBody, ruled = true, padding = 0, promptGlyph = PROMPT_GLYPH, promptRole = "muted") {
|
|
30
32
|
if (width <= 0)
|
|
31
33
|
return [];
|
|
32
34
|
const { prefixWidth, innerWidth } = this.geometry(width, padding, promptGlyph);
|
|
@@ -40,7 +42,7 @@ export class PromptInput {
|
|
|
40
42
|
return clipped + " ".repeat(Math.max(0, width - this.metrics.measure(clipped)));
|
|
41
43
|
};
|
|
42
44
|
const rows = body.rows.map((row, index) => fit(`${index === 0
|
|
43
|
-
? promptArrow(promptGlyph, this.theme)
|
|
45
|
+
? promptArrow(promptGlyph, this.theme, promptRole)
|
|
44
46
|
: " ".repeat(prefixWidth)}${row}`));
|
|
45
47
|
const rule = (fragment) => fragment === undefined
|
|
46
48
|
? promptRule(width, this.theme)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export type UiTimeFormat = "20:00" | "8:00 PM";
|
|
2
2
|
/** Deterministic local hour/minute presentation shared by A1-owned time labels. */
|
|
3
3
|
export declare function formatLocalTime(source: Date | number, format?: UiTimeFormat): string | null;
|
|
4
|
-
/** Visible width of the submitted-prompt prefix (
|
|
5
|
-
export declare const SUBMITTED_PROMPT_PREFIX_WIDTH =
|
|
4
|
+
/** Visible width of the submitted-prompt prefix (` ❯ `). */
|
|
5
|
+
export declare const SUBMITTED_PROMPT_PREFIX_WIDTH = 3;
|
|
6
6
|
export interface SubmittedPromptStyle {
|
|
7
7
|
readonly prefix?: (text: string) => string;
|
|
8
8
|
readonly timestamp?: (text: string) => string;
|
|
@@ -10,8 +10,8 @@ export function formatLocalTime(source, format = "8:00 PM") {
|
|
|
10
10
|
return `${String(hour).padStart(2, "0")}:${minute}`;
|
|
11
11
|
return `${hour % 12 || 12}:${minute} ${hour < 12 ? "AM" : "PM"}`;
|
|
12
12
|
}
|
|
13
|
-
/** Visible width of the submitted-prompt prefix (
|
|
14
|
-
export const SUBMITTED_PROMPT_PREFIX_WIDTH =
|
|
13
|
+
/** Visible width of the submitted-prompt prefix (` ❯ `). */
|
|
14
|
+
export const SUBMITTED_PROMPT_PREFIX_WIDTH = 3;
|
|
15
15
|
const TIMESTAMP_MARGIN = 3;
|
|
16
16
|
const MIN_USEFUL_CONTENT_WIDTH = 8;
|
|
17
17
|
/** Formats transcript metadata, never render time, in the active A1 time format. */
|
|
@@ -42,7 +42,7 @@ export function composeSubmittedPromptRows(contentRows, width, source, style = {
|
|
|
42
42
|
if (width <= 0)
|
|
43
43
|
return [];
|
|
44
44
|
const layout = submittedPromptLayout(width, source, format);
|
|
45
|
-
const prefix = (style.prefix ?? identity)("❯ ");
|
|
45
|
+
const prefix = (style.prefix ?? identity)(" ❯ ");
|
|
46
46
|
const rows = (contentRows.length === 0 ? [""] : contentRows).map((row, index) => {
|
|
47
47
|
const content = truncateToWidth(row, layout.contentWidth);
|
|
48
48
|
return `${index === 0 ? prefix : " ".repeat(SUBMITTED_PROMPT_PREFIX_WIDTH)}${content}`;
|
|
@@ -29,10 +29,12 @@ export function withScrollbarRail(lines, geometry, contentWidth, theme, options
|
|
|
29
29
|
*/
|
|
30
30
|
export function renderEmptyState(message, mark, height, width, theme) {
|
|
31
31
|
const middle = Math.floor(height / 2);
|
|
32
|
+
const messageRow = Math.min(Math.max(0, height - 1), middle + 1);
|
|
33
|
+
const markRow = messageRow - 1;
|
|
32
34
|
return Array.from({ length: Math.max(0, height) }, (_line, index) => {
|
|
33
|
-
if (index ===
|
|
35
|
+
if (index === markRow)
|
|
34
36
|
return centre(theme.fg("muted", mark), mark, width);
|
|
35
|
-
return index ===
|
|
37
|
+
return index === messageRow ? centre(theme.fg("muted", message), message, width) : "";
|
|
36
38
|
});
|
|
37
39
|
}
|
|
38
40
|
function centre(painted, raw, width) {
|
|
@@ -178,7 +178,7 @@ export declare class TranscriptViewport {
|
|
|
178
178
|
releaseSelection(): boolean;
|
|
179
179
|
clearSelection(): boolean;
|
|
180
180
|
selectedText(): string | null;
|
|
181
|
-
/** Captures
|
|
181
|
+
/** Captures selected text, including document rows of a visible selection scrolled off-screen. */
|
|
182
182
|
captureSelectedText(): SelectionCopySnapshot | null;
|
|
183
183
|
scrollBy(lines: number, now?: number): boolean;
|
|
184
184
|
scrollTo(position: number, now?: number): boolean;
|
|
@@ -15,6 +15,19 @@ const IDENTITY_ROW = (row) => row;
|
|
|
15
15
|
function documentSelectionAnchor(row, source, liveFromBottom) {
|
|
16
16
|
return { kind: "document", row, source, identity: stripAnsi(source).trimEnd(), liveFromBottom };
|
|
17
17
|
}
|
|
18
|
+
function sameSelectionRowAnchor(left, right) {
|
|
19
|
+
if (left.kind !== right.kind)
|
|
20
|
+
return false;
|
|
21
|
+
if (left.kind === "document" && right.kind === "document") {
|
|
22
|
+
return left.row === right.row && left.source === right.source && left.liveFromBottom === right.liveFromBottom;
|
|
23
|
+
}
|
|
24
|
+
if (left.kind === "screen" && right.kind === "screen") {
|
|
25
|
+
return left.row === right.row && left.source === right.source;
|
|
26
|
+
}
|
|
27
|
+
if (left.kind === "transient" && right.kind === "transient")
|
|
28
|
+
return left.fromBottom === right.fromBottom;
|
|
29
|
+
return left.kind === "dock" && right.kind === "dock" && left.fromBottom === right.fromBottom;
|
|
30
|
+
}
|
|
18
31
|
function viewportSelectionAnchor(row, source, selectableDocumentRowCount, documentRowCount) {
|
|
19
32
|
return row < selectableDocumentRowCount
|
|
20
33
|
? documentSelectionAnchor(row, source)
|
|
@@ -140,7 +153,12 @@ export class TranscriptViewport {
|
|
|
140
153
|
this.scrollBy(1, now);
|
|
141
154
|
else if (autoScroll && frameRow <= 1 && this.#scrollTop > 0)
|
|
142
155
|
this.scrollBy(-1, now);
|
|
143
|
-
const
|
|
156
|
+
const pointerLine = clamp(frameRow - 1, 0, this.#selectionRows.length - 1);
|
|
157
|
+
// Invariant: a pinned prompt alias is chrome; an upward drag reaching it keeps the pointer
|
|
158
|
+
// column on the first visible content row.
|
|
159
|
+
const line = pointerLine < this.#firstSelectableRow && pointerLine < selection.anchor.line
|
|
160
|
+
? this.#firstSelectableRow
|
|
161
|
+
: pointerLine;
|
|
144
162
|
const selectionWidth = line < (this.#frame?.hits.viewportHeight ?? 0)
|
|
145
163
|
? this.#contentWidth
|
|
146
164
|
: this.#selectionWidth;
|
|
@@ -151,12 +169,13 @@ export class TranscriptViewport {
|
|
|
151
169
|
? { line, column: targetColumn }
|
|
152
170
|
: textSelectionPointAt(line, targetColumn, this.#selectionRows[line] ?? "");
|
|
153
171
|
const next = extendTextSelection(selection, point);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
172
|
+
const headAnchor = this.#selectionAnchorAt(line);
|
|
173
|
+
if (headAnchor === undefined || this.#selectionAnchors === undefined) {
|
|
174
|
+
this.clearSelection();
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
// Invariant: edge auto-scroll changes the document row under a still pointer; that advances the head.
|
|
178
|
+
if (next !== selection || !sameSelectionRowAnchor(this.#selectionAnchors.head, headAnchor)) {
|
|
160
179
|
this.#selection = next;
|
|
161
180
|
this.#selectionAnchors = { ...this.#selectionAnchors, head: headAnchor };
|
|
162
181
|
this.#selectionRevision += 1;
|
|
@@ -184,25 +203,66 @@ export class TranscriptViewport {
|
|
|
184
203
|
return true;
|
|
185
204
|
}
|
|
186
205
|
selectedText() {
|
|
187
|
-
const
|
|
188
|
-
if (
|
|
206
|
+
const visible = this.#visibleSelection();
|
|
207
|
+
if (visible === undefined)
|
|
189
208
|
return null;
|
|
190
|
-
const
|
|
209
|
+
const offscreen = offscreenSelectionRows(this.#selection, this.#selectionAnchors, this.#scrollTop, this.#firstSelectableRow, this.#viewportHeight, this.#documentRows.length);
|
|
210
|
+
// Rationale: edge auto-scroll grows a drag beyond one screen. Paint stays clipped to the
|
|
211
|
+
// frame, while copy reads the selected document rows past either viewport edge and beneath
|
|
212
|
+
// the floating bottom control.
|
|
213
|
+
const selectionRows = [];
|
|
214
|
+
const rowAnchors = [];
|
|
215
|
+
const controlLine = (this.#frame?.hits.bottom?.row ?? 0) - 1;
|
|
216
|
+
const pushDocumentRow = (row) => {
|
|
217
|
+
const source = this.#documentRows[row] ?? "";
|
|
218
|
+
selectionRows.push(truncateToWidth(source, this.#contentWidth));
|
|
219
|
+
rowAnchors.push(documentSelectionAnchor(row, source));
|
|
220
|
+
};
|
|
221
|
+
const pushBelow = () => {
|
|
222
|
+
for (let row = offscreen.below?.from ?? 0; row <= (offscreen.below?.to ?? -1); row += 1)
|
|
223
|
+
pushDocumentRow(row);
|
|
224
|
+
};
|
|
225
|
+
for (let row = offscreen.above?.from ?? 0; row <= (offscreen.above?.to ?? -1); row += 1)
|
|
226
|
+
pushDocumentRow(row);
|
|
227
|
+
if (offscreen.belowFirst)
|
|
228
|
+
pushBelow();
|
|
229
|
+
for (let line = visible.start.line; line <= visible.end.line; line += 1) {
|
|
191
230
|
const anchor = this.#selectionRowAnchors[line];
|
|
231
|
+
if (line === controlLine && anchor?.kind === "document")
|
|
232
|
+
pushDocumentRow(anchor.row);
|
|
233
|
+
else {
|
|
234
|
+
selectionRows.push(this.#selectionRows[line] ?? "");
|
|
235
|
+
rowAnchors.push(anchor);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!offscreen.belowFirst)
|
|
239
|
+
pushBelow();
|
|
240
|
+
const offscreenStart = offscreen.above !== undefined || offscreen.belowFirst === true;
|
|
241
|
+
const offscreenEnd = offscreen.below !== undefined && !offscreen.belowFirst;
|
|
242
|
+
const selection = {
|
|
243
|
+
...visible,
|
|
244
|
+
start: { line: 0, column: offscreenStart ? offscreen.startColumn ?? 0 : visible.start.column },
|
|
245
|
+
end: {
|
|
246
|
+
line: selectionRows.length - 1,
|
|
247
|
+
column: offscreenEnd ? offscreen.endColumn ?? Number.MAX_SAFE_INTEGER : visible.end.column,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
const copiedLines = Array.from({ length: Math.max(0, selection.end.line - selection.start.line + 1) }, (_value, index) => selection.start.line + index).filter(line => {
|
|
251
|
+
const anchor = rowAnchors[line];
|
|
192
252
|
return anchor?.kind === "document" && anchor.row < this.#selectableDocumentRowCount
|
|
193
253
|
|| anchor?.kind === "dock" && this.#copyableDockRows.has(anchor.fromBottom);
|
|
194
254
|
});
|
|
195
255
|
if (copiedLines.length === 0)
|
|
196
256
|
return null;
|
|
197
|
-
const transcriptLines = copiedLines.filter(line =>
|
|
257
|
+
const transcriptLines = copiedLines.filter(line => rowAnchors[line]?.kind === "document");
|
|
198
258
|
const prompt = transcriptLines.length === 0 ? undefined : this.#promptAnchors.find(candidate => transcriptLines.every(line => {
|
|
199
|
-
const anchor =
|
|
259
|
+
const anchor = rowAnchors[line];
|
|
200
260
|
return anchor?.kind === "document" && anchor.row >= candidate.firstRow && anchor.row <= candidate.lastRow;
|
|
201
261
|
}));
|
|
202
262
|
const included = new Set(copiedLines);
|
|
203
|
-
const text = textSelectionText(selection,
|
|
204
|
-
const row =
|
|
205
|
-
const anchor =
|
|
263
|
+
const text = textSelectionText(selection, selectionRows, line => {
|
|
264
|
+
const row = selectionRows[line] ?? "";
|
|
265
|
+
const anchor = rowAnchors[line];
|
|
206
266
|
const dock = anchor?.kind === "dock" ? this.#copyableDockRows.get(anchor.fromBottom) : undefined;
|
|
207
267
|
if (prompt !== undefined && anchor?.kind === "document") {
|
|
208
268
|
const first = anchor.row === prompt.firstRow;
|
|
@@ -211,11 +271,12 @@ export class TranscriptViewport {
|
|
|
211
271
|
: row;
|
|
212
272
|
return selectionCopyLineContent(copyRow, first ? "first" : "continuation");
|
|
213
273
|
}
|
|
214
|
-
|
|
274
|
+
// Invariant: the dock editor has no prefix; transcript prompts still carry chrome.
|
|
275
|
+
return dock?.prompt !== undefined ? usefulTextLineContent(row, 1) : usefulTextLineContent(row);
|
|
215
276
|
}, line => included.has(line));
|
|
216
277
|
return text.length === 0 ? null : text;
|
|
217
278
|
}
|
|
218
|
-
/** Captures
|
|
279
|
+
/** Captures selected text, including document rows of a visible selection scrolled off-screen. */
|
|
219
280
|
captureSelectedText() {
|
|
220
281
|
if (!this.#copyableSelection)
|
|
221
282
|
return null;
|
|
@@ -470,7 +531,7 @@ export class TranscriptViewport {
|
|
|
470
531
|
const bottomHovered = pointer !== undefined && pointer.row === bottomHit.row
|
|
471
532
|
&& pointer.column >= bottomHit.columnStart && pointer.column <= bottomHit.columnEnd;
|
|
472
533
|
control = { row, left, text: `${CONTROL_STYLE_RESET}${theme.bottomControl(label, bottomHovered)}` };
|
|
473
|
-
// Invariant: the control floats above selection;
|
|
534
|
+
// Invariant: the control floats above selection; copy restores the text beneath it.
|
|
474
535
|
selectionRows[row] = overlaySpan(padRowPreservingBackground(selectionRows[row] ?? "", width), left, left + labelWidth, " ".repeat(labelWidth));
|
|
475
536
|
}
|
|
476
537
|
}
|
|
@@ -701,7 +762,7 @@ export class TranscriptViewport {
|
|
|
701
762
|
const anchors = this.#selectionAnchors;
|
|
702
763
|
if (selection === undefined || anchors === undefined)
|
|
703
764
|
return;
|
|
704
|
-
const project = (anchor) => {
|
|
765
|
+
const project = (anchor, moving) => {
|
|
705
766
|
if (anchor.kind === "dock") {
|
|
706
767
|
const dockIndex = dockRows.length - anchor.fromBottom - 1;
|
|
707
768
|
return dockIndex < 0 || dockIndex >= dockRows.length
|
|
@@ -743,7 +804,7 @@ export class TranscriptViewport {
|
|
|
743
804
|
// Concurrency: a streaming chunk may replace or reflow the pointer's current live-tail
|
|
744
805
|
// row before the next motion report. During the held gesture only, retain its distance
|
|
745
806
|
// from the persistent tail; release restores identity-only projection.
|
|
746
|
-
if (selection.selecting && anchor.liveFromBottom !== undefined && this.#liveTailStartRow !== undefined) {
|
|
807
|
+
if (selection.selecting && moving && anchor.liveFromBottom !== undefined && this.#liveTailStartRow !== undefined) {
|
|
747
808
|
const documentRow = this.#selectableDocumentRowCount - anchor.liveFromBottom - 1;
|
|
748
809
|
if (documentRow >= this.#liveTailStartRow && documentRow < this.#selectableDocumentRowCount) {
|
|
749
810
|
const visible = this.#selectionRowAnchors.findIndex(candidate => candidate?.kind === "document" && candidate.row === documentRow);
|
|
@@ -755,10 +816,18 @@ export class TranscriptViewport {
|
|
|
755
816
|
}
|
|
756
817
|
}
|
|
757
818
|
}
|
|
819
|
+
// Concurrency: streaming may restyle a held endpoint's row; it keeps its document index.
|
|
820
|
+
// Following the tail would drag the press row toward the newest output.
|
|
821
|
+
if (selection.selecting && anchor.row >= 0 && anchor.row < documentRows.length) {
|
|
822
|
+
return {
|
|
823
|
+
line: anchor.row - this.#scrollTop,
|
|
824
|
+
anchor: documentSelectionAnchor(anchor.row, documentRows[anchor.row] ?? "", anchor.liveFromBottom),
|
|
825
|
+
};
|
|
826
|
+
}
|
|
758
827
|
return undefined;
|
|
759
828
|
};
|
|
760
|
-
const projectedAnchor = project(anchors.anchor);
|
|
761
|
-
const projectedHead = project(anchors.head);
|
|
829
|
+
const projectedAnchor = project(anchors.anchor, false);
|
|
830
|
+
const projectedHead = project(anchors.head, true);
|
|
762
831
|
if (projectedAnchor === undefined || projectedHead === undefined) {
|
|
763
832
|
this.clearSelection();
|
|
764
833
|
return;
|
|
@@ -778,11 +847,59 @@ export class TranscriptViewport {
|
|
|
778
847
|
// Invariant: mixed viewport/dock ranges project across the same complete frame regardless
|
|
779
848
|
// of origin. Document-only ranges remain clipped to the scrollable viewport when off-screen.
|
|
780
849
|
const rows = crossesDock ? this.#selectionRows.length : this.#viewportHeight;
|
|
781
|
-
|
|
850
|
+
const ordered = orderedTextSelection(dockOrderedSelection(this.#selection, anchors, this.#viewportHeight));
|
|
851
|
+
return visibleTextSelection(ordered, rows, this.#firstSelectableRow);
|
|
782
852
|
}
|
|
783
853
|
#updateCopyableSelection() {
|
|
784
|
-
|
|
854
|
+
// Performance: off-screen rows count as copyable by range alone; capture trims whitespace later.
|
|
855
|
+
const offscreen = offscreenSelectionRows(this.#selection, this.#selectionAnchors, this.#scrollTop, this.#firstSelectableRow, this.#viewportHeight, this.#documentRows.length);
|
|
856
|
+
const selectable = [offscreen.above, offscreen.below]
|
|
857
|
+
.some(range => range !== undefined && range.from < this.#selectableDocumentRowCount);
|
|
858
|
+
this.#copyableSelection = selectable && this.#visibleSelection() !== undefined || this.selectedText() !== null;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Invariant: dock rows follow the whole transcript, so a document endpoint scrolled below the
|
|
863
|
+
* viewport orders before every dock row instead of at its numeric frame line among them.
|
|
864
|
+
*/
|
|
865
|
+
function dockOrderedSelection(selection, anchors, viewportHeight) {
|
|
866
|
+
if (selection === undefined || anchors === undefined || anchors.anchor.kind !== "dock" && anchors.head.kind !== "dock") {
|
|
867
|
+
return selection;
|
|
785
868
|
}
|
|
869
|
+
const place = (point, kind) => kind === "document" && point.line >= viewportHeight ? { line: viewportHeight, column: 1, from: 0, to: 0 } : point;
|
|
870
|
+
return { ...selection, anchor: place(selection.anchor, anchors.anchor.kind), head: place(selection.head, anchors.head.kind) };
|
|
871
|
+
}
|
|
872
|
+
/** Document rows of a selection whose endpoints lie beyond the visible viewport edges. */
|
|
873
|
+
function offscreenSelectionRows(selection, anchors, scrollTop, firstSelectableRow, viewportHeight, documentRowCount) {
|
|
874
|
+
const ordered = orderedTextSelection(dockOrderedSelection(selection, anchors, viewportHeight));
|
|
875
|
+
if (selection === undefined || ordered === undefined || anchors === undefined)
|
|
876
|
+
return {};
|
|
877
|
+
const placed = dockOrderedSelection(selection, anchors, viewportHeight);
|
|
878
|
+
const anchorFirst = placed.anchor.line < placed.head.line
|
|
879
|
+
|| placed.anchor.line === placed.head.line && placed.anchor.column <= placed.head.column;
|
|
880
|
+
const top = anchorFirst ? anchors.anchor : anchors.head;
|
|
881
|
+
const bottom = anchorFirst ? anchors.head : anchors.anchor;
|
|
882
|
+
const topPoint = anchorFirst ? selection.anchor : selection.head;
|
|
883
|
+
const bottomPoint = anchorFirst ? selection.head : selection.anchor;
|
|
884
|
+
const firstVisibleRow = scrollTop + firstSelectableRow;
|
|
885
|
+
const pastVisibleRow = scrollTop + viewportHeight;
|
|
886
|
+
const columns = {
|
|
887
|
+
startColumn: selection.fullRow ? 0 : topPoint.from ?? Math.max(0, topPoint.column - 1),
|
|
888
|
+
endColumn: selection.fullRow ? Number.MAX_SAFE_INTEGER : bottomPoint.to ?? bottomPoint.column,
|
|
889
|
+
};
|
|
890
|
+
if (top.kind === "document" && bottom.kind === "dock" && top.row >= pastVisibleRow) {
|
|
891
|
+
return { ...columns, below: { from: top.row, to: documentRowCount - 1 }, belowFirst: true };
|
|
892
|
+
}
|
|
893
|
+
return {
|
|
894
|
+
...columns,
|
|
895
|
+
...(top.kind === "document" && top.row < firstVisibleRow && ordered.start.line < firstSelectableRow
|
|
896
|
+
? { above: { from: top.row, to: firstVisibleRow - 1 } }
|
|
897
|
+
: {}),
|
|
898
|
+
...(top.kind === "document" && bottom.kind === "document" && bottom.row >= pastVisibleRow
|
|
899
|
+
&& ordered.end.line >= viewportHeight
|
|
900
|
+
? { below: { from: pastVisibleRow, to: bottom.row } }
|
|
901
|
+
: {}),
|
|
902
|
+
};
|
|
786
903
|
}
|
|
787
904
|
function visibleTextSelection(selection, rowCount, firstRow) {
|
|
788
905
|
if (selection === undefined || rowCount <= firstRow || selection.end.line < firstRow || selection.start.line >= rowCount)
|