poly-weaver 0.12.2 → 0.12.4
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/ansi.d.ts +12 -0
- package/dist/ansi.d.ts.map +1 -1
- package/dist/ansi.js +334 -9
- package/dist/ansi.js.map +1 -1
- package/dist/offscreen/artifact-pane.d.ts +5 -0
- package/dist/offscreen/artifact-pane.d.ts.map +1 -1
- package/dist/offscreen/artifact-pane.js +20 -1
- package/dist/offscreen/artifact-pane.js.map +1 -1
- package/dist/offscreen/controller.d.ts +1 -0
- package/dist/offscreen/controller.d.ts.map +1 -1
- package/dist/offscreen/controller.js +105 -0
- package/dist/offscreen/controller.js.map +1 -1
- package/dist/offscreen/inquiry-pane.d.ts +2 -0
- package/dist/offscreen/inquiry-pane.d.ts.map +1 -1
- package/dist/offscreen/inquiry-pane.js +19 -0
- package/dist/offscreen/inquiry-pane.js.map +1 -1
- package/dist/offscreen/inquiry-sender.js +1 -1
- package/dist/offscreen/inquiry-sender.js.map +1 -1
- package/dist/offscreen/panes.d.ts +2 -0
- package/dist/offscreen/panes.d.ts.map +1 -1
- package/dist/offscreen/split-inquiry.d.ts +17 -0
- package/dist/offscreen/split-inquiry.d.ts.map +1 -1
- package/dist/offscreen/split-inquiry.js +107 -1
- package/dist/offscreen/split-inquiry.js.map +1 -1
- package/dist/providers/codex/plan-mode-activator.js +0 -3
- package/dist/providers/codex/plan-mode-activator.js.map +1 -1
- package/dist/providers/option-catalog.d.ts.map +1 -1
- package/dist/providers/option-catalog.js +23 -0
- package/dist/providers/option-catalog.js.map +1 -1
- package/dist/session/locator.d.ts +2 -2
- package/dist/session/locator.d.ts.map +1 -1
- package/dist/session/locator.js +2 -2
- package/dist/session/locator.js.map +1 -1
- package/dist/status-bar.d.ts +5 -2
- package/dist/status-bar.d.ts.map +1 -1
- package/dist/status-bar.js +18 -20
- package/dist/status-bar.js.map +1 -1
- package/dist/terminal/clipboard-write.d.ts +17 -0
- package/dist/terminal/clipboard-write.d.ts.map +1 -0
- package/dist/terminal/clipboard-write.js +58 -0
- package/dist/terminal/clipboard-write.js.map +1 -0
- package/dist/terminal/host.d.ts +90 -0
- package/dist/terminal/host.d.ts.map +1 -1
- package/dist/terminal/host.js +858 -17
- package/dist/terminal/host.js.map +1 -1
- package/dist/terminal/input-router.d.ts +21 -6
- package/dist/terminal/input-router.d.ts.map +1 -1
- package/dist/terminal/input-router.js +30 -8
- package/dist/terminal/input-router.js.map +1 -1
- package/dist/terminal/mouse-mode-tracker.d.ts +1 -0
- package/dist/terminal/mouse-mode-tracker.d.ts.map +1 -1
- package/dist/terminal/mouse-mode-tracker.js +3 -0
- package/dist/terminal/mouse-mode-tracker.js.map +1 -1
- package/dist/terminal/osc52.d.ts +1 -0
- package/dist/terminal/osc52.d.ts.map +1 -1
- package/dist/terminal/osc52.js +3 -0
- package/dist/terminal/osc52.js.map +1 -1
- package/dist/terminal/paste-submit.d.ts +1 -1
- package/dist/terminal/paste-submit.d.ts.map +1 -1
- package/dist/terminal/paste-submit.js +2 -2
- package/dist/terminal/paste-submit.js.map +1 -1
- package/dist/terminal/render.d.ts +5 -0
- package/dist/terminal/render.d.ts.map +1 -1
- package/dist/terminal/render.js +14 -4
- package/dist/terminal/render.js.map +1 -1
- package/dist/terminal/selection.d.ts +101 -0
- package/dist/terminal/selection.d.ts.map +1 -0
- package/dist/terminal/selection.js +517 -0
- package/dist/terminal/selection.js.map +1 -0
- package/dist/terminal/virtual-terminal.d.ts +5 -1
- package/dist/terminal/virtual-terminal.d.ts.map +1 -1
- package/dist/terminal/virtual-terminal.js +10 -0
- package/dist/terminal/virtual-terminal.js.map +1 -1
- package/dist/user/host-curate-prompt.d.ts +2 -0
- package/dist/user/host-curate-prompt.d.ts.map +1 -1
- package/dist/user/host-curate-prompt.js +239 -43
- package/dist/user/host-curate-prompt.js.map +1 -1
- package/dist/user/host-prompt.d.ts.map +1 -1
- package/dist/user/host-prompt.js +257 -14
- package/dist/user/host-prompt.js.map +1 -1
- package/dist/user/selectable-doc-pane.d.ts +13 -0
- package/dist/user/selectable-doc-pane.d.ts.map +1 -0
- package/dist/user/selectable-doc-pane.js +34 -0
- package/dist/user/selectable-doc-pane.js.map +1 -0
- package/dist/user/split-surface.d.ts +2 -0
- package/dist/user/split-surface.d.ts.map +1 -1
- package/dist/user/split-surface.js +17 -0
- package/dist/user/split-surface.js.map +1 -1
- package/package.json +1 -1
package/dist/terminal/host.js
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
|
-
import { ALT_SCREEN_OFF, ALT_SCREEN_ON, BEGIN_SYNC_UPDATE, CLEAR_SCREEN, CURSOR_HOME, END_SYNC_UPDATE, HIDE_CURSOR, MOUSE_OFF, QUERY_SYNC_UPDATE, RESET_SCROLL_REGION, SHOW_CURSOR, padOrClip, } from "../ansi.js";
|
|
1
|
+
import { ALT_SCREEN_OFF, ALT_SCREEN_ON, BEGIN_SYNC_UPDATE, CLEAR_SCREEN, CURSOR_HOME, END_SYNC_UPDATE, HIDE_CURSOR, MOUSE_OFF, QUERY_SYNC_UPDATE, RESET_SCROLL_REGION, SHOW_CURSOR, applySelectionBackgroundRange, padOrClip, } from "../ansi.js";
|
|
2
2
|
import { computeTerminalLayout } from "./layout.js";
|
|
3
3
|
import { VirtualTerminal } from "./virtual-terminal.js";
|
|
4
|
-
import { renderChildViewportRows, renderViewportLines, renderStatusRowBody, shouldShowVtCursor, } from "./render.js";
|
|
4
|
+
import { composeAbsoluteRow, renderChildViewportRows, renderViewportLines, renderStatusRowBody, shouldShowVtCursor, } from "./render.js";
|
|
5
5
|
import { BufferOrchestrationOutput, formatOrchestrationMessage, } from "./output.js";
|
|
6
|
-
import { InputRouter, } from "./input-router.js";
|
|
6
|
+
import { InputRouter, parseSgrMouseFrame, } from "./input-router.js";
|
|
7
7
|
import { createKeyEventSource } from "./key-event-source.js";
|
|
8
8
|
import { ChildKittyNegotiator } from "./kitty-negotiator.js";
|
|
9
9
|
import { MouseModeReconciler, MouseModeTracker, } from "./mouse-mode-tracker.js";
|
|
10
10
|
import { buildOsc52Forward } from "./osc52.js";
|
|
11
|
+
import { copyToClipboard } from "./clipboard-write.js";
|
|
12
|
+
import { Selection, } from "./selection.js";
|
|
11
13
|
import { buildSplitSurface, buildVerticalStackSurface, computeSplitMetrics, SPLIT_COLLAPSE_AT_COLS, } from "../user/split-surface.js";
|
|
12
14
|
const MAIN_CHILD_MOUSE_SOURCE = "main-child";
|
|
13
15
|
const HOST_MOUSE_SOURCE = "host-owned";
|
|
14
16
|
const AGENT_SPLIT_MOUSE_SOURCE = "agent-split";
|
|
15
|
-
const HOST_MOUSE_CAPTURE_MODES = [
|
|
17
|
+
const HOST_MOUSE_CAPTURE_MODES = [1002, 1006];
|
|
18
|
+
const MULTI_CLICK_MS = 500;
|
|
19
|
+
const DRAG_SCROLL_INTERVAL_MS = 50;
|
|
20
|
+
const DRAG_SCROLL_LINES = 1;
|
|
21
|
+
const DRAG_EDGE_LOSS_MS = 80;
|
|
16
22
|
/**
|
|
17
23
|
* Parent-owned orchestration terminal host.
|
|
18
24
|
*
|
|
@@ -38,6 +44,12 @@ export class TerminalHost {
|
|
|
38
44
|
router;
|
|
39
45
|
bufferOutput;
|
|
40
46
|
onDump;
|
|
47
|
+
clipboardCopy;
|
|
48
|
+
now;
|
|
49
|
+
setIntervalFn;
|
|
50
|
+
clearIntervalFn;
|
|
51
|
+
setTimeoutFn;
|
|
52
|
+
clearTimeoutFn;
|
|
41
53
|
interruptListeners = new Set();
|
|
42
54
|
statusBar;
|
|
43
55
|
childHandle;
|
|
@@ -80,6 +92,15 @@ export class TerminalHost {
|
|
|
80
92
|
externalChildStatus;
|
|
81
93
|
syncOutputSupported = false;
|
|
82
94
|
lastFrameRows = null;
|
|
95
|
+
lastBaseRows = null;
|
|
96
|
+
selection = new Selection();
|
|
97
|
+
gestureOwner = "none";
|
|
98
|
+
lastClickTime = 0;
|
|
99
|
+
lastClickCell;
|
|
100
|
+
clickCount = 0;
|
|
101
|
+
dragScroll;
|
|
102
|
+
dragScrollTimer;
|
|
103
|
+
dragEdgeIntent;
|
|
83
104
|
constructor(opts = {}) {
|
|
84
105
|
this.stdin = opts.stdin ?? process.stdin;
|
|
85
106
|
this.stdout = opts.stdout ?? process.stdout;
|
|
@@ -92,6 +113,16 @@ export class TerminalHost {
|
|
|
92
113
|
this.bufferOutput.setOnChange(() => this.handleOutputChange());
|
|
93
114
|
this.onDump = opts.onDump;
|
|
94
115
|
this.injectedSource = opts.keyEventSource;
|
|
116
|
+
this.clipboardCopy = opts.copyToClipboard ?? copyToClipboard;
|
|
117
|
+
this.now = opts.now ?? Date.now;
|
|
118
|
+
this.setIntervalFn = opts.setIntervalFn ?? setInterval;
|
|
119
|
+
this.clearIntervalFn = opts.clearIntervalFn ?? clearInterval;
|
|
120
|
+
this.setTimeoutFn = opts.setTimeoutFn ?? setTimeout;
|
|
121
|
+
this.clearTimeoutFn = opts.clearTimeoutFn ?? clearTimeout;
|
|
122
|
+
const selectionCollaborator = {
|
|
123
|
+
hasSelection: () => this.hasSelection(),
|
|
124
|
+
handleEvent: (ev) => this.handleSelectionInput(ev),
|
|
125
|
+
};
|
|
95
126
|
this.router = new InputRouter({
|
|
96
127
|
onDump: opts.onDump,
|
|
97
128
|
onInterrupt: () => this.fireInterrupt(),
|
|
@@ -102,8 +133,10 @@ export class TerminalHost {
|
|
|
102
133
|
onScrollToBottom: () => this.handleScrollToBottom(),
|
|
103
134
|
isChildAlternate: () => this.vt?.isAlternate === true,
|
|
104
135
|
isChildMouseActive: () => this.childMouseTracker.hasActiveReportingMode(),
|
|
136
|
+
childWantsMotion: () => this.childMouseTracker.wantsMotion(),
|
|
105
137
|
getChildKittyFlags: () => this.kittyNegotiator.activeFlags,
|
|
106
138
|
getChildBracketedPaste: () => this.vt?.bracketedPaste === true,
|
|
139
|
+
selection: selectionCollaborator,
|
|
107
140
|
onSyncOutputReport: (supported) => {
|
|
108
141
|
this.syncOutputSupported = supported;
|
|
109
142
|
},
|
|
@@ -172,6 +205,7 @@ export class TerminalHost {
|
|
|
172
205
|
openAgentSplit(pane) {
|
|
173
206
|
if (!this.hasAttachedChild())
|
|
174
207
|
return;
|
|
208
|
+
this.clearSelection({ schedule: false });
|
|
175
209
|
const state = this.agentSplit ?? { pane: undefined, open: false, collaborator: undefined };
|
|
176
210
|
state.pane = pane;
|
|
177
211
|
state.open = true;
|
|
@@ -185,6 +219,7 @@ export class TerminalHost {
|
|
|
185
219
|
closeAgentSplit() {
|
|
186
220
|
if (this.agentSplit?.open !== true)
|
|
187
221
|
return;
|
|
222
|
+
this.clearSelection({ schedule: false });
|
|
188
223
|
this.agentSplit.open = false;
|
|
189
224
|
this.clearMouseModeSource(AGENT_SPLIT_MOUSE_SOURCE);
|
|
190
225
|
this.applyChildViewportSize();
|
|
@@ -254,6 +289,7 @@ export class TerminalHost {
|
|
|
254
289
|
if (!this.started || this.stopped)
|
|
255
290
|
return;
|
|
256
291
|
this.stopped = true;
|
|
292
|
+
this.resetDragScrollState();
|
|
257
293
|
this.stopSource();
|
|
258
294
|
if (this.resizeListener) {
|
|
259
295
|
this.stdout.removeListener("resize", this.resizeListener);
|
|
@@ -367,6 +403,7 @@ export class TerminalHost {
|
|
|
367
403
|
return;
|
|
368
404
|
if (!this.childHandle)
|
|
369
405
|
return;
|
|
406
|
+
this.clearSelection({ schedule: false });
|
|
370
407
|
const collaborator = this.agentSplit?.collaborator;
|
|
371
408
|
if (this.agentSplit?.open === true) {
|
|
372
409
|
this.closeAgentSplit();
|
|
@@ -408,6 +445,7 @@ export class TerminalHost {
|
|
|
408
445
|
if (!this.layout) {
|
|
409
446
|
this.layout = this.computeLayout();
|
|
410
447
|
}
|
|
448
|
+
this.clearSelection({ schedule: false });
|
|
411
449
|
this.vt?.dispose();
|
|
412
450
|
const rect = this.childViewportRect();
|
|
413
451
|
this.vt = new VirtualTerminal(rect.cols, rect.rows, {
|
|
@@ -463,6 +501,7 @@ export class TerminalHost {
|
|
|
463
501
|
detachParentUi(mode) {
|
|
464
502
|
if (mode && this.parentUi !== mode)
|
|
465
503
|
return;
|
|
504
|
+
this.resetDragScrollState();
|
|
466
505
|
this.router.detachParentUi(this.parentUi);
|
|
467
506
|
this.parentUi = undefined;
|
|
468
507
|
this.invalidateFrameCache();
|
|
@@ -533,6 +572,7 @@ export class TerminalHost {
|
|
|
533
572
|
}
|
|
534
573
|
invalidateFrameCache() {
|
|
535
574
|
this.lastFrameRows = null;
|
|
575
|
+
this.lastBaseRows = null;
|
|
536
576
|
}
|
|
537
577
|
/** Force an immediate render (synchronously composes one frame). */
|
|
538
578
|
renderNow() {
|
|
@@ -556,24 +596,31 @@ export class TerminalHost {
|
|
|
556
596
|
const parentOnly = !fullRender
|
|
557
597
|
&& surface !== undefined
|
|
558
598
|
&& surface.lines.length >= this.layout.childRows;
|
|
559
|
-
const
|
|
599
|
+
const baseRows = new Array(this.layout.rows).fill(null);
|
|
560
600
|
if (!parentOnly) {
|
|
561
601
|
const childRows = renderChildViewportRows(this.vt, this.layout);
|
|
562
602
|
for (let i = 0; i < childRows.length; i++) {
|
|
563
|
-
|
|
603
|
+
baseRows[i] = childRows[i];
|
|
564
604
|
}
|
|
565
605
|
}
|
|
566
606
|
if (surface) {
|
|
567
|
-
this.renderUiSurfaceRows(
|
|
607
|
+
this.renderUiSurfaceRows(baseRows, this.layout, surface);
|
|
568
608
|
}
|
|
569
609
|
// Warning strip is host-owned and lives in its own dedicated rows
|
|
570
610
|
// (between the child viewport and the status bar), so it is rendered
|
|
571
611
|
// independently of the parent UI surface and the child viewport.
|
|
572
|
-
this.renderMessageRows(
|
|
612
|
+
this.renderMessageRows(baseRows, this.layout);
|
|
573
613
|
if (this.statusBar && this.layout.statusRow !== undefined) {
|
|
574
614
|
const text = this.statusBar.render(this.layout.columns);
|
|
575
|
-
|
|
615
|
+
baseRows[this.layout.statusRow - 1] = renderStatusRowBody(this.layout, text);
|
|
616
|
+
}
|
|
617
|
+
const prevBase = this.lastBaseRows;
|
|
618
|
+
for (let i = 0; i < baseRows.length; i++) {
|
|
619
|
+
if (baseRows[i] === null)
|
|
620
|
+
baseRows[i] = prevBase?.[i] ?? null;
|
|
576
621
|
}
|
|
622
|
+
const displayRows = [...baseRows];
|
|
623
|
+
this.applySelectionHighlight(baseRows, displayRows);
|
|
577
624
|
// Move the real cursor to the active owner of the frame. Host-owned
|
|
578
625
|
// surfaces keep the historic hidden default unless they opt in; child-owned
|
|
579
626
|
// frames mirror the child's own DECTCEM visibility.
|
|
@@ -604,25 +651,738 @@ export class TerminalHost {
|
|
|
604
651
|
}
|
|
605
652
|
const prev = this.lastFrameRows;
|
|
606
653
|
let diffBody = HIDE_CURSOR;
|
|
607
|
-
for (let i = 0; i <
|
|
608
|
-
if (
|
|
609
|
-
if (prev === null || prev[i] !==
|
|
610
|
-
diffBody += `\x1b[${i + 1};1H${
|
|
654
|
+
for (let i = 0; i < displayRows.length; i++) {
|
|
655
|
+
if (displayRows[i] !== null) {
|
|
656
|
+
if (prev === null || prev[i] !== displayRows[i]) {
|
|
657
|
+
diffBody += `\x1b[${i + 1};1H${displayRows[i]}`;
|
|
611
658
|
}
|
|
612
659
|
}
|
|
613
660
|
else {
|
|
614
|
-
|
|
615
|
-
// so `next` doubles as the cache for the following frame.
|
|
616
|
-
next[i] = prev?.[i] ?? null;
|
|
661
|
+
displayRows[i] = prev?.[i] ?? null;
|
|
617
662
|
}
|
|
618
663
|
}
|
|
619
|
-
this.lastFrameRows =
|
|
664
|
+
this.lastFrameRows = displayRows;
|
|
665
|
+
this.lastBaseRows = baseRows;
|
|
620
666
|
diffBody += cursorPlacement;
|
|
621
667
|
const frame = this.syncOutputSupported
|
|
622
668
|
? BEGIN_SYNC_UPDATE + diffBody + END_SYNC_UPDATE
|
|
623
669
|
: diffBody;
|
|
624
670
|
this.stdout.write(frame);
|
|
625
671
|
}
|
|
672
|
+
applySelectionHighlight(baseRows, displayRows) {
|
|
673
|
+
if (!this.selection.hasSelection())
|
|
674
|
+
return;
|
|
675
|
+
const state = this.selection.getState();
|
|
676
|
+
if (!state)
|
|
677
|
+
return;
|
|
678
|
+
if (state.kind === "surface") {
|
|
679
|
+
const snapshot = this.snapshotRegionFromRows(state.region, baseRows);
|
|
680
|
+
this.selection.refreshSurfaceSnapshot(snapshot);
|
|
681
|
+
for (const span of this.selection.perRowSpans()) {
|
|
682
|
+
const idx = span.row - 1;
|
|
683
|
+
const base = baseRows[idx];
|
|
684
|
+
if (base === null || base === undefined)
|
|
685
|
+
continue;
|
|
686
|
+
displayRows[idx] = applySelectionBackgroundRange(base, span.startCol, span.endCol);
|
|
687
|
+
}
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (state.kind === "agent-buffer") {
|
|
691
|
+
if (!this.vt || !this.selection.refreshAgentSnapshot((absRow) => composeAbsoluteRow(this.vt, absRow, state.composeCols))) {
|
|
692
|
+
this.selection.clear();
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
const viewportY = this.vt.viewportY;
|
|
696
|
+
for (const span of this.selection.perRowSpans()) {
|
|
697
|
+
const screenRow = span.row - viewportY + 1;
|
|
698
|
+
if (screenRow < state.region.rowStart || screenRow > state.region.rowEnd)
|
|
699
|
+
continue;
|
|
700
|
+
const base = baseRows[screenRow - 1];
|
|
701
|
+
if (base === null || base === undefined)
|
|
702
|
+
continue;
|
|
703
|
+
displayRows[screenRow - 1] = applySelectionBackgroundRange(base, span.startCol, span.endCol);
|
|
704
|
+
}
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
if (state.kind === "fork-buffer") {
|
|
708
|
+
const placement = this.parentUi && isHostParentUiMode(this.parentUi)
|
|
709
|
+
? this.parentUi.forkSelectionPlacement?.()
|
|
710
|
+
: undefined;
|
|
711
|
+
if (!placement || !this.selection.refreshForkSnapshot((absRow) => composeAbsoluteRow(placement.vt, absRow, state.composeCols))) {
|
|
712
|
+
this.selection.clear();
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
for (const span of this.selection.perRowSpans()) {
|
|
716
|
+
const screenRow = span.row - placement.vt.viewportY + placement.region.rowStart;
|
|
717
|
+
if (screenRow < placement.region.rowStart || screenRow > placement.region.rowEnd)
|
|
718
|
+
continue;
|
|
719
|
+
const base = baseRows[screenRow - 1];
|
|
720
|
+
if (base === null || base === undefined)
|
|
721
|
+
continue;
|
|
722
|
+
const startCol = Math.max(placement.region.colStart, span.startCol + placement.region.colStart - 1);
|
|
723
|
+
const endCol = Math.min(placement.region.colEnd, span.endCol + placement.region.colStart - 1);
|
|
724
|
+
if (startCol <= endCol) {
|
|
725
|
+
displayRows[screenRow - 1] = applySelectionBackgroundRange(base, startCol, endCol);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const placement = this.docSurfacePlacement(state.pane);
|
|
731
|
+
if (!placement) {
|
|
732
|
+
this.selection.clear();
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
for (const span of this.selection.perRowSpans()) {
|
|
736
|
+
const paneRow = span.row - placement.pane.getScrollTop() + 1;
|
|
737
|
+
const screenRow = placement.region.rowStart + paneRow - 1;
|
|
738
|
+
if (screenRow < placement.region.rowStart || screenRow > placement.region.rowEnd)
|
|
739
|
+
continue;
|
|
740
|
+
const base = baseRows[screenRow - 1];
|
|
741
|
+
if (base === null || base === undefined)
|
|
742
|
+
continue;
|
|
743
|
+
const startCol = Math.max(placement.region.colStart, span.startCol + placement.region.colStart - 1);
|
|
744
|
+
const endCol = Math.min(placement.region.colEnd, span.endCol + placement.region.colStart - 1);
|
|
745
|
+
if (startCol <= endCol) {
|
|
746
|
+
displayRows[screenRow - 1] = applySelectionBackgroundRange(base, startCol, endCol);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
hasSelection() {
|
|
751
|
+
return this.selection.hasSelection();
|
|
752
|
+
}
|
|
753
|
+
handleSelectionInput(ev) {
|
|
754
|
+
if (ev.kind === "wheel") {
|
|
755
|
+
return "pass";
|
|
756
|
+
}
|
|
757
|
+
if (ev.kind === "control" && ev.byte === 0x03) {
|
|
758
|
+
if (!this.hasSelection())
|
|
759
|
+
return "pass";
|
|
760
|
+
void this.copySelection();
|
|
761
|
+
return "consume";
|
|
762
|
+
}
|
|
763
|
+
if (ev.kind === "escape") {
|
|
764
|
+
if (!this.hasSelection())
|
|
765
|
+
return "pass";
|
|
766
|
+
this.clearSelection();
|
|
767
|
+
return "consume";
|
|
768
|
+
}
|
|
769
|
+
const mouse = parseSgrMouseFrame(ev);
|
|
770
|
+
if (!mouse)
|
|
771
|
+
return "pass";
|
|
772
|
+
const point = { row: mouse.row, col: mouse.column };
|
|
773
|
+
const isMotion = mouse.final === "M" && (mouse.button & 32) !== 0;
|
|
774
|
+
const isLeftPress = mouse.final === "M" && mouse.button === 0;
|
|
775
|
+
const isLeftRelease = mouse.final === "m" && (mouse.button === 0 || mouse.button === 3);
|
|
776
|
+
const isRightPress = mouse.final === "M" && mouse.button === 2;
|
|
777
|
+
if (isLeftPress) {
|
|
778
|
+
this.clearSelection({ schedule: true });
|
|
779
|
+
const hit = this.hitTestSelectable(mouse.column, mouse.row);
|
|
780
|
+
if (!hit) {
|
|
781
|
+
this.gestureOwner = "child";
|
|
782
|
+
return "pass";
|
|
783
|
+
}
|
|
784
|
+
this.gestureOwner = "host";
|
|
785
|
+
const clickCount = this.nextClickCount(point);
|
|
786
|
+
if (hit.scrollTarget === "main-vt" || hit.scrollTarget === "agent-vt") {
|
|
787
|
+
if (!this.beginAgentBufferSelection(hit.region, point, clickCount)) {
|
|
788
|
+
this.gestureOwner = "child";
|
|
789
|
+
return "pass";
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
else if (hit.scrollTarget === "offscreen-inquiry") {
|
|
793
|
+
if (!this.beginForkBufferSelection(hit.region, point, clickCount)) {
|
|
794
|
+
this.gestureOwner = "child";
|
|
795
|
+
return "pass";
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
else if (hit.scrollTarget === "agent-artifact"
|
|
799
|
+
|| hit.scrollTarget === "offscreen-artifact"
|
|
800
|
+
|| hit.scrollTarget === "parent-doc-surface") {
|
|
801
|
+
if (!this.beginDocSurfaceSelection(hit.scrollTarget, hit.region, point, clickCount)) {
|
|
802
|
+
this.gestureOwner = "child";
|
|
803
|
+
return "pass";
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
else if (clickCount >= 3) {
|
|
807
|
+
const snapshot = this.snapshotRegion(hit.region);
|
|
808
|
+
this.selection.selectLine(hit.region, point, snapshot);
|
|
809
|
+
}
|
|
810
|
+
else if (clickCount === 2) {
|
|
811
|
+
const snapshot = this.snapshotRegion(hit.region);
|
|
812
|
+
this.selection.selectWord(hit.region, point, snapshot);
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
const snapshot = this.snapshotRegion(hit.region);
|
|
816
|
+
this.selection.beginDrag(hit.region, point, snapshot);
|
|
817
|
+
}
|
|
818
|
+
this.dragScroll = {
|
|
819
|
+
region: hit.region,
|
|
820
|
+
target: hit.scrollTarget,
|
|
821
|
+
col: mouse.column,
|
|
822
|
+
row: mouse.row,
|
|
823
|
+
};
|
|
824
|
+
this.scheduleParentUiRender();
|
|
825
|
+
return "consume";
|
|
826
|
+
}
|
|
827
|
+
if (isMotion) {
|
|
828
|
+
if (this.gestureOwner === "host") {
|
|
829
|
+
const localPoint = this.localSelectionPoint(point);
|
|
830
|
+
this.selection.extendTo(localPoint, this.bufferSelectionComposeRow());
|
|
831
|
+
if (this.dragScroll) {
|
|
832
|
+
this.dragScroll.col = mouse.column;
|
|
833
|
+
this.dragScroll.row = mouse.row;
|
|
834
|
+
if (this.dragScroll.target !== "none"
|
|
835
|
+
&& this.dragScrollPastEdge(this.dragScroll)) {
|
|
836
|
+
this.cancelDragEdgeIntent();
|
|
837
|
+
this.startDragScroll();
|
|
838
|
+
}
|
|
839
|
+
else if (this.dragScroll.target !== "none"
|
|
840
|
+
&& this.updateDragEdgeIntent()) {
|
|
841
|
+
if (this.dragScroll.edgeIntent)
|
|
842
|
+
this.startDragScroll();
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
this.cancelDragEdgeIntent();
|
|
846
|
+
this.stopDragScroll();
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
this.scheduleParentUiRender();
|
|
850
|
+
return "consume";
|
|
851
|
+
}
|
|
852
|
+
return "pass";
|
|
853
|
+
}
|
|
854
|
+
if (isLeftRelease) {
|
|
855
|
+
if (this.gestureOwner === "host") {
|
|
856
|
+
this.resetDragScrollState();
|
|
857
|
+
if (this.selection.mode === "char" && this.selection.isEmpty()) {
|
|
858
|
+
this.clearSelection({ schedule: true });
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
this.scheduleParentUiRender();
|
|
862
|
+
}
|
|
863
|
+
this.gestureOwner = "none";
|
|
864
|
+
return "consume";
|
|
865
|
+
}
|
|
866
|
+
if (this.gestureOwner === "child")
|
|
867
|
+
this.gestureOwner = "none";
|
|
868
|
+
return "pass";
|
|
869
|
+
}
|
|
870
|
+
if (isRightPress) {
|
|
871
|
+
if (this.hasSelection()) {
|
|
872
|
+
void this.copySelection();
|
|
873
|
+
return "consume";
|
|
874
|
+
}
|
|
875
|
+
return this.hitTestSelectable(mouse.column, mouse.row) ? "consume" : "pass";
|
|
876
|
+
}
|
|
877
|
+
return "pass";
|
|
878
|
+
}
|
|
879
|
+
nextClickCount(point) {
|
|
880
|
+
const now = this.now();
|
|
881
|
+
const sameCell = this.lastClickCell?.row === point.row
|
|
882
|
+
&& this.lastClickCell?.col === point.col;
|
|
883
|
+
this.clickCount = sameCell && now - this.lastClickTime <= MULTI_CLICK_MS
|
|
884
|
+
? this.clickCount + 1
|
|
885
|
+
: 1;
|
|
886
|
+
this.lastClickTime = now;
|
|
887
|
+
this.lastClickCell = point;
|
|
888
|
+
return this.clickCount;
|
|
889
|
+
}
|
|
890
|
+
beginAgentBufferSelection(region, point, clickCount) {
|
|
891
|
+
if (!this.vt)
|
|
892
|
+
return false;
|
|
893
|
+
const normalized = normalizeSelectionRegion(region);
|
|
894
|
+
const allPaneMarkers = [];
|
|
895
|
+
for (let row = normalized.rowStart; row <= normalized.rowEnd; row++) {
|
|
896
|
+
const marker = this.vt.registerMarker(this.vt.viewportY + (row - 1));
|
|
897
|
+
if (marker)
|
|
898
|
+
allPaneMarkers.push(marker);
|
|
899
|
+
}
|
|
900
|
+
if (allPaneMarkers.length !== normalized.rowEnd - normalized.rowStart + 1) {
|
|
901
|
+
for (const marker of allPaneMarkers)
|
|
902
|
+
marker.dispose();
|
|
903
|
+
return false;
|
|
904
|
+
}
|
|
905
|
+
const composeCols = this.vt.cols;
|
|
906
|
+
const composeRow = (absRow) => composeAbsoluteRow(this.vt, absRow, composeCols);
|
|
907
|
+
const anchorPaneIdx = Math.min(Math.max(point.row - normalized.rowStart, 0), allPaneMarkers.length - 1);
|
|
908
|
+
if (clickCount === 2) {
|
|
909
|
+
this.selection.selectMarkerBufferWord("agent-buffer", normalized, composeCols, allPaneMarkers, anchorPaneIdx, point.col, composeRow);
|
|
910
|
+
}
|
|
911
|
+
else if (clickCount >= 3) {
|
|
912
|
+
this.selection.selectMarkerBufferLine("agent-buffer", normalized, composeCols, allPaneMarkers, anchorPaneIdx, composeRow);
|
|
913
|
+
}
|
|
914
|
+
else {
|
|
915
|
+
this.selection.beginMarkerBufferDrag("agent-buffer", normalized, composeCols, allPaneMarkers, anchorPaneIdx, point.col, composeRow);
|
|
916
|
+
}
|
|
917
|
+
return true;
|
|
918
|
+
}
|
|
919
|
+
beginForkBufferSelection(region, point, clickCount) {
|
|
920
|
+
const placement = this.parentUi && isHostParentUiMode(this.parentUi)
|
|
921
|
+
? this.parentUi.forkSelectionPlacement?.()
|
|
922
|
+
: undefined;
|
|
923
|
+
if (!placement || placement.vt.isAlternate === true)
|
|
924
|
+
return false;
|
|
925
|
+
const normalized = normalizeSelectionRegion(placement.region);
|
|
926
|
+
const allPaneMarkers = [];
|
|
927
|
+
for (let row = normalized.rowStart; row <= normalized.rowEnd; row++) {
|
|
928
|
+
const marker = placement.vt.registerMarker(placement.vt.viewportY + (row - normalized.rowStart));
|
|
929
|
+
if (marker)
|
|
930
|
+
allPaneMarkers.push(marker);
|
|
931
|
+
}
|
|
932
|
+
if (allPaneMarkers.length !== normalized.rowEnd - normalized.rowStart + 1) {
|
|
933
|
+
for (const marker of allPaneMarkers)
|
|
934
|
+
marker.dispose();
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
const composeCols = placement.vt.cols;
|
|
938
|
+
const composeRow = (absRow) => composeAbsoluteRow(placement.vt, absRow, composeCols);
|
|
939
|
+
const anchorPaneIdx = Math.min(Math.max(point.row - normalized.rowStart, 0), allPaneMarkers.length - 1);
|
|
940
|
+
const localCol = this.localColumnForRegion(region, point.col);
|
|
941
|
+
if (clickCount === 2) {
|
|
942
|
+
this.selection.selectMarkerBufferWord("fork-buffer", normalized, composeCols, allPaneMarkers, anchorPaneIdx, localCol, composeRow);
|
|
943
|
+
}
|
|
944
|
+
else if (clickCount >= 3) {
|
|
945
|
+
this.selection.selectMarkerBufferLine("fork-buffer", normalized, composeCols, allPaneMarkers, anchorPaneIdx, composeRow);
|
|
946
|
+
}
|
|
947
|
+
else {
|
|
948
|
+
this.selection.beginMarkerBufferDrag("fork-buffer", normalized, composeCols, allPaneMarkers, anchorPaneIdx, localCol, composeRow);
|
|
949
|
+
}
|
|
950
|
+
return true;
|
|
951
|
+
}
|
|
952
|
+
beginDocSurfaceSelection(target, region, point, clickCount) {
|
|
953
|
+
const placement = this.docSurfacePlacementForTarget(target, point);
|
|
954
|
+
if (!placement)
|
|
955
|
+
return false;
|
|
956
|
+
const normalized = normalizeSelectionRegion(placement.region);
|
|
957
|
+
const localRow = Math.min(Math.max(point.row - normalized.rowStart + 1, 1), Math.max(1, normalized.rowEnd - normalized.rowStart + 1));
|
|
958
|
+
const docLine = placement.pane.getScrollTop() + localRow - 1;
|
|
959
|
+
const localCol = this.localColumnForRegion(region, point.col);
|
|
960
|
+
if (clickCount === 2) {
|
|
961
|
+
this.selection.selectDocWord(normalized, placement.pane, docLine, localCol);
|
|
962
|
+
}
|
|
963
|
+
else if (clickCount >= 3) {
|
|
964
|
+
this.selection.selectDocLine(normalized, placement.pane, docLine);
|
|
965
|
+
}
|
|
966
|
+
else {
|
|
967
|
+
this.selection.beginDocDrag(normalized, placement.pane, docLine, localCol);
|
|
968
|
+
}
|
|
969
|
+
return true;
|
|
970
|
+
}
|
|
971
|
+
bufferSelectionComposeRow() {
|
|
972
|
+
const state = this.selection.getState();
|
|
973
|
+
if (!state)
|
|
974
|
+
return undefined;
|
|
975
|
+
if (state.kind === "agent-buffer") {
|
|
976
|
+
if (!this.vt)
|
|
977
|
+
return undefined;
|
|
978
|
+
return (absRow) => composeAbsoluteRow(this.vt, absRow, state.composeCols);
|
|
979
|
+
}
|
|
980
|
+
if (state.kind === "fork-buffer") {
|
|
981
|
+
const placement = this.parentUi && isHostParentUiMode(this.parentUi)
|
|
982
|
+
? this.parentUi.forkSelectionPlacement?.()
|
|
983
|
+
: undefined;
|
|
984
|
+
if (!placement)
|
|
985
|
+
return undefined;
|
|
986
|
+
return (absRow) => composeAbsoluteRow(placement.vt, absRow, state.composeCols);
|
|
987
|
+
}
|
|
988
|
+
return undefined;
|
|
989
|
+
}
|
|
990
|
+
clearSelection(opts = {}) {
|
|
991
|
+
this.resetDragScrollState();
|
|
992
|
+
if (!this.selection.clear())
|
|
993
|
+
return;
|
|
994
|
+
this.gestureOwner = "none";
|
|
995
|
+
if (opts.schedule !== false)
|
|
996
|
+
this.scheduleParentUiRender();
|
|
997
|
+
}
|
|
998
|
+
async copySelection() {
|
|
999
|
+
this.refreshSelectionBeforeCopy();
|
|
1000
|
+
const text = this.selection.extractText();
|
|
1001
|
+
if (text.length === 0)
|
|
1002
|
+
return;
|
|
1003
|
+
const result = await this.clipboardCopy(text, { stdout: this.stdout });
|
|
1004
|
+
if (result.osWritten) {
|
|
1005
|
+
this.statusBar?.flash(`Copied ${text.split("\n").length} line(s)`, 1500, "copy-success");
|
|
1006
|
+
this.clearSelection({ schedule: false });
|
|
1007
|
+
}
|
|
1008
|
+
else {
|
|
1009
|
+
this.statusBar?.flash("Copy failed (clipboard unavailable)", 1500, "copy-failure");
|
|
1010
|
+
}
|
|
1011
|
+
this.scheduleRender();
|
|
1012
|
+
}
|
|
1013
|
+
refreshSelectionBeforeCopy() {
|
|
1014
|
+
const state = this.selection.getState();
|
|
1015
|
+
if (!state)
|
|
1016
|
+
return;
|
|
1017
|
+
if (state.kind === "surface") {
|
|
1018
|
+
this.selection.refreshSurfaceSnapshot(this.snapshotRegion(state.region));
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
if (state.kind === "agent-buffer") {
|
|
1022
|
+
if (!this.vt || !this.selection.refreshAgentSnapshot((absRow) => composeAbsoluteRow(this.vt, absRow, state.composeCols))) {
|
|
1023
|
+
this.selection.clear();
|
|
1024
|
+
}
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
if (state.kind === "fork-buffer") {
|
|
1028
|
+
const placement = this.parentUi && isHostParentUiMode(this.parentUi)
|
|
1029
|
+
? this.parentUi.forkSelectionPlacement?.()
|
|
1030
|
+
: undefined;
|
|
1031
|
+
if (!placement || !this.selection.refreshForkSnapshot((absRow) => composeAbsoluteRow(placement.vt, absRow, state.composeCols))) {
|
|
1032
|
+
this.selection.clear();
|
|
1033
|
+
}
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
if (state.kind === "doc-surface" && !this.docSurfacePlacement(state.pane)) {
|
|
1037
|
+
this.selection.clear();
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
localSelectionPoint(point) {
|
|
1041
|
+
const state = this.selection.getState();
|
|
1042
|
+
if (state?.kind === "fork-buffer" || state?.kind === "doc-surface") {
|
|
1043
|
+
return { row: point.row, col: this.localColumnForRegion(state.region, point.col) };
|
|
1044
|
+
}
|
|
1045
|
+
return point;
|
|
1046
|
+
}
|
|
1047
|
+
localColumnForRegion(region, column) {
|
|
1048
|
+
const clamped = Math.min(Math.max(column, region.colStart), region.colEnd);
|
|
1049
|
+
return clamped - region.colStart + 1;
|
|
1050
|
+
}
|
|
1051
|
+
dragScrollPastEdge(drag) {
|
|
1052
|
+
return drag.row < drag.region.rowStart || drag.row > drag.region.rowEnd;
|
|
1053
|
+
}
|
|
1054
|
+
dragScrollDirection(drag) {
|
|
1055
|
+
if (drag.row < drag.region.rowStart)
|
|
1056
|
+
return "back";
|
|
1057
|
+
if (drag.row > drag.region.rowEnd)
|
|
1058
|
+
return "forward";
|
|
1059
|
+
return drag.edgeIntent;
|
|
1060
|
+
}
|
|
1061
|
+
startDragScroll() {
|
|
1062
|
+
if (this.dragScrollTimer)
|
|
1063
|
+
return;
|
|
1064
|
+
if (!this.dragScrollTick())
|
|
1065
|
+
return;
|
|
1066
|
+
if (this.dragScrollTimer)
|
|
1067
|
+
return;
|
|
1068
|
+
const timer = this.setIntervalFn(() => {
|
|
1069
|
+
this.dragScrollTick();
|
|
1070
|
+
}, DRAG_SCROLL_INTERVAL_MS);
|
|
1071
|
+
this.dragScrollTimer = timer;
|
|
1072
|
+
const maybeTimer = timer;
|
|
1073
|
+
maybeTimer.unref?.();
|
|
1074
|
+
}
|
|
1075
|
+
stopDragScroll() {
|
|
1076
|
+
const timer = this.dragScrollTimer;
|
|
1077
|
+
if (!timer)
|
|
1078
|
+
return;
|
|
1079
|
+
this.clearIntervalFn(timer);
|
|
1080
|
+
this.dragScrollTimer = undefined;
|
|
1081
|
+
}
|
|
1082
|
+
resetDragScrollState() {
|
|
1083
|
+
this.stopDragScroll();
|
|
1084
|
+
this.cancelDragEdgeIntent();
|
|
1085
|
+
this.dragScroll = undefined;
|
|
1086
|
+
}
|
|
1087
|
+
cancelDragEdgeIntent() {
|
|
1088
|
+
if (this.dragEdgeIntent) {
|
|
1089
|
+
this.clearTimeoutFn(this.dragEdgeIntent.timer);
|
|
1090
|
+
this.dragEdgeIntent = undefined;
|
|
1091
|
+
}
|
|
1092
|
+
if (this.dragScroll)
|
|
1093
|
+
this.dragScroll.edgeIntent = undefined;
|
|
1094
|
+
}
|
|
1095
|
+
updateDragEdgeIntent() {
|
|
1096
|
+
const drag = this.dragScroll;
|
|
1097
|
+
const direction = drag ? this.terminalEdgeDragDirection(drag) : undefined;
|
|
1098
|
+
if (!drag || !direction) {
|
|
1099
|
+
this.cancelDragEdgeIntent();
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
if (drag.edgeIntent === direction)
|
|
1103
|
+
return true;
|
|
1104
|
+
if (this.dragEdgeIntent?.direction === direction)
|
|
1105
|
+
return true;
|
|
1106
|
+
this.cancelDragEdgeIntent();
|
|
1107
|
+
const timer = this.setTimeoutFn(() => {
|
|
1108
|
+
if (!this.dragScroll || this.gestureOwner !== "host")
|
|
1109
|
+
return;
|
|
1110
|
+
if (this.terminalEdgeDragDirection(this.dragScroll) !== direction)
|
|
1111
|
+
return;
|
|
1112
|
+
this.dragEdgeIntent = undefined;
|
|
1113
|
+
this.dragScroll.edgeIntent = direction;
|
|
1114
|
+
this.startDragScroll();
|
|
1115
|
+
}, DRAG_EDGE_LOSS_MS);
|
|
1116
|
+
const maybeTimer = timer;
|
|
1117
|
+
maybeTimer.unref?.();
|
|
1118
|
+
this.dragEdgeIntent = { direction, timer };
|
|
1119
|
+
return true;
|
|
1120
|
+
}
|
|
1121
|
+
terminalEdgeDragDirection(drag) {
|
|
1122
|
+
if (drag.target === "none")
|
|
1123
|
+
return undefined;
|
|
1124
|
+
const layout = this.layout;
|
|
1125
|
+
if (!layout)
|
|
1126
|
+
return undefined;
|
|
1127
|
+
if (drag.row === drag.region.rowStart && drag.region.rowStart === 1)
|
|
1128
|
+
return "back";
|
|
1129
|
+
if (drag.row === drag.region.rowEnd && drag.region.rowEnd === layout.rows)
|
|
1130
|
+
return "forward";
|
|
1131
|
+
return undefined;
|
|
1132
|
+
}
|
|
1133
|
+
dragScrollTick() {
|
|
1134
|
+
const drag = this.dragScroll;
|
|
1135
|
+
if (!drag || drag.target === "none") {
|
|
1136
|
+
this.stopDragScroll();
|
|
1137
|
+
return false;
|
|
1138
|
+
}
|
|
1139
|
+
const direction = this.dragScrollDirection(drag);
|
|
1140
|
+
if (!direction) {
|
|
1141
|
+
this.stopDragScroll();
|
|
1142
|
+
return false;
|
|
1143
|
+
}
|
|
1144
|
+
const moved = this.scrollSurface(direction);
|
|
1145
|
+
if (moved === 0) {
|
|
1146
|
+
this.stopDragScroll();
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1149
|
+
if (!this.growSelectionAfterDragScroll(direction)) {
|
|
1150
|
+
this.stopDragScroll();
|
|
1151
|
+
return false;
|
|
1152
|
+
}
|
|
1153
|
+
this.scheduleRender();
|
|
1154
|
+
return true;
|
|
1155
|
+
}
|
|
1156
|
+
scrollSurface(direction) {
|
|
1157
|
+
const drag = this.dragScroll;
|
|
1158
|
+
if (!drag)
|
|
1159
|
+
return 0;
|
|
1160
|
+
switch (drag.target) {
|
|
1161
|
+
case "main-vt":
|
|
1162
|
+
case "agent-vt": {
|
|
1163
|
+
if (!this.vt)
|
|
1164
|
+
return 0;
|
|
1165
|
+
const before = this.vt.viewportY;
|
|
1166
|
+
if (direction === "back")
|
|
1167
|
+
this.vt.scrollBack(DRAG_SCROLL_LINES);
|
|
1168
|
+
else
|
|
1169
|
+
this.vt.scrollForward(DRAG_SCROLL_LINES);
|
|
1170
|
+
return Math.abs(this.vt.viewportY - before);
|
|
1171
|
+
}
|
|
1172
|
+
case "agent-artifact": {
|
|
1173
|
+
const placement = this.agentSplitArtifactPlacement();
|
|
1174
|
+
return placement?.pane.scrollLines(direction, DRAG_SCROLL_LINES) ?? 0;
|
|
1175
|
+
}
|
|
1176
|
+
case "offscreen-inquiry":
|
|
1177
|
+
case "offscreen-artifact": {
|
|
1178
|
+
if (!this.parentUi || !isHostParentUiMode(this.parentUi))
|
|
1179
|
+
return 0;
|
|
1180
|
+
const column = Math.min(Math.max(drag.col, drag.region.colStart), drag.region.colEnd);
|
|
1181
|
+
return this.parentUi.scrollSelectableSurfaceAt?.(column, drag.row, direction) ?? 0;
|
|
1182
|
+
}
|
|
1183
|
+
case "parent-doc-surface": {
|
|
1184
|
+
const state = this.selection.getState();
|
|
1185
|
+
if (state?.kind !== "doc-surface")
|
|
1186
|
+
return 0;
|
|
1187
|
+
return scrollDocSurfacePane(state.pane, direction, DRAG_SCROLL_LINES);
|
|
1188
|
+
}
|
|
1189
|
+
case "none":
|
|
1190
|
+
return 0;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
growSelectionAfterDragScroll(direction) {
|
|
1194
|
+
const drag = this.dragScroll;
|
|
1195
|
+
if (!drag)
|
|
1196
|
+
return false;
|
|
1197
|
+
const edgeScreenRow = direction === "back" ? drag.region.rowStart : drag.region.rowEnd;
|
|
1198
|
+
const focusColLocal = this.localColumnForRegion(drag.region, drag.col);
|
|
1199
|
+
if (drag.target === "main-vt" || drag.target === "agent-vt") {
|
|
1200
|
+
if (!this.vt)
|
|
1201
|
+
return false;
|
|
1202
|
+
const state = this.selection.getState();
|
|
1203
|
+
if (state?.kind !== "agent-buffer")
|
|
1204
|
+
return false;
|
|
1205
|
+
const absRow = this.vt.viewportY + (edgeScreenRow - drag.region.rowStart);
|
|
1206
|
+
const marker = this.vt.registerMarker(absRow);
|
|
1207
|
+
if (!marker)
|
|
1208
|
+
return false;
|
|
1209
|
+
this.selection.growAgentBufferByMarker(marker, direction, focusColLocal, (row) => composeAbsoluteRow(this.vt, row, state.composeCols));
|
|
1210
|
+
return true;
|
|
1211
|
+
}
|
|
1212
|
+
if (drag.target === "offscreen-inquiry") {
|
|
1213
|
+
const placement = this.parentUi && isHostParentUiMode(this.parentUi)
|
|
1214
|
+
? this.parentUi.forkSelectionPlacement?.()
|
|
1215
|
+
: undefined;
|
|
1216
|
+
const state = this.selection.getState();
|
|
1217
|
+
if (!placement || state?.kind !== "fork-buffer")
|
|
1218
|
+
return false;
|
|
1219
|
+
const absRow = placement.vt.viewportY + (edgeScreenRow - placement.region.rowStart);
|
|
1220
|
+
const marker = placement.vt.registerMarker(absRow);
|
|
1221
|
+
if (!marker)
|
|
1222
|
+
return false;
|
|
1223
|
+
this.selection.growForkByMarker(marker, direction, focusColLocal, (row) => composeAbsoluteRow(placement.vt, row, state.composeCols));
|
|
1224
|
+
return true;
|
|
1225
|
+
}
|
|
1226
|
+
if (drag.target === "agent-artifact"
|
|
1227
|
+
|| drag.target === "offscreen-artifact"
|
|
1228
|
+
|| drag.target === "parent-doc-surface") {
|
|
1229
|
+
const state = this.selection.getState();
|
|
1230
|
+
if (state?.kind !== "doc-surface")
|
|
1231
|
+
return false;
|
|
1232
|
+
const placement = this.docSurfacePlacement(state.pane);
|
|
1233
|
+
if (!placement)
|
|
1234
|
+
return false;
|
|
1235
|
+
const edgeDocLine = placement.pane.getScrollTop()
|
|
1236
|
+
+ (direction === "back" ? 0 : placement.pane.getViewportRows() - 1);
|
|
1237
|
+
this.selection.growDocSurface(direction, focusColLocal, edgeDocLine);
|
|
1238
|
+
return true;
|
|
1239
|
+
}
|
|
1240
|
+
return false;
|
|
1241
|
+
}
|
|
1242
|
+
hitTestSelectable(column, row) {
|
|
1243
|
+
const layout = this.layout;
|
|
1244
|
+
if (!layout)
|
|
1245
|
+
return null;
|
|
1246
|
+
if (column < 1 || column > layout.columns || row < 1 || row > layout.rows)
|
|
1247
|
+
return null;
|
|
1248
|
+
if (layout.statusRow !== undefined && row === layout.statusRow)
|
|
1249
|
+
return null;
|
|
1250
|
+
if (this.vt?.isAlternate !== true
|
|
1251
|
+
&&
|
|
1252
|
+
layout.warningRows > 0
|
|
1253
|
+
&& layout.warningStartRow !== undefined
|
|
1254
|
+
&& this.bufferOutput.snapshot().length > 0) {
|
|
1255
|
+
const rowStart = layout.warningStartRow;
|
|
1256
|
+
const rowEnd = layout.warningStartRow + layout.warningRows - 1;
|
|
1257
|
+
if (row >= rowStart && row <= rowEnd) {
|
|
1258
|
+
return {
|
|
1259
|
+
source: "surface",
|
|
1260
|
+
scrollTarget: "none",
|
|
1261
|
+
region: { rowStart, rowEnd, colStart: 1, colEnd: layout.columns },
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
if (this.isAgentSplitOpen()) {
|
|
1266
|
+
return this.hitTestAgentSplitPane(column, row);
|
|
1267
|
+
}
|
|
1268
|
+
if (this.parentUi && isHostParentUiMode(this.parentUi)) {
|
|
1269
|
+
const perPane = this.parentUi.selectionRegionAt?.(column, row);
|
|
1270
|
+
if (perPane !== undefined) {
|
|
1271
|
+
if (!perPane)
|
|
1272
|
+
return null;
|
|
1273
|
+
return {
|
|
1274
|
+
source: "surface",
|
|
1275
|
+
scrollTarget: this.parentUi.selectionTargetAt?.(column, row) ?? "none",
|
|
1276
|
+
region: perPane,
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
if (this.vt?.isAlternate === true)
|
|
1281
|
+
return null;
|
|
1282
|
+
if (this.parentUi && isHostParentUiMode(this.parentUi)) {
|
|
1283
|
+
const surface = this.parentUi.getSurface();
|
|
1284
|
+
const maxLines = Math.min(surface.lines.length, layout.childRows);
|
|
1285
|
+
if (maxLines > 0) {
|
|
1286
|
+
const rowStart = layout.childRows - maxLines + 1;
|
|
1287
|
+
const rowEnd = layout.childRows;
|
|
1288
|
+
if (row >= rowStart && row <= rowEnd) {
|
|
1289
|
+
return {
|
|
1290
|
+
source: "surface",
|
|
1291
|
+
scrollTarget: "none",
|
|
1292
|
+
region: { rowStart, rowEnd, colStart: 1, colEnd: layout.columns },
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
return null;
|
|
1297
|
+
}
|
|
1298
|
+
if (this.hasAttachedChild() && layout.childRows > 0) {
|
|
1299
|
+
return {
|
|
1300
|
+
source: "agent-buffer",
|
|
1301
|
+
scrollTarget: "main-vt",
|
|
1302
|
+
region: {
|
|
1303
|
+
rowStart: 1,
|
|
1304
|
+
rowEnd: layout.childRows,
|
|
1305
|
+
colStart: 1,
|
|
1306
|
+
colEnd: layout.columns,
|
|
1307
|
+
},
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
return null;
|
|
1311
|
+
}
|
|
1312
|
+
hitTestAgentSplitPane(column, row) {
|
|
1313
|
+
const layout = this.layout;
|
|
1314
|
+
if (!layout)
|
|
1315
|
+
return null;
|
|
1316
|
+
const pane = this.routeAgentSplitMouse(column, row);
|
|
1317
|
+
if (!pane)
|
|
1318
|
+
return null;
|
|
1319
|
+
const metrics = computeSplitMetrics({
|
|
1320
|
+
cols: layout.columns,
|
|
1321
|
+
rows: layout.childRows,
|
|
1322
|
+
collapseAt: SPLIT_COLLAPSE_AT_COLS,
|
|
1323
|
+
});
|
|
1324
|
+
if (!metrics.collapsed) {
|
|
1325
|
+
if (pane === "agent") {
|
|
1326
|
+
if (this.vt?.isAlternate === true)
|
|
1327
|
+
return null;
|
|
1328
|
+
return {
|
|
1329
|
+
source: "agent-buffer",
|
|
1330
|
+
scrollTarget: "agent-vt",
|
|
1331
|
+
region: {
|
|
1332
|
+
rowStart: 1,
|
|
1333
|
+
rowEnd: layout.childRows,
|
|
1334
|
+
colStart: 1,
|
|
1335
|
+
colEnd: metrics.leftWidth,
|
|
1336
|
+
},
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
return {
|
|
1340
|
+
source: "surface",
|
|
1341
|
+
scrollTarget: "agent-artifact",
|
|
1342
|
+
region: {
|
|
1343
|
+
rowStart: 1,
|
|
1344
|
+
rowEnd: layout.childRows,
|
|
1345
|
+
colStart: metrics.leftWidth + 2,
|
|
1346
|
+
colEnd: layout.columns,
|
|
1347
|
+
},
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
const topRows = this.collapsedTopRows(layout.childRows);
|
|
1351
|
+
if (pane === "agent") {
|
|
1352
|
+
if (this.vt?.isAlternate === true)
|
|
1353
|
+
return null;
|
|
1354
|
+
return {
|
|
1355
|
+
source: "agent-buffer",
|
|
1356
|
+
scrollTarget: "agent-vt",
|
|
1357
|
+
region: {
|
|
1358
|
+
rowStart: 1,
|
|
1359
|
+
rowEnd: topRows,
|
|
1360
|
+
colStart: 1,
|
|
1361
|
+
colEnd: layout.columns,
|
|
1362
|
+
},
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
return {
|
|
1366
|
+
source: "surface",
|
|
1367
|
+
scrollTarget: "agent-artifact",
|
|
1368
|
+
region: {
|
|
1369
|
+
rowStart: topRows + 1,
|
|
1370
|
+
rowEnd: layout.childRows,
|
|
1371
|
+
colStart: 1,
|
|
1372
|
+
colEnd: layout.columns,
|
|
1373
|
+
},
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
snapshotRegion(region) {
|
|
1377
|
+
return this.snapshotRegionFromRows(region, this.lastBaseRows);
|
|
1378
|
+
}
|
|
1379
|
+
snapshotRegionFromRows(region, rows) {
|
|
1380
|
+
const snapshot = [];
|
|
1381
|
+
for (let row = region.rowStart; row <= region.rowEnd; row++) {
|
|
1382
|
+
snapshot.push(rows?.[row - 1] ?? "");
|
|
1383
|
+
}
|
|
1384
|
+
return snapshot;
|
|
1385
|
+
}
|
|
626
1386
|
/**
|
|
627
1387
|
* Schedule a coalesced full render; multiple events in the same tick
|
|
628
1388
|
* produce one frame. Use this for any change that can affect the child
|
|
@@ -777,6 +1537,71 @@ export class TerminalHost {
|
|
|
777
1537
|
: null;
|
|
778
1538
|
return buildVerticalStackSurface(agent, artifact, { cols: layout.childCols, rows: layout.childRows, topRows });
|
|
779
1539
|
}
|
|
1540
|
+
agentSplitArtifactPlacement() {
|
|
1541
|
+
if (!this.isAgentSplitOpen() || !this.layout || this.agentSplit?.pane === undefined) {
|
|
1542
|
+
return undefined;
|
|
1543
|
+
}
|
|
1544
|
+
const layout = this.layout;
|
|
1545
|
+
const metrics = computeSplitMetrics({
|
|
1546
|
+
cols: layout.columns,
|
|
1547
|
+
rows: layout.childRows,
|
|
1548
|
+
collapseAt: SPLIT_COLLAPSE_AT_COLS,
|
|
1549
|
+
});
|
|
1550
|
+
if (!metrics.collapsed) {
|
|
1551
|
+
return {
|
|
1552
|
+
region: {
|
|
1553
|
+
rowStart: 1,
|
|
1554
|
+
rowEnd: layout.childRows,
|
|
1555
|
+
colStart: metrics.leftWidth + 2,
|
|
1556
|
+
colEnd: layout.columns,
|
|
1557
|
+
},
|
|
1558
|
+
pane: this.agentSplit.pane,
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
const topRows = this.collapsedTopRows(layout.childRows);
|
|
1562
|
+
if (topRows >= layout.childRows)
|
|
1563
|
+
return undefined;
|
|
1564
|
+
return {
|
|
1565
|
+
region: {
|
|
1566
|
+
rowStart: topRows + 1,
|
|
1567
|
+
rowEnd: layout.childRows,
|
|
1568
|
+
colStart: 1,
|
|
1569
|
+
colEnd: layout.columns,
|
|
1570
|
+
},
|
|
1571
|
+
pane: this.agentSplit.pane,
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
docSurfacePlacementForTarget(target, point) {
|
|
1575
|
+
if (target === "agent-artifact")
|
|
1576
|
+
return this.agentSplitArtifactPlacement();
|
|
1577
|
+
if (!this.parentUi || !isHostParentUiMode(this.parentUi))
|
|
1578
|
+
return undefined;
|
|
1579
|
+
if (point) {
|
|
1580
|
+
const placement = this.parentUi.docSurfaceSelectionPlacementAt?.(point.col, point.row);
|
|
1581
|
+
if (placement)
|
|
1582
|
+
return placement;
|
|
1583
|
+
}
|
|
1584
|
+
if (target === "parent-doc-surface")
|
|
1585
|
+
return undefined;
|
|
1586
|
+
return this.parentUi.artifactSelectionPlacement?.();
|
|
1587
|
+
}
|
|
1588
|
+
docSurfacePlacement(pane) {
|
|
1589
|
+
const agentPlacement = this.agentSplitArtifactPlacement();
|
|
1590
|
+
if (agentPlacement?.pane === pane)
|
|
1591
|
+
return agentPlacement;
|
|
1592
|
+
const parentPlacements = this.parentUi && isHostParentUiMode(this.parentUi)
|
|
1593
|
+
? this.parentUi.docSurfaceSelectionPlacements?.()
|
|
1594
|
+
: undefined;
|
|
1595
|
+
const parentPlacement = parentPlacements?.find((placement) => placement.pane === pane);
|
|
1596
|
+
if (parentPlacement)
|
|
1597
|
+
return parentPlacement;
|
|
1598
|
+
const offscreenPlacement = this.parentUi && isHostParentUiMode(this.parentUi)
|
|
1599
|
+
? this.parentUi.artifactSelectionPlacement?.()
|
|
1600
|
+
: undefined;
|
|
1601
|
+
if (offscreenPlacement?.pane === pane)
|
|
1602
|
+
return offscreenPlacement;
|
|
1603
|
+
return undefined;
|
|
1604
|
+
}
|
|
780
1605
|
routeAgentSplitMouse(column, row) {
|
|
781
1606
|
if (!this.isAgentSplitOpen() || !this.layout)
|
|
782
1607
|
return null;
|
|
@@ -809,6 +1634,8 @@ export class TerminalHost {
|
|
|
809
1634
|
}
|
|
810
1635
|
}
|
|
811
1636
|
handleChildAlternateChange(isAlternate) {
|
|
1637
|
+
if (isAlternate)
|
|
1638
|
+
this.clearSelection({ schedule: false });
|
|
812
1639
|
this.agentSplit?.collaborator?.onChildAlternateChange?.(isAlternate);
|
|
813
1640
|
}
|
|
814
1641
|
renderUiSurfaceRows(rows, layout, surface) {
|
|
@@ -841,6 +1668,7 @@ export class TerminalHost {
|
|
|
841
1668
|
handleResize(size) {
|
|
842
1669
|
if (!this.isActive)
|
|
843
1670
|
return;
|
|
1671
|
+
this.clearSelection({ schedule: false });
|
|
844
1672
|
let next;
|
|
845
1673
|
if (size) {
|
|
846
1674
|
// Source-provided dimensions are already post-resize and may be
|
|
@@ -900,6 +1728,7 @@ export class TerminalHost {
|
|
|
900
1728
|
this.layout = next;
|
|
901
1729
|
if (prev !== undefined
|
|
902
1730
|
&& (prev.childCols !== next.childCols || prev.childRows !== next.childRows)) {
|
|
1731
|
+
this.clearSelection({ schedule: false });
|
|
903
1732
|
this.invalidateFrameCache();
|
|
904
1733
|
this.applyChildViewportSize();
|
|
905
1734
|
this.notifyParentUiResize();
|
|
@@ -923,6 +1752,18 @@ export class TerminalHost {
|
|
|
923
1752
|
function isHostParentUiMode(mode) {
|
|
924
1753
|
return typeof mode.getSurface === "function";
|
|
925
1754
|
}
|
|
1755
|
+
function scrollDocSurfacePane(pane, direction, lines) {
|
|
1756
|
+
const scrollable = pane;
|
|
1757
|
+
return scrollable.scrollLines?.(direction, lines) ?? 0;
|
|
1758
|
+
}
|
|
1759
|
+
function normalizeSelectionRegion(region) {
|
|
1760
|
+
return {
|
|
1761
|
+
rowStart: Math.min(region.rowStart, region.rowEnd),
|
|
1762
|
+
rowEnd: Math.max(region.rowStart, region.rowEnd),
|
|
1763
|
+
colStart: Math.min(region.colStart, region.colEnd),
|
|
1764
|
+
colEnd: Math.max(region.colStart, region.colEnd),
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
926
1767
|
/**
|
|
927
1768
|
* Map a surface-relative cursor position to a 1-based screen position.
|
|
928
1769
|
*
|