silvery 0.21.1 → 0.23.0
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/{Text-BRf-59j2.mjs → Text-Ci6UCSTL.mjs} +46 -46
- package/dist/Text-Ci6UCSTL.mjs.map +1 -0
- package/dist/{ag-BeFC4S2N.mjs → ag-q9lRlKlt.mjs} +42 -19
- package/dist/ag-q9lRlKlt.mjs.map +1 -0
- package/dist/{animation-N8MFybTk.mjs → animation-CZ4x2R7C.mjs} +1 -1
- package/dist/{animation-N8MFybTk.mjs.map → animation-CZ4x2R7C.mjs.map} +1 -1
- package/dist/{ansi-C6Qs1Wn2.mjs → ansi-CLmIMnop.mjs} +1 -1
- package/dist/{ansi-C6Qs1Wn2.mjs.map → ansi-CLmIMnop.mjs.map} +1 -1
- package/dist/{ansi-CBkam1ty.d.mts → ansi-zmNzgkPB.d.mts} +1 -1
- package/dist/{ansi-CBkam1ty.d.mts.map → ansi-zmNzgkPB.d.mts.map} +1 -1
- package/dist/backend-DSiSIc-h.mjs +13396 -0
- package/dist/backend-DSiSIc-h.mjs.map +1 -0
- package/dist/chain-bridge-CkYcMxlq.mjs +4149 -0
- package/dist/chain-bridge-CkYcMxlq.mjs.map +1 -0
- package/dist/{chunk-hT5z_Zn9.mjs → chunk-BEJ448es.mjs} +1 -1
- package/dist/cli-VNqUKlXw.mjs +4 -0
- package/dist/{context-BU5LkkIy.mjs → context-CEhuD1R_.mjs} +1 -1
- package/dist/{context-BU5LkkIy.mjs.map → context-CEhuD1R_.mjs.map} +1 -1
- package/dist/devtools-BF4IMumr.mjs +2 -0
- package/dist/{devtools-9zfhpFyG.mjs → devtools-td2AjFwc.mjs} +5 -4
- package/dist/{devtools-9zfhpFyG.mjs.map → devtools-td2AjFwc.mjs.map} +1 -1
- package/dist/{easing-B0oZKDki.d.mts → easing-C5MrdO7V.d.mts} +1 -1
- package/dist/{easing-B0oZKDki.d.mts.map → easing-C5MrdO7V.d.mts.map} +1 -1
- package/dist/{eta-DGOuC8yU.mjs → eta-BwYWEjfS.mjs} +1 -5
- package/dist/{eta-DGOuC8yU.mjs.map → eta-BwYWEjfS.mjs.map} +1 -1
- package/dist/{flexily-zero-adapter-D6hcFgrH.mjs → flexily-zero-adapter-DktX3HsX.mjs} +1 -1
- package/dist/{flexily-zero-adapter-CEJOcbNp.mjs → flexily-zero-adapter-VoBGHG14.mjs} +1 -1
- package/dist/{flexily-zero-adapter-CEJOcbNp.mjs.map → flexily-zero-adapter-VoBGHG14.mjs.map} +1 -1
- package/dist/hit-registry-D_nxOivS.d.mts +71 -0
- package/dist/hit-registry-D_nxOivS.d.mts.map +1 -0
- package/dist/{image-1nRyKa60.mjs → image-CIF75xMr.mjs} +24 -24
- package/dist/image-CIF75xMr.mjs.map +1 -0
- package/dist/{index-2E1jYgak.d.mts → index-377zdPLP.d.mts} +35 -12
- package/dist/index-377zdPLP.d.mts.map +1 -0
- package/dist/{index-DnuadDNL.d.mts → index-BcSr0RAQ.d.mts} +8590 -11173
- package/dist/index-BcSr0RAQ.d.mts.map +1 -0
- package/dist/{index-Dg1YaeJb.d.mts → index-DLnpxDAm.d.mts} +2 -2
- package/dist/{index-Dg1YaeJb.d.mts.map → index-DLnpxDAm.d.mts.map} +1 -1
- package/dist/{index-Bi4Jdz5g.d.mts → index-DlNmckTq.d.mts} +2 -2
- package/dist/{index-Bi4Jdz5g.d.mts.map → index-DlNmckTq.d.mts.map} +1 -1
- package/dist/index.d.mts +12 -8
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +18 -17
- package/dist/index.mjs.map +1 -1
- package/dist/{layout-engine-Ca_nbtfL.mjs → layout-engine-BgR58vL0.mjs} +3 -3
- package/dist/{layout-engine-Ca_nbtfL.mjs.map → layout-engine-BgR58vL0.mjs.map} +1 -1
- package/dist/{layout-engine-CgsoBRIn.mjs → layout-engine-DNjTha8f.mjs} +1 -1
- package/dist/{layout-signals-Dch2EiCy.mjs → layout-signals-CGYbHmxy.mjs} +2 -2
- package/dist/layout-signals-CGYbHmxy.mjs.map +1 -0
- package/dist/{bound-term-BumfuXXW.d.mts → mouse-events-B2z5tbsL.d.mts} +540 -401
- package/dist/mouse-events-B2z5tbsL.d.mts.map +1 -0
- package/dist/{mouse-events-hnbJZRwK.mjs → mouse-events-WQ2h-uqG.mjs} +14 -6
- package/dist/mouse-events-WQ2h-uqG.mjs.map +1 -0
- package/dist/{multi-progress-Bg4ngK80.d.mts → multi-progress-BaEFfHKP.d.mts} +2 -2
- package/dist/{multi-progress-Bg4ngK80.d.mts.map → multi-progress-BaEFfHKP.d.mts.map} +1 -1
- package/dist/{multi-progress-CaXTuL9G.mjs → multi-progress-zCWHEm9P.mjs} +3 -3
- package/dist/{multi-progress-CaXTuL9G.mjs.map → multi-progress-zCWHEm9P.mjs.map} +1 -1
- package/dist/{node-BiFu8I9Y.mjs → node-CJ3J_rW-.mjs} +4 -4
- package/dist/node-CJ3J_rW-.mjs.map +1 -0
- package/dist/{progress-B_UPy6zk.mjs → progress-6vJ7nxUg.mjs} +4 -4
- package/dist/{progress-B_UPy6zk.mjs.map → progress-6vJ7nxUg.mjs.map} +1 -1
- package/dist/{progress-bar-DmIMPdL0.mjs → progress-bar-BlFNsZL8.mjs} +4 -4
- package/dist/{progress-bar-DmIMPdL0.mjs.map → progress-bar-BlFNsZL8.mjs.map} +1 -1
- package/dist/{reconciler-NBDSEm8k.mjs → reconciler--_-biN23.mjs} +111 -150
- package/dist/reconciler--_-biN23.mjs.map +1 -0
- package/dist/{render-string-BntLj7Xq.mjs → render-string-DaghHpLN.mjs} +1 -1
- package/dist/{render-string-B4h4SmK7.mjs → render-string-DtHZP0cU.mjs} +15 -11
- package/dist/render-string-DtHZP0cU.mjs.map +1 -0
- package/dist/runtime.d.mts +3 -2
- package/dist/runtime.mjs +3 -2
- package/dist/{schemes-DYt2ushj.mjs → schemes-DWQPxXAi.mjs} +2 -2
- package/dist/{schemes-DYt2ushj.mjs.map → schemes-DWQPxXAi.mjs.map} +1 -1
- package/dist/{spinner-Py8_-hn9.mjs → spinner-B50hILqx.mjs} +3 -3
- package/dist/{spinner-Py8_-hn9.mjs.map → spinner-B50hILqx.mjs.map} +1 -1
- package/dist/{spinner-CLgzJ_QF.d.mts → spinner-DN10UfF0.d.mts} +2 -2
- package/dist/{spinner-CLgzJ_QF.d.mts.map → spinner-DN10UfF0.d.mts.map} +1 -1
- package/dist/{src-Oe6x5PrS.mjs → src-B8lf1kiR.mjs} +190 -83
- package/dist/src-B8lf1kiR.mjs.map +1 -0
- package/dist/{src-Dvq-s8iD.mjs → src-BOLdi3WG.mjs} +3 -3
- package/dist/{src-Dvq-s8iD.mjs.map → src-BOLdi3WG.mjs.map} +1 -1
- package/dist/{src-B9S_woYc.mjs → src-BXeTHHlL.mjs} +20289 -23262
- package/dist/src-BXeTHHlL.mjs.map +1 -0
- package/dist/{src-WeA_J4BV.mjs → src-BqeBFB3X.mjs} +90 -47
- package/dist/src-BqeBFB3X.mjs.map +1 -0
- package/dist/{src-CbhWmUnF.mjs → src-DbqcSrhy.mjs} +1400 -1427
- package/dist/src-DbqcSrhy.mjs.map +1 -0
- package/dist/{steps-DYrzCUCK.d.mts → steps-B2kBmFpb.d.mts} +1 -1
- package/dist/{steps-DYrzCUCK.d.mts.map → steps-B2kBmFpb.d.mts.map} +1 -1
- package/dist/{svg-DhxQkz-O.mjs → svg-NmV9dgB9.mjs} +1 -1
- package/dist/{svg-DhxQkz-O.mjs.map → svg-NmV9dgB9.mjs.map} +1 -1
- package/dist/{svg-Hk7lIl4F.d.mts → svg-VUr78l5d.d.mts} +1 -1
- package/dist/{svg-Hk7lIl4F.d.mts.map → svg-VUr78l5d.d.mts.map} +1 -1
- package/dist/term.d.mts +2 -2
- package/dist/term.mjs +3 -3
- package/dist/test.d.mts +673 -0
- package/dist/test.d.mts.map +1 -0
- package/dist/test.mjs +2829 -0
- package/dist/test.mjs.map +1 -0
- package/dist/theme.d.mts +2 -2
- package/dist/theme.d.mts.map +1 -1
- package/dist/theme.mjs +3 -3
- package/dist/{types-Bx-XZNbE.d.mts → types-D_E0FbqA.d.mts} +1 -1
- package/dist/{types-Bx-XZNbE.d.mts.map → types-D_E0FbqA.d.mts.map} +1 -1
- package/dist/ui/animation.d.mts +2 -2
- package/dist/ui/animation.mjs +1 -1
- package/dist/ui/ansi.d.mts +1 -1
- package/dist/ui/ansi.mjs +1 -1
- package/dist/ui/cli.d.mts +3 -3
- package/dist/ui/cli.mjs +5 -5
- package/dist/ui/image.d.mts +1 -1
- package/dist/ui/image.mjs +1 -1
- package/dist/ui/progress.d.mts +4 -4
- package/dist/ui/progress.mjs +4 -4
- package/dist/ui/react.d.mts +1 -1
- package/dist/ui/react.mjs +2 -2
- package/dist/ui/react.mjs.map +1 -1
- package/dist/ui/recording-chrome-react.d.mts +1 -1
- package/dist/ui/recording-chrome-react.mjs +2 -2
- package/dist/ui/recording-chrome.d.mts +1 -1
- package/dist/ui/recording-chrome.mjs +1 -1
- package/dist/ui/utils.mjs +1 -1
- package/dist/ui/wrappers.d.mts +2 -2
- package/dist/ui/wrappers.mjs +1 -1
- package/dist/ui.d.mts +6 -6
- package/dist/ui.mjs +7 -7
- package/dist/{ansi-OYRLxxZQ.mjs → unicode-CxyxVGmB.mjs} +7317 -6909
- package/dist/unicode-CxyxVGmB.mjs.map +1 -0
- package/dist/{useLatest-DC8i7guK.d.mts → useLatest-UOvWy0ZW.d.mts} +2 -2
- package/dist/{useLatest-DC8i7guK.d.mts.map → useLatest-UOvWy0ZW.d.mts.map} +1 -1
- package/dist/{useLayout-BKsQl2Or.mjs → useLayout-D5cE8OCI.mjs} +4 -4
- package/dist/{useLayout-BKsQl2Or.mjs.map → useLayout-D5cE8OCI.mjs.map} +1 -1
- package/dist/{with-text-input-DG4f7JII.d.mts → with-text-input-DLoSCBrm.d.mts} +3 -3
- package/dist/{with-text-input-DG4f7JII.d.mts.map → with-text-input-DLoSCBrm.d.mts.map} +1 -1
- package/dist/wrap-measurer-registration-IV2HtcCd.d.mts +3106 -0
- package/dist/wrap-measurer-registration-IV2HtcCd.d.mts.map +1 -0
- package/dist/{wrapper-D7gNSsgf.mjs → wrapper-CD29afOs.mjs} +17 -37
- package/dist/wrapper-CD29afOs.mjs.map +1 -0
- package/dist/{wrappers-CypAzrMO.mjs → wrappers-CInBJm44.mjs} +6 -6
- package/dist/{wrappers-CypAzrMO.mjs.map → wrappers-CInBJm44.mjs.map} +1 -1
- package/dist/{yoga-adapter-SsEIqMc1.mjs → yoga-adapter-9uab5Qn-.mjs} +2 -2
- package/dist/{yoga-adapter-SsEIqMc1.mjs.map → yoga-adapter-9uab5Qn-.mjs.map} +1 -1
- package/dist/yoga-adapter-yqTl1aXK.mjs +2 -0
- package/package.json +20 -15
- package/dist/Text-BRf-59j2.mjs.map +0 -1
- package/dist/ag-BeFC4S2N.mjs.map +0 -1
- package/dist/ansi-OYRLxxZQ.mjs.map +0 -1
- package/dist/bound-term-BumfuXXW.d.mts.map +0 -1
- package/dist/cli-DG6zsfsS.mjs +0 -4
- package/dist/devtools-nmPUmYU_.mjs +0 -2
- package/dist/image-1nRyKa60.mjs.map +0 -1
- package/dist/index-2E1jYgak.d.mts.map +0 -1
- package/dist/index-DnuadDNL.d.mts.map +0 -1
- package/dist/layout-signals-Dch2EiCy.mjs.map +0 -1
- package/dist/mouse-events-hnbJZRwK.mjs.map +0 -1
- package/dist/node-BiFu8I9Y.mjs.map +0 -1
- package/dist/reconciler-NBDSEm8k.mjs.map +0 -1
- package/dist/render-string-B4h4SmK7.mjs.map +0 -1
- package/dist/src-B9S_woYc.mjs.map +0 -1
- package/dist/src-CbhWmUnF.mjs.map +0 -1
- package/dist/src-Oe6x5PrS.mjs.map +0 -1
- package/dist/src-WeA_J4BV.mjs.map +0 -1
- package/dist/ui/display.d.mts +0 -35
- package/dist/ui/display.d.mts.map +0 -1
- package/dist/ui/display.mjs +0 -123
- package/dist/ui/display.mjs.map +0 -1
- package/dist/ui/input.d.mts +0 -184
- package/dist/ui/input.d.mts.map +0 -1
- package/dist/ui/input.mjs +0 -283
- package/dist/ui/input.mjs.map +0 -1
- package/dist/wrapper-D7gNSsgf.mjs.map +0 -1
- package/dist/yoga-adapter-1ex8r0ws.mjs +0 -2
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { f as syncFocusedSignal } from "./layout-signals-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { C as
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import "./
|
|
9
|
-
import {
|
|
10
|
-
import React, { createContext, useContext, useEffect, useLayoutEffect, useRef } from "react";
|
|
1
|
+
import { C as findFocusableAncestor, D as isFocusHidden, E as getTabOrder$1, O as isFocusable, T as getExplicitFocusLink, b as setFocused, w as findSpatialTarget, x as findByTestID } from "./mouse-events-WQ2h-uqG.mjs";
|
|
2
|
+
import { f as syncFocusedSignal } from "./layout-signals-CGYbHmxy.mjs";
|
|
3
|
+
import { N as stripAnsi, Rt as queryKittyKeyboard, U as getInternalStreams, mt as createOutputPhase, r as createMeasurer } from "./unicode-CxyxVGmB.mjs";
|
|
4
|
+
import { j as createTerminalProfile } from "./src-B8lf1kiR.mjs";
|
|
5
|
+
import { C as isDirty } from "./reconciler--_-biN23.mjs";
|
|
6
|
+
import "./layout-engine-BgR58vL0.mjs";
|
|
7
|
+
import { a as setActiveColorLevel, i as parseColor } from "./ag-q9lRlKlt.mjs";
|
|
8
|
+
import { a as NodeContext } from "./context-CEhuD1R_.mjs";
|
|
9
|
+
import { u as useScrollRect } from "./useLayout-D5cE8OCI.mjs";
|
|
11
10
|
import { createLogger } from "loggily";
|
|
11
|
+
import React, { createContext, useContext, useEffect, useLayoutEffect, useRef } from "react";
|
|
12
12
|
//#region packages/ag/src/focus-manager.ts
|
|
13
13
|
function createFocusManager(options) {
|
|
14
14
|
const onFocusChange = options?.onFocusChange;
|
|
@@ -456,807 +456,301 @@ function createFocusManager(options) {
|
|
|
456
456
|
};
|
|
457
457
|
}
|
|
458
458
|
//#endregion
|
|
459
|
-
//#region packages/
|
|
459
|
+
//#region packages/headless/src/selection.ts
|
|
460
460
|
/**
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
* Each item represents a rendered list entry (card, row, etc.) with its
|
|
464
|
-
* ANSI snapshot, split rows, and plain-text rows for searching.
|
|
465
|
-
* When total rows or cached ANSI bytes exceed capacity, the oldest items are
|
|
466
|
-
* evicted. The byte count tracks the serialized string payload; it is a
|
|
467
|
-
* deterministic cache budget, not an exact process heap measurement.
|
|
461
|
+
* Check if a character is a word character (not whitespace/punctuation).
|
|
462
|
+
* Word chars: letters, digits, underscore (matching \w).
|
|
468
463
|
*/
|
|
469
|
-
function
|
|
470
|
-
|
|
464
|
+
function isWordChar(ch) {
|
|
465
|
+
return /\w/.test(ch);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Find word boundaries at a given column in the buffer row.
|
|
469
|
+
* Returns { startCol, endCol } inclusive of the word.
|
|
470
|
+
* If the position is on whitespace/punctuation, selects that single char.
|
|
471
|
+
*/
|
|
472
|
+
function findWordBoundary(buffer, col, row) {
|
|
473
|
+
const width = buffer.width;
|
|
474
|
+
const ch = buffer.getCell(col, row).char;
|
|
475
|
+
if (isWordChar(ch)) {
|
|
476
|
+
let startCol = col;
|
|
477
|
+
while (startCol > 0 && isWordChar(buffer.getCell(startCol - 1, row).char)) startCol--;
|
|
478
|
+
let endCol = col;
|
|
479
|
+
while (endCol < width - 1 && isWordChar(buffer.getCell(endCol + 1, row).char)) endCol++;
|
|
480
|
+
return {
|
|
481
|
+
startCol,
|
|
482
|
+
endCol
|
|
483
|
+
};
|
|
484
|
+
}
|
|
471
485
|
return {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
rows,
|
|
475
|
-
plainTextRows: rows.map((r) => stripAnsi(r)),
|
|
476
|
-
width
|
|
486
|
+
startCol: col,
|
|
487
|
+
endCol: col
|
|
477
488
|
};
|
|
478
489
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
490
|
+
/**
|
|
491
|
+
* Find line boundaries for a given row.
|
|
492
|
+
* Returns { startCol, endCol } spanning from first content to last content.
|
|
493
|
+
* If the row is empty, returns { startCol: 0, endCol: width - 1 }.
|
|
494
|
+
*/
|
|
495
|
+
function findLineBoundary(buffer, row) {
|
|
496
|
+
const width = buffer.width;
|
|
497
|
+
let endCol = width - 1;
|
|
498
|
+
while (endCol > 0 && buffer.getCell(endCol, row).char.trim() === "") endCol--;
|
|
499
|
+
let startCol = 0;
|
|
500
|
+
while (startCol < endCol && buffer.getCell(startCol, row).char.trim() === "") startCol++;
|
|
501
|
+
if (startCol >= endCol && buffer.getCell(startCol, row).char.trim() === "") return {
|
|
502
|
+
startCol: 0,
|
|
503
|
+
endCol: width - 1
|
|
504
|
+
};
|
|
505
|
+
return {
|
|
506
|
+
startCol,
|
|
507
|
+
endCol
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Extend the head position according to the current granularity.
|
|
512
|
+
* For word granularity: snaps to word boundaries.
|
|
513
|
+
* For line granularity: snaps to line boundaries.
|
|
514
|
+
*/
|
|
515
|
+
function extendByGranularity(col, row, anchor, granularity, buffer) {
|
|
516
|
+
if (granularity === "character" || !buffer) return {
|
|
517
|
+
col,
|
|
518
|
+
row
|
|
519
|
+
};
|
|
520
|
+
if (granularity === "word") {
|
|
521
|
+
const { startCol, endCol } = findWordBoundary(buffer, col, row);
|
|
522
|
+
return row > anchor.row || row === anchor.row && col >= anchor.col ? {
|
|
523
|
+
col: endCol,
|
|
524
|
+
row
|
|
525
|
+
} : {
|
|
526
|
+
col: startCol,
|
|
527
|
+
row
|
|
528
|
+
};
|
|
491
529
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
};
|
|
502
|
-
cumulative += itemRows;
|
|
503
|
-
}
|
|
504
|
-
return null;
|
|
530
|
+
if (granularity === "line") {
|
|
531
|
+
const { startCol, endCol } = findLineBoundary(buffer, row);
|
|
532
|
+
return row > anchor.row || row === anchor.row && col >= anchor.col ? {
|
|
533
|
+
col: endCol,
|
|
534
|
+
row
|
|
535
|
+
} : {
|
|
536
|
+
col: startCol,
|
|
537
|
+
row
|
|
538
|
+
};
|
|
505
539
|
}
|
|
506
540
|
return {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
_totalRows += item.rows.length;
|
|
510
|
-
_totalBytes += estimateHistoryItemBytes(item);
|
|
511
|
-
evict();
|
|
512
|
-
},
|
|
513
|
-
get totalRows() {
|
|
514
|
-
return _totalRows;
|
|
515
|
-
},
|
|
516
|
-
get totalBytes() {
|
|
517
|
-
return _totalBytes;
|
|
518
|
-
},
|
|
519
|
-
get itemCount() {
|
|
520
|
-
return items.length;
|
|
521
|
-
},
|
|
522
|
-
get capacity() {
|
|
523
|
-
return capacity;
|
|
524
|
-
},
|
|
525
|
-
get capacityBytes() {
|
|
526
|
-
return capacityBytes;
|
|
527
|
-
},
|
|
528
|
-
getRows(startRow, count) {
|
|
529
|
-
const result = [];
|
|
530
|
-
for (let r = startRow; r < startRow + count; r++) {
|
|
531
|
-
const resolved = resolveRow(r);
|
|
532
|
-
if (resolved) result.push(items[resolved.itemIndex].rows[resolved.localRow]);
|
|
533
|
-
else result.push("");
|
|
534
|
-
}
|
|
535
|
-
return result;
|
|
536
|
-
},
|
|
537
|
-
getPlainTextRows(startRow, count) {
|
|
538
|
-
const result = [];
|
|
539
|
-
for (let r = startRow; r < startRow + count; r++) {
|
|
540
|
-
const resolved = resolveRow(r);
|
|
541
|
-
if (resolved) result.push(items[resolved.itemIndex].plainTextRows[resolved.localRow]);
|
|
542
|
-
else result.push("");
|
|
543
|
-
}
|
|
544
|
-
return result;
|
|
545
|
-
},
|
|
546
|
-
search(query) {
|
|
547
|
-
if (!query) return [];
|
|
548
|
-
const lowerQuery = query.toLowerCase();
|
|
549
|
-
const matches = [];
|
|
550
|
-
let rowOffset = 0;
|
|
551
|
-
for (const item of items) {
|
|
552
|
-
for (let r = 0; r < item.plainTextRows.length; r++) if (item.plainTextRows[r].toLowerCase().includes(lowerQuery)) matches.push(rowOffset + r);
|
|
553
|
-
rowOffset += item.rows.length;
|
|
554
|
-
}
|
|
555
|
-
return matches;
|
|
556
|
-
},
|
|
557
|
-
getItemAtRow(row) {
|
|
558
|
-
const resolved = resolveRow(row);
|
|
559
|
-
if (!resolved) return null;
|
|
560
|
-
return {
|
|
561
|
-
item: items[resolved.itemIndex],
|
|
562
|
-
localRow: resolved.localRow
|
|
563
|
-
};
|
|
564
|
-
},
|
|
565
|
-
clear() {
|
|
566
|
-
items = [];
|
|
567
|
-
_totalRows = 0;
|
|
568
|
-
_totalBytes = 0;
|
|
569
|
-
}
|
|
541
|
+
col,
|
|
542
|
+
row
|
|
570
543
|
};
|
|
571
544
|
}
|
|
572
|
-
function
|
|
573
|
-
return
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
545
|
+
function createTerminalSelectionState() {
|
|
546
|
+
return {
|
|
547
|
+
range: null,
|
|
548
|
+
selecting: false,
|
|
549
|
+
source: null,
|
|
550
|
+
granularity: "character",
|
|
551
|
+
scope: null
|
|
552
|
+
};
|
|
578
553
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
554
|
+
/**
|
|
555
|
+
* Clamp a position to a scope boundary.
|
|
556
|
+
*/
|
|
557
|
+
function clampToScope(col, row, scope) {
|
|
558
|
+
if (!scope) return {
|
|
559
|
+
col,
|
|
560
|
+
row
|
|
561
|
+
};
|
|
587
562
|
return {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
},
|
|
591
|
-
get frozenRows() {
|
|
592
|
-
return history.totalRows;
|
|
593
|
-
},
|
|
594
|
-
get liveRows() {
|
|
595
|
-
return liveRowCount();
|
|
596
|
-
},
|
|
597
|
-
getRows(startRow, count) {
|
|
598
|
-
const frozen = history.totalRows;
|
|
599
|
-
const result = [];
|
|
600
|
-
for (let r = startRow; r < startRow + count; r++) if (r < 0 || r >= this.totalRows) result.push("");
|
|
601
|
-
else if (r < frozen) result.push(...history.getRows(r, 1));
|
|
602
|
-
else {
|
|
603
|
-
let liveRow = r - frozen;
|
|
604
|
-
let found = false;
|
|
605
|
-
for (const block of getLiveItems()) {
|
|
606
|
-
if (liveRow < block.rows.length) {
|
|
607
|
-
result.push(block.rows[liveRow]);
|
|
608
|
-
found = true;
|
|
609
|
-
break;
|
|
610
|
-
}
|
|
611
|
-
liveRow -= block.rows.length;
|
|
612
|
-
}
|
|
613
|
-
if (!found) result.push("");
|
|
614
|
-
}
|
|
615
|
-
return result;
|
|
616
|
-
},
|
|
617
|
-
getPlainTextRows(startRow, count) {
|
|
618
|
-
const frozen = history.totalRows;
|
|
619
|
-
const result = [];
|
|
620
|
-
for (let r = startRow; r < startRow + count; r++) if (r < 0 || r >= this.totalRows) result.push("");
|
|
621
|
-
else if (r < frozen) result.push(...history.getPlainTextRows(r, 1));
|
|
622
|
-
else {
|
|
623
|
-
let liveRow = r - frozen;
|
|
624
|
-
let found = false;
|
|
625
|
-
for (const block of getLiveItems()) {
|
|
626
|
-
if (liveRow < block.plainTextRows.length) {
|
|
627
|
-
result.push(block.plainTextRows[liveRow]);
|
|
628
|
-
found = true;
|
|
629
|
-
break;
|
|
630
|
-
}
|
|
631
|
-
liveRow -= block.plainTextRows.length;
|
|
632
|
-
}
|
|
633
|
-
if (!found) result.push("");
|
|
634
|
-
}
|
|
635
|
-
return result;
|
|
636
|
-
},
|
|
637
|
-
getSource(row) {
|
|
638
|
-
const frozen = history.totalRows;
|
|
639
|
-
if (row < 0 || row >= this.totalRows) return null;
|
|
640
|
-
if (row < frozen) {
|
|
641
|
-
const hit = history.getItemAtRow(row);
|
|
642
|
-
if (!hit) return null;
|
|
643
|
-
return {
|
|
644
|
-
type: "frozen",
|
|
645
|
-
itemKey: hit.item.key,
|
|
646
|
-
localRow: hit.localRow
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
const liveItems = getLiveItems();
|
|
650
|
-
let liveRow = row - frozen;
|
|
651
|
-
for (const block of liveItems) {
|
|
652
|
-
if (liveRow < block.rows.length) return {
|
|
653
|
-
type: "live",
|
|
654
|
-
itemIndex: block.itemIndex,
|
|
655
|
-
localRow: liveRow
|
|
656
|
-
};
|
|
657
|
-
liveRow -= block.rows.length;
|
|
658
|
-
}
|
|
659
|
-
return null;
|
|
660
|
-
},
|
|
661
|
-
search(query) {
|
|
662
|
-
if (!query) return [];
|
|
663
|
-
const lowerQuery = query.toLowerCase();
|
|
664
|
-
const matches = [];
|
|
665
|
-
const frozen = history.totalRows;
|
|
666
|
-
const frozenRowMatches = history.search(query);
|
|
667
|
-
for (const row of frozenRowMatches) {
|
|
668
|
-
const line = history.getPlainTextRows(row, 1)[0].toLowerCase();
|
|
669
|
-
let pos = line.indexOf(lowerQuery);
|
|
670
|
-
while (pos !== -1) {
|
|
671
|
-
matches.push({
|
|
672
|
-
row,
|
|
673
|
-
startCol: pos,
|
|
674
|
-
endCol: pos + query.length
|
|
675
|
-
});
|
|
676
|
-
pos = line.indexOf(lowerQuery, pos + 1);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
let rowOffset = 0;
|
|
680
|
-
for (const block of getLiveItems()) {
|
|
681
|
-
for (let i = 0; i < block.plainTextRows.length; i++) {
|
|
682
|
-
const line = block.plainTextRows[i].toLowerCase();
|
|
683
|
-
let pos = line.indexOf(lowerQuery);
|
|
684
|
-
while (pos !== -1) {
|
|
685
|
-
matches.push({
|
|
686
|
-
row: frozen + rowOffset + i,
|
|
687
|
-
startCol: pos,
|
|
688
|
-
endCol: pos + query.length
|
|
689
|
-
});
|
|
690
|
-
pos = line.indexOf(lowerQuery, pos + 1);
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
rowOffset += block.plainTextRows.length;
|
|
694
|
-
}
|
|
695
|
-
return matches;
|
|
696
|
-
}
|
|
563
|
+
col: Math.max(scope.left, Math.min(scope.right, col)),
|
|
564
|
+
row: Math.max(scope.top, Math.min(scope.bottom, row))
|
|
697
565
|
};
|
|
698
566
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
const rows = config.document.getRows(startRow, endRow - startRow + 1);
|
|
715
|
-
const lines = [];
|
|
716
|
-
for (let i = 0; i < rows.length; i++) {
|
|
717
|
-
const plain = stripAnsi(rows[i]);
|
|
718
|
-
const row = startRow + i;
|
|
719
|
-
if (row === startRow && row === endRow) lines.push(plain.slice(startCol, endCol));
|
|
720
|
-
else if (row === startRow) lines.push(plain.slice(startCol));
|
|
721
|
-
else if (row === endRow) lines.push(plain.slice(0, endCol));
|
|
722
|
-
else lines.push(plain);
|
|
723
|
-
}
|
|
724
|
-
return lines.join("\n");
|
|
725
|
-
},
|
|
726
|
-
search(query) {
|
|
727
|
-
return config.document.search(query);
|
|
728
|
-
},
|
|
729
|
-
hitTest(viewportRow, viewportCol) {
|
|
730
|
-
const docRow = config.viewportToDocument(viewportRow);
|
|
731
|
-
if (docRow < 0 || docRow >= config.document.totalRows) return null;
|
|
732
|
-
return {
|
|
733
|
-
row: docRow,
|
|
734
|
-
col: viewportCol
|
|
735
|
-
};
|
|
736
|
-
},
|
|
737
|
-
reveal(row) {
|
|
738
|
-
config.onReveal(row);
|
|
739
|
-
notify();
|
|
740
|
-
},
|
|
741
|
-
notifyContentChange() {
|
|
742
|
-
notify();
|
|
743
|
-
},
|
|
744
|
-
subscribe(listener) {
|
|
745
|
-
listeners.add(listener);
|
|
746
|
-
return () => {
|
|
747
|
-
listeners.delete(listener);
|
|
748
|
-
};
|
|
749
|
-
},
|
|
750
|
-
get capabilities() {
|
|
751
|
-
return config.capabilities;
|
|
567
|
+
function terminalSelectionUpdate(action, state) {
|
|
568
|
+
switch (action.type) {
|
|
569
|
+
case "start": {
|
|
570
|
+
const scope = action.scope ?? null;
|
|
571
|
+
const pos = clampToScope(action.col, action.row, scope);
|
|
572
|
+
return [{
|
|
573
|
+
range: {
|
|
574
|
+
anchor: pos,
|
|
575
|
+
head: pos
|
|
576
|
+
},
|
|
577
|
+
selecting: true,
|
|
578
|
+
source: action.source ?? "mouse",
|
|
579
|
+
granularity: "character",
|
|
580
|
+
scope
|
|
581
|
+
}, [{ type: "render" }]];
|
|
752
582
|
}
|
|
753
|
-
|
|
583
|
+
case "startWord": {
|
|
584
|
+
const scope = action.scope ?? null;
|
|
585
|
+
const { startCol, endCol } = findWordBoundary(action.buffer, action.col, action.row);
|
|
586
|
+
return [{
|
|
587
|
+
range: {
|
|
588
|
+
anchor: clampToScope(startCol, action.row, scope),
|
|
589
|
+
head: clampToScope(endCol, action.row, scope)
|
|
590
|
+
},
|
|
591
|
+
selecting: true,
|
|
592
|
+
source: action.source ?? "mouse",
|
|
593
|
+
granularity: "word",
|
|
594
|
+
scope
|
|
595
|
+
}, [{ type: "render" }]];
|
|
596
|
+
}
|
|
597
|
+
case "startLine": {
|
|
598
|
+
const scope = action.scope ?? null;
|
|
599
|
+
const { startCol, endCol } = findLineBoundary(action.buffer, action.row);
|
|
600
|
+
return [{
|
|
601
|
+
range: {
|
|
602
|
+
anchor: clampToScope(startCol, action.row, scope),
|
|
603
|
+
head: clampToScope(endCol, action.row, scope)
|
|
604
|
+
},
|
|
605
|
+
selecting: true,
|
|
606
|
+
source: action.source ?? "mouse",
|
|
607
|
+
granularity: "line",
|
|
608
|
+
scope
|
|
609
|
+
}, [{ type: "render" }]];
|
|
610
|
+
}
|
|
611
|
+
case "extend": {
|
|
612
|
+
if (!state.selecting) return [state, []];
|
|
613
|
+
const scope = "scope" in action ? action.scope ?? null : state.scope;
|
|
614
|
+
const extended = extendByGranularity(action.col, action.row, state.range.anchor, state.granularity, action.buffer);
|
|
615
|
+
const head = clampToScope(extended.col, extended.row, scope);
|
|
616
|
+
return [{
|
|
617
|
+
...state,
|
|
618
|
+
range: {
|
|
619
|
+
anchor: state.range.anchor,
|
|
620
|
+
head
|
|
621
|
+
},
|
|
622
|
+
scope,
|
|
623
|
+
selecting: true
|
|
624
|
+
}, [{ type: "render" }]];
|
|
625
|
+
}
|
|
626
|
+
case "finish":
|
|
627
|
+
if (!state.range) return [{
|
|
628
|
+
...state,
|
|
629
|
+
selecting: false
|
|
630
|
+
}, []];
|
|
631
|
+
return [{
|
|
632
|
+
...state,
|
|
633
|
+
range: state.range,
|
|
634
|
+
selecting: false
|
|
635
|
+
}, []];
|
|
636
|
+
case "clear": {
|
|
637
|
+
const hadRange = state.range !== null;
|
|
638
|
+
return [createTerminalSelectionState(), hadRange ? [{ type: "render" }] : []];
|
|
639
|
+
}
|
|
640
|
+
}
|
|
754
641
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
overlayRowCount: 0,
|
|
763
|
-
liveRowsVisible: viewportHeight,
|
|
764
|
-
isScrolledUp: false,
|
|
765
|
-
totalHeight: totalHistory + viewportHeight
|
|
642
|
+
function normalizeRange(range) {
|
|
643
|
+
const { anchor, head } = range;
|
|
644
|
+
if (anchor.row < head.row || anchor.row === head.row && anchor.col <= head.col) return {
|
|
645
|
+
startRow: anchor.row,
|
|
646
|
+
startCol: anchor.col,
|
|
647
|
+
endRow: head.row,
|
|
648
|
+
endCol: head.col
|
|
766
649
|
};
|
|
767
|
-
const startRow = Math.max(0, totalHistory - Math.min(scrollOffset, totalHistory));
|
|
768
|
-
const rowsToShow = Math.min(viewportHeight, totalHistory - startRow);
|
|
769
650
|
return {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
totalHeight: totalHistory + viewportHeight
|
|
651
|
+
startRow: head.row,
|
|
652
|
+
startCol: head.col,
|
|
653
|
+
endRow: anchor.row,
|
|
654
|
+
endCol: anchor.col
|
|
775
655
|
};
|
|
776
656
|
}
|
|
777
|
-
//#endregion
|
|
778
|
-
//#region packages/ag-term/src/search-overlay.ts
|
|
779
657
|
/**
|
|
780
|
-
*
|
|
781
|
-
* their character offsets. Empty query or empty text returns `[]`.
|
|
782
|
-
*
|
|
783
|
-
* This is the canonical silvery search-match algorithm. The same logic runs
|
|
784
|
-
* inside `ListView`'s registered Searchable (to find matches across items)
|
|
785
|
-
* and is exposed to consumers that render multi-segment items — a LogRow
|
|
786
|
-
* whose searchable text is a concatenation of field values, but whose visual
|
|
787
|
-
* rendering splits those fields across separate Text nodes, needs per-segment
|
|
788
|
-
* ranges to highlight without re-implementing the semantics.
|
|
789
|
-
*
|
|
790
|
-
* Ranges are returned in ascending `start` order; overlapping matches are
|
|
791
|
-
* not produced (`indexOf(..., start = last.start + 1)` advances past each
|
|
792
|
-
* match start, not past its full length, preserving overlapping runs of
|
|
793
|
-
* short queries — e.g. `"aa"` in `"aaaa"` yields `[0..2], [1..3], [2..4]`).
|
|
658
|
+
* Extract text from a buffer within a selection range.
|
|
794
659
|
*
|
|
795
|
-
*
|
|
796
|
-
*
|
|
797
|
-
*
|
|
660
|
+
* Handles:
|
|
661
|
+
* - Soft-wrap joining (via RowMetadata.softWrapped) — rejoins wrapped visual
|
|
662
|
+
* rows into their logical line, reinserting the word-wrap break space
|
|
663
|
+
* (RowMetadata.wrapJoinSpace) that trim-mode rendering stripped
|
|
664
|
+
* - Trailing fill exclusion (clamp to RowMetadata.lastContentCol; falls back
|
|
665
|
+
* to a trailing-whitespace trim when a row carries no metadata)
|
|
666
|
+
* - Blank line preservation within selection
|
|
667
|
+
* - Wide-char continuation cell skipping
|
|
668
|
+
* - SELECTABLE_FLAG filtering (when respectSelectableFlag is true)
|
|
669
|
+
* - Contain-scope clipping (when options.scope is set)
|
|
798
670
|
*/
|
|
799
|
-
function
|
|
800
|
-
|
|
801
|
-
const
|
|
802
|
-
const
|
|
803
|
-
const
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
const
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
query: "",
|
|
820
|
-
matches: [],
|
|
821
|
-
currentMatch: -1,
|
|
822
|
-
cursorPosition: 0
|
|
823
|
-
};
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* Update search state. searchFn is called when query changes to find matches.
|
|
827
|
-
*/
|
|
828
|
-
function searchUpdate(action, state, searchFn) {
|
|
829
|
-
switch (action.type) {
|
|
830
|
-
case "open": return [{
|
|
831
|
-
...state,
|
|
832
|
-
active: true,
|
|
833
|
-
query: "",
|
|
834
|
-
matches: [],
|
|
835
|
-
currentMatch: -1,
|
|
836
|
-
cursorPosition: 0
|
|
837
|
-
}, [{ type: "render" }]];
|
|
838
|
-
case "close": return [{
|
|
839
|
-
...state,
|
|
840
|
-
active: false,
|
|
841
|
-
query: "",
|
|
842
|
-
cursorPosition: 0
|
|
843
|
-
}, [{ type: "render" }]];
|
|
844
|
-
case "input": {
|
|
845
|
-
const query = state.query.slice(0, state.cursorPosition) + action.char + state.query.slice(state.cursorPosition);
|
|
846
|
-
const cursorPosition = state.cursorPosition + 1;
|
|
847
|
-
const matches = searchFn ? searchFn(query) : [];
|
|
848
|
-
const currentMatch = matches.length > 0 ? 0 : -1;
|
|
849
|
-
const effects = [{ type: "render" }];
|
|
850
|
-
if (currentMatch >= 0) effects.push({
|
|
851
|
-
type: "scrollTo",
|
|
852
|
-
row: matches[0].row
|
|
853
|
-
});
|
|
854
|
-
return [{
|
|
855
|
-
...state,
|
|
856
|
-
query,
|
|
857
|
-
cursorPosition,
|
|
858
|
-
matches,
|
|
859
|
-
currentMatch
|
|
860
|
-
}, effects];
|
|
861
|
-
}
|
|
862
|
-
case "backspace": {
|
|
863
|
-
if (state.cursorPosition === 0) return [state, []];
|
|
864
|
-
const query = state.query.slice(0, state.cursorPosition - 1) + state.query.slice(state.cursorPosition);
|
|
865
|
-
const cursorPosition = state.cursorPosition - 1;
|
|
866
|
-
const matches = searchFn ? searchFn(query) : [];
|
|
867
|
-
const currentMatch = matches.length > 0 ? 0 : -1;
|
|
868
|
-
const effects = [{ type: "render" }];
|
|
869
|
-
if (currentMatch >= 0) effects.push({
|
|
870
|
-
type: "scrollTo",
|
|
871
|
-
row: matches[0].row
|
|
872
|
-
});
|
|
873
|
-
return [{
|
|
874
|
-
...state,
|
|
875
|
-
query,
|
|
876
|
-
cursorPosition,
|
|
877
|
-
matches,
|
|
878
|
-
currentMatch
|
|
879
|
-
}, effects];
|
|
880
|
-
}
|
|
881
|
-
case "nextMatch": {
|
|
882
|
-
if (state.matches.length === 0) return [state, []];
|
|
883
|
-
const currentMatch = (state.currentMatch + 1) % state.matches.length;
|
|
884
|
-
return [{
|
|
885
|
-
...state,
|
|
886
|
-
currentMatch
|
|
887
|
-
}, [{
|
|
888
|
-
type: "scrollTo",
|
|
889
|
-
row: state.matches[currentMatch].row
|
|
890
|
-
}, { type: "render" }]];
|
|
891
|
-
}
|
|
892
|
-
case "prevMatch": {
|
|
893
|
-
if (state.matches.length === 0) return [state, []];
|
|
894
|
-
const currentMatch = (state.currentMatch - 1 + state.matches.length) % state.matches.length;
|
|
895
|
-
return [{
|
|
896
|
-
...state,
|
|
897
|
-
currentMatch
|
|
898
|
-
}, [{
|
|
899
|
-
type: "scrollTo",
|
|
900
|
-
row: state.matches[currentMatch].row
|
|
901
|
-
}, { type: "render" }]];
|
|
671
|
+
function extractText(buffer, range, options) {
|
|
672
|
+
const { startRow, startCol, endRow, endCol } = normalizeRange(range);
|
|
673
|
+
const respectSelectable = options?.respectSelectableFlag ?? true;
|
|
674
|
+
const rowMeta = options?.rowMetadata;
|
|
675
|
+
const scope = options?.scope;
|
|
676
|
+
const parts = [];
|
|
677
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
678
|
+
const meta = rowMeta?.[row];
|
|
679
|
+
let colStart = row === startRow ? startCol : 0;
|
|
680
|
+
let colEnd = row === endRow ? endCol : buffer.width - 1;
|
|
681
|
+
if (scope) {
|
|
682
|
+
colStart = Math.max(colStart, scope.left);
|
|
683
|
+
colEnd = Math.min(colEnd, scope.right);
|
|
684
|
+
if (colStart > colEnd) {
|
|
685
|
+
if (!(meta?.softWrapped && row < endRow)) {
|
|
686
|
+
parts.push("");
|
|
687
|
+
if (row < endRow) parts.push("\n");
|
|
688
|
+
}
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
902
691
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
case "cursorRight":
|
|
910
|
-
if (state.cursorPosition >= state.query.length) return [state, []];
|
|
911
|
-
return [{
|
|
912
|
-
...state,
|
|
913
|
-
cursorPosition: state.cursorPosition + 1
|
|
914
|
-
}, []];
|
|
915
|
-
case "cursorToStart":
|
|
916
|
-
if (state.cursorPosition === 0) return [state, []];
|
|
917
|
-
return [{
|
|
918
|
-
...state,
|
|
919
|
-
cursorPosition: 0
|
|
920
|
-
}, []];
|
|
921
|
-
case "cursorToEnd":
|
|
922
|
-
if (state.cursorPosition >= state.query.length) return [state, []];
|
|
923
|
-
return [{
|
|
924
|
-
...state,
|
|
925
|
-
cursorPosition: state.query.length
|
|
926
|
-
}, []];
|
|
927
|
-
case "deleteWordBack": {
|
|
928
|
-
if (state.cursorPosition === 0) return [state, []];
|
|
929
|
-
let pos = state.cursorPosition;
|
|
930
|
-
while (pos > 0 && /\s/.test(state.query[pos - 1] ?? "")) pos--;
|
|
931
|
-
while (pos > 0 && !/\s/.test(state.query[pos - 1] ?? "")) pos--;
|
|
932
|
-
const query = state.query.slice(0, pos) + state.query.slice(state.cursorPosition);
|
|
933
|
-
const cursorPosition = pos;
|
|
934
|
-
const matches = searchFn ? searchFn(query) : [];
|
|
935
|
-
const currentMatch = matches.length > 0 ? 0 : -1;
|
|
936
|
-
const effects = [{ type: "render" }];
|
|
937
|
-
if (currentMatch >= 0) effects.push({
|
|
938
|
-
type: "scrollTo",
|
|
939
|
-
row: matches[0].row
|
|
940
|
-
});
|
|
941
|
-
return [{
|
|
942
|
-
...state,
|
|
943
|
-
query,
|
|
944
|
-
cursorPosition,
|
|
945
|
-
matches,
|
|
946
|
-
currentMatch
|
|
947
|
-
}, effects];
|
|
692
|
+
if (meta && meta.lastContentCol >= 0) colEnd = Math.min(colEnd, meta.lastContentCol);
|
|
693
|
+
let line = "";
|
|
694
|
+
for (let col = colStart; col <= colEnd; col++) {
|
|
695
|
+
if (buffer.isCellContinuation(col, row)) continue;
|
|
696
|
+
if (respectSelectable && !buffer.isCellSelectable(col, row)) continue;
|
|
697
|
+
line += buffer.getCellChar(col, row);
|
|
948
698
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
if (currentMatch >= 0) effects.push({
|
|
957
|
-
type: "scrollTo",
|
|
958
|
-
row: matches[0].row
|
|
959
|
-
});
|
|
960
|
-
return [{
|
|
961
|
-
...state,
|
|
962
|
-
query,
|
|
963
|
-
cursorPosition,
|
|
964
|
-
matches,
|
|
965
|
-
currentMatch
|
|
966
|
-
}, effects];
|
|
699
|
+
if (!meta || meta.lastContentCol < 0) line = line.replace(/\s+$/, "");
|
|
700
|
+
if (meta?.softWrapped && row < endRow) {
|
|
701
|
+
parts.push(line);
|
|
702
|
+
if (meta.wrapJoinSpace) parts.push(" ");
|
|
703
|
+
} else {
|
|
704
|
+
parts.push(line);
|
|
705
|
+
if (row < endRow) parts.push("\n");
|
|
967
706
|
}
|
|
968
707
|
}
|
|
708
|
+
return parts.join("");
|
|
969
709
|
}
|
|
710
|
+
//#endregion
|
|
711
|
+
//#region packages/ag-term/src/drag-events.ts
|
|
970
712
|
/**
|
|
971
|
-
*
|
|
972
|
-
* Format: " / query [2/15]" or " / query [no matches]" — padded with spaces.
|
|
973
|
-
*
|
|
974
|
-
* Caller decides how to display it — wrap with `\x1b[7m...\x1b[27m` for the
|
|
975
|
-
* legacy overlay path, or stamp into a buffer's cells with the inverse
|
|
976
|
-
* attribute for the compose+apply path
|
|
977
|
-
* (see `applySearchBarToPaintBuffer` in `runtime/renderer.ts`).
|
|
713
|
+
* Create a DragEvent payload.
|
|
978
714
|
*/
|
|
979
|
-
function
|
|
980
|
-
const prefix = " / ";
|
|
981
|
-
const matchInfo = state.matches.length > 0 ? ` [${state.currentMatch + 1}/${state.matches.length}]` : state.query ? " [no matches]" : "";
|
|
982
|
-
return (prefix + state.query + matchInfo).padEnd(cols);
|
|
983
|
-
}
|
|
984
|
-
//#endregion
|
|
985
|
-
//#region packages/ag-term/src/pane-manager.ts
|
|
986
|
-
/** Create a single-pane layout */
|
|
987
|
-
function createLeaf(id) {
|
|
715
|
+
function createDragEvent(source, position, dropTarget) {
|
|
988
716
|
return {
|
|
989
|
-
|
|
990
|
-
|
|
717
|
+
source,
|
|
718
|
+
position,
|
|
719
|
+
dropTarget
|
|
991
720
|
};
|
|
992
721
|
}
|
|
993
|
-
/** Get all leaf pane IDs in depth-first left-to-right order */
|
|
994
|
-
function getPaneIds(layout) {
|
|
995
|
-
if (layout.type === "leaf") return [layout.id];
|
|
996
|
-
return [...getPaneIds(layout.first), ...getPaneIds(layout.second)];
|
|
997
|
-
}
|
|
998
|
-
/** Find the next/previous pane in tab order (depth-first left-to-right) */
|
|
999
|
-
function getTabOrder(layout) {
|
|
1000
|
-
return getPaneIds(layout);
|
|
1001
|
-
}
|
|
1002
722
|
/**
|
|
1003
|
-
*
|
|
1004
|
-
* The original pane becomes the first child; the new pane becomes the second.
|
|
723
|
+
* Create initial DragState when a drag starts.
|
|
1005
724
|
*/
|
|
1006
|
-
function
|
|
1007
|
-
const clampedRatio = clampRatio(ratio);
|
|
1008
|
-
if (layout.type === "leaf") {
|
|
1009
|
-
if (layout.id === targetPaneId) return {
|
|
1010
|
-
type: "split",
|
|
1011
|
-
direction,
|
|
1012
|
-
ratio: clampedRatio,
|
|
1013
|
-
first: {
|
|
1014
|
-
type: "leaf",
|
|
1015
|
-
id: targetPaneId
|
|
1016
|
-
},
|
|
1017
|
-
second: {
|
|
1018
|
-
type: "leaf",
|
|
1019
|
-
id: newPaneId
|
|
1020
|
-
}
|
|
1021
|
-
};
|
|
1022
|
-
return layout;
|
|
1023
|
-
}
|
|
1024
|
-
const newFirst = splitPane(layout.first, targetPaneId, direction, newPaneId, ratio);
|
|
1025
|
-
const newSecond = splitPane(layout.second, targetPaneId, direction, newPaneId, ratio);
|
|
1026
|
-
if (newFirst === layout.first && newSecond === layout.second) return layout;
|
|
725
|
+
function createDragState(source, startPos) {
|
|
1027
726
|
return {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
727
|
+
active: true,
|
|
728
|
+
source,
|
|
729
|
+
startPos,
|
|
730
|
+
currentPos: startPos,
|
|
731
|
+
dropTarget: null
|
|
1031
732
|
};
|
|
1032
733
|
}
|
|
1033
734
|
/**
|
|
1034
|
-
*
|
|
1035
|
-
* Returns null if the removed pane was the last one.
|
|
735
|
+
* Check if a node accepts drops (has any drag event handler).
|
|
1036
736
|
*/
|
|
1037
|
-
function
|
|
1038
|
-
if (
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
const newFirst = removePane(layout.first, paneId);
|
|
1042
|
-
const newSecond = removePane(layout.second, paneId);
|
|
1043
|
-
if (newFirst === null) return newSecond;
|
|
1044
|
-
if (newSecond === null) return newFirst;
|
|
1045
|
-
if (newFirst === layout.first && newSecond === layout.second) return layout;
|
|
1046
|
-
return {
|
|
1047
|
-
...layout,
|
|
1048
|
-
first: newFirst,
|
|
1049
|
-
second: newSecond
|
|
1050
|
-
};
|
|
1051
|
-
}
|
|
1052
|
-
/** Swap two panes' positions in the layout */
|
|
1053
|
-
function swapPanes(layout, paneId1, paneId2) {
|
|
1054
|
-
if (layout.type === "leaf") {
|
|
1055
|
-
if (layout.id === paneId1) return {
|
|
1056
|
-
type: "leaf",
|
|
1057
|
-
id: paneId2
|
|
1058
|
-
};
|
|
1059
|
-
if (layout.id === paneId2) return {
|
|
1060
|
-
type: "leaf",
|
|
1061
|
-
id: paneId1
|
|
1062
|
-
};
|
|
1063
|
-
return layout;
|
|
1064
|
-
}
|
|
1065
|
-
const newFirst = swapPanes(layout.first, paneId1, paneId2);
|
|
1066
|
-
const newSecond = swapPanes(layout.second, paneId1, paneId2);
|
|
1067
|
-
if (newFirst === layout.first && newSecond === layout.second) return layout;
|
|
1068
|
-
return {
|
|
1069
|
-
...layout,
|
|
1070
|
-
first: newFirst,
|
|
1071
|
-
second: newSecond
|
|
1072
|
-
};
|
|
737
|
+
function isDropTarget(node) {
|
|
738
|
+
if (!node) return false;
|
|
739
|
+
const props = node.props;
|
|
740
|
+
return !!(props.onDragEnter || props.onDragLeave || props.onDragOver || props.onDrop);
|
|
1073
741
|
}
|
|
1074
742
|
/**
|
|
1075
|
-
*
|
|
1076
|
-
*
|
|
1077
|
-
* Positive delta = grow (first child gets more), negative = shrink.
|
|
1078
|
-
*/
|
|
1079
|
-
function resizeSplit(layout, paneId, delta) {
|
|
1080
|
-
if (layout.type === "leaf") return layout;
|
|
1081
|
-
if (getPaneIds(layout.first).includes(paneId)) {
|
|
1082
|
-
const newRatio = clampRatio(layout.ratio + delta);
|
|
1083
|
-
if (newRatio === layout.ratio) return layout;
|
|
1084
|
-
return {
|
|
1085
|
-
...layout,
|
|
1086
|
-
ratio: newRatio
|
|
1087
|
-
};
|
|
1088
|
-
}
|
|
1089
|
-
if (getPaneIds(layout.second).includes(paneId)) {
|
|
1090
|
-
const newRatio = clampRatio(layout.ratio - delta);
|
|
1091
|
-
if (newRatio === layout.ratio) return layout;
|
|
1092
|
-
return {
|
|
1093
|
-
...layout,
|
|
1094
|
-
ratio: newRatio
|
|
1095
|
-
};
|
|
1096
|
-
}
|
|
1097
|
-
return layout;
|
|
1098
|
-
}
|
|
1099
|
-
/**
|
|
1100
|
-
* Find the pane adjacent to the given pane in a direction.
|
|
1101
|
-
*
|
|
1102
|
-
* For left/right: looks for siblings in horizontal splits.
|
|
1103
|
-
* For up/down: looks for siblings in vertical splits.
|
|
1104
|
-
*
|
|
1105
|
-
* Returns null if no adjacent pane exists in that direction.
|
|
743
|
+
* Find the nearest ancestor (including self) that is a drop target.
|
|
744
|
+
* Returns null if no ancestor accepts drops.
|
|
1106
745
|
*/
|
|
1107
|
-
function
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
for (let i = path.length - 1; i >= 0; i--) {
|
|
1113
|
-
const step = path[i];
|
|
1114
|
-
if (step.node.type !== "split") continue;
|
|
1115
|
-
if (step.node.direction !== splitDirection) continue;
|
|
1116
|
-
if (step.side === "first" && goToSecond) return firstLeaf(step.node.second);
|
|
1117
|
-
if (step.side === "second" && !goToSecond) return lastLeaf(step.node.first);
|
|
746
|
+
function findDropTarget(node) {
|
|
747
|
+
let current = node;
|
|
748
|
+
while (current) {
|
|
749
|
+
if (isDropTarget(current)) return current;
|
|
750
|
+
current = current.parent;
|
|
1118
751
|
}
|
|
1119
752
|
return null;
|
|
1120
753
|
}
|
|
1121
|
-
function clampRatio(ratio) {
|
|
1122
|
-
return Math.max(.1, Math.min(.9, ratio));
|
|
1123
|
-
}
|
|
1124
|
-
/** Get the first (leftmost/topmost) leaf ID */
|
|
1125
|
-
function firstLeaf(node) {
|
|
1126
|
-
if (node.type === "leaf") return node.id;
|
|
1127
|
-
return firstLeaf(node.first);
|
|
1128
|
-
}
|
|
1129
|
-
/** Get the last (rightmost/bottommost) leaf ID */
|
|
1130
|
-
function lastLeaf(node) {
|
|
1131
|
-
if (node.type === "leaf") return node.id;
|
|
1132
|
-
return lastLeaf(node.second);
|
|
1133
|
-
}
|
|
1134
|
-
/** Find the path from root to a leaf, recording which side we took at each split */
|
|
1135
|
-
function findPath(layout, paneId) {
|
|
1136
|
-
if (layout.type === "leaf") return layout.id === paneId ? [] : null;
|
|
1137
|
-
const firstPath = findPath(layout.first, paneId);
|
|
1138
|
-
if (firstPath !== null) return [{
|
|
1139
|
-
node: layout,
|
|
1140
|
-
side: "first"
|
|
1141
|
-
}, ...firstPath];
|
|
1142
|
-
const secondPath = findPath(layout.second, paneId);
|
|
1143
|
-
if (secondPath !== null) return [{
|
|
1144
|
-
node: layout,
|
|
1145
|
-
side: "second"
|
|
1146
|
-
}, ...secondPath];
|
|
1147
|
-
return null;
|
|
1148
|
-
}
|
|
1149
|
-
//#endregion
|
|
1150
|
-
//#region packages/ag-term/src/scroll-region.ts
|
|
1151
|
-
/**
|
|
1152
|
-
* Terminal scroll region (DECSTBM) utilities.
|
|
1153
|
-
*
|
|
1154
|
-
* Scroll regions tell the terminal to natively scroll content within
|
|
1155
|
-
* a defined row range, which is faster than re-rendering all cells.
|
|
1156
|
-
*
|
|
1157
|
-
* DECSTBM (DEC Set Top and Bottom Margins) is supported by most modern
|
|
1158
|
-
* terminal emulators: xterm, iTerm2, Kitty, Ghostty, WezTerm, etc.
|
|
1159
|
-
*/
|
|
1160
|
-
const ESC = "\x1B";
|
|
1161
|
-
/** Set terminal scroll region (1-indexed top and bottom rows). */
|
|
1162
|
-
function setScrollRegion(stdout, top, bottom) {
|
|
1163
|
-
stdout.write(`${ESC}[${top};${bottom}r`);
|
|
1164
|
-
}
|
|
1165
|
-
/** Reset scroll region to full terminal. */
|
|
1166
|
-
function resetScrollRegion(stdout) {
|
|
1167
|
-
stdout.write(`${ESC}[r`);
|
|
1168
|
-
}
|
|
1169
|
-
/** Scroll content up by N lines within the current scroll region. */
|
|
1170
|
-
function scrollUp(stdout, lines = 1) {
|
|
1171
|
-
stdout.write(`${ESC}[${lines}S`);
|
|
1172
|
-
}
|
|
1173
|
-
/** Scroll content down by N lines within the current scroll region. */
|
|
1174
|
-
function scrollDown(stdout, lines = 1) {
|
|
1175
|
-
stdout.write(`${ESC}[${lines}T`);
|
|
1176
|
-
}
|
|
1177
|
-
/** Move cursor to a specific position (1-indexed row and column). */
|
|
1178
|
-
function moveCursor(stdout, row, col) {
|
|
1179
|
-
stdout.write(`${ESC}[${row};${col}H`);
|
|
1180
|
-
}
|
|
1181
|
-
/**
|
|
1182
|
-
* Detect if the terminal likely supports DECSTBM scroll regions.
|
|
1183
|
-
*
|
|
1184
|
-
* Most modern terminals do (xterm, iTerm2, Kitty, Ghostty, WezTerm, etc.)
|
|
1185
|
-
* but some (e.g., Linux console) may not handle them correctly.
|
|
1186
|
-
*
|
|
1187
|
-
* Pass `emulator` (via `term.emulator` or a test fixture) when in scope.
|
|
1188
|
-
* Without it, this falls back to {@link createTerminalProfile} — the canonical
|
|
1189
|
-
* single-source-of-truth entry point in `@silvery/ansi/profile`. Direct
|
|
1190
|
-
* reads of terminal-signal env vars (TERM / TERM_PROGRAM / …) are banned
|
|
1191
|
-
* outside that module — see `scripts/lint-env-reads.ts`.
|
|
1192
|
-
*/
|
|
1193
|
-
function supportsScrollRegions(emulator) {
|
|
1194
|
-
const resolved = emulator ?? createTerminalProfile().emulator;
|
|
1195
|
-
const term = resolved.TERM;
|
|
1196
|
-
const termProgram = resolved.program;
|
|
1197
|
-
if (termProgram === "iTerm.app" || termProgram === "WezTerm" || termProgram === "ghostty" || termProgram === "Ghostty" || termProgram === "vscode") return true;
|
|
1198
|
-
if (term.includes("xterm") || term.includes("screen") || term.includes("tmux") || term.includes("kitty")) return true;
|
|
1199
|
-
if (term === "linux") return false;
|
|
1200
|
-
return term !== "";
|
|
1201
|
-
}
|
|
1202
|
-
//#endregion
|
|
1203
|
-
//#region packages/ag-term/src/scroll-utils.ts
|
|
1204
|
-
/**
|
|
1205
|
-
* Scroll Utilities
|
|
1206
|
-
*
|
|
1207
|
-
* Shared functions for edge-based scrolling behavior across VirtualList,
|
|
1208
|
-
* HorizontalVirtualList, and other scroll-aware components.
|
|
1209
|
-
*/
|
|
1210
|
-
/**
|
|
1211
|
-
* Calculate edge-based scroll offset.
|
|
1212
|
-
*
|
|
1213
|
-
* Only scrolls when cursor approaches the edge of the visible area.
|
|
1214
|
-
* This provides smoother scrolling by starting to scroll before hitting
|
|
1215
|
-
* the absolute edge, maintaining context around the selected item.
|
|
1216
|
-
*
|
|
1217
|
-
* ## Algorithm
|
|
1218
|
-
*
|
|
1219
|
-
* The viewport is divided into zones:
|
|
1220
|
-
* ```
|
|
1221
|
-
* |<padding>|<------ safe zone ------>|<padding>|
|
|
1222
|
-
* | scroll | no scroll needed | scroll |
|
|
1223
|
-
* | if < | | if > |
|
|
1224
|
-
* ```
|
|
1225
|
-
*
|
|
1226
|
-
* When the selected item enters a padding zone, the viewport scrolls
|
|
1227
|
-
* to keep the item visible with margin.
|
|
1228
|
-
*
|
|
1229
|
-
* ## Asymmetry Note
|
|
1230
|
-
*
|
|
1231
|
-
* The +1 in the "scroll down/right" case is intentional:
|
|
1232
|
-
* - Offset points to the TOP/LEFT of the viewport
|
|
1233
|
-
* - We want the selected item to be `padding` items from the BOTTOM/RIGHT
|
|
1234
|
-
* - Formula: `selectedIndex - visibleCount + padding + 1`
|
|
1235
|
-
*
|
|
1236
|
-
* Example: visibleCount=10, padding=2, selectedIndex=15
|
|
1237
|
-
* offset = 15 - 10 + 2 + 1 = 8
|
|
1238
|
-
* viewport shows items 8-17, selected item 15 is at position 7 (2 from bottom)
|
|
1239
|
-
*
|
|
1240
|
-
* @param selectedIndex - Currently selected item index
|
|
1241
|
-
* @param currentOffset - Current scroll offset (topmost/leftmost visible item)
|
|
1242
|
-
* @param visibleCount - Number of items visible in viewport
|
|
1243
|
-
* @param totalCount - Total number of items
|
|
1244
|
-
* @param padding - Items to keep visible before/after cursor (default: 1)
|
|
1245
|
-
* @returns New scroll offset
|
|
1246
|
-
*/
|
|
1247
|
-
function calcEdgeBasedScrollOffset(selectedIndex, currentOffset, visibleCount, totalCount, padding = 1) {
|
|
1248
|
-
if (totalCount <= visibleCount) return 0;
|
|
1249
|
-
const effectivePadding = padding * 2 >= visibleCount ? 0 : padding;
|
|
1250
|
-
const visibleStart = currentOffset;
|
|
1251
|
-
const visibleEnd = currentOffset + visibleCount - 1;
|
|
1252
|
-
const paddedStart = visibleStart + effectivePadding;
|
|
1253
|
-
const paddedEnd = visibleEnd - effectivePadding;
|
|
1254
|
-
let newOffset = currentOffset;
|
|
1255
|
-
if (selectedIndex < paddedStart) newOffset = Math.max(0, selectedIndex - effectivePadding);
|
|
1256
|
-
else if (effectivePadding === 0 && selectedIndex === paddedStart && currentOffset > 0 && visibleCount > padding) newOffset = Math.max(0, selectedIndex - padding);
|
|
1257
|
-
else if (selectedIndex > paddedEnd) newOffset = Math.min(totalCount - visibleCount, selectedIndex - visibleCount + effectivePadding + 1);
|
|
1258
|
-
return Math.max(0, Math.min(newOffset, totalCount - visibleCount));
|
|
1259
|
-
}
|
|
1260
754
|
//#endregion
|
|
1261
755
|
//#region packages/ag-react/src/hooks/useCursor.ts
|
|
1262
756
|
/**
|
|
@@ -1445,135 +939,8 @@ function useCursor(position) {
|
|
|
1445
939
|
};
|
|
1446
940
|
}, []);
|
|
1447
941
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
/**
|
|
1451
|
-
* Registry for managing hit regions.
|
|
1452
|
-
*
|
|
1453
|
-
* Components register their screen regions with targets, and the registry
|
|
1454
|
-
* resolves mouse clicks to the appropriate target based on position and z-index.
|
|
1455
|
-
*
|
|
1456
|
-
* @example
|
|
1457
|
-
* ```typescript
|
|
1458
|
-
* const registry = new HitRegistry();
|
|
1459
|
-
*
|
|
1460
|
-
* // Register a card region
|
|
1461
|
-
* registry.register('card-1', {
|
|
1462
|
-
* x: 10, y: 5, width: 30, height: 8,
|
|
1463
|
-
* target: { type: 'node', nodeId: 'abc123' },
|
|
1464
|
-
* zIndex: 10
|
|
1465
|
-
* });
|
|
1466
|
-
*
|
|
1467
|
-
* // Hit test a click
|
|
1468
|
-
* const target = registry.hitTest(15, 7);
|
|
1469
|
-
* // Returns { type: 'node', nodeId: 'abc123' }
|
|
1470
|
-
* ```
|
|
1471
|
-
*/
|
|
1472
|
-
var HitRegistry = class {
|
|
1473
|
-
regions = /* @__PURE__ */ new Map();
|
|
1474
|
-
/**
|
|
1475
|
-
* Register a hit region with a unique ID.
|
|
1476
|
-
*
|
|
1477
|
-
* @param id - Unique identifier for the region (used for unregistration)
|
|
1478
|
-
* @param region - The region definition including position, size, target, and z-index
|
|
1479
|
-
*/
|
|
1480
|
-
register(id, region) {
|
|
1481
|
-
this.regions.set(id, region);
|
|
1482
|
-
}
|
|
1483
|
-
/**
|
|
1484
|
-
* Unregister a hit region by ID.
|
|
1485
|
-
*
|
|
1486
|
-
* @param id - The ID used when registering the region
|
|
1487
|
-
*/
|
|
1488
|
-
unregister(id) {
|
|
1489
|
-
this.regions.delete(id);
|
|
1490
|
-
}
|
|
1491
|
-
/**
|
|
1492
|
-
* Clear all registered regions.
|
|
1493
|
-
* Useful when the UI is completely redrawn.
|
|
1494
|
-
*/
|
|
1495
|
-
clear() {
|
|
1496
|
-
this.regions.clear();
|
|
1497
|
-
}
|
|
1498
|
-
/**
|
|
1499
|
-
* Get the number of registered regions.
|
|
1500
|
-
* Useful for debugging.
|
|
1501
|
-
*/
|
|
1502
|
-
get size() {
|
|
1503
|
-
return this.regions.size;
|
|
1504
|
-
}
|
|
1505
|
-
/**
|
|
1506
|
-
* Test a screen position and return the highest z-index matching target.
|
|
1507
|
-
*
|
|
1508
|
-
* @param screenX - X position on screen (0-indexed column)
|
|
1509
|
-
* @param screenY - Y position on screen (0-indexed row)
|
|
1510
|
-
* @returns The target of the highest z-index region containing the point, or null if none
|
|
1511
|
-
*/
|
|
1512
|
-
hitTest(screenX, screenY) {
|
|
1513
|
-
let bestMatch = null;
|
|
1514
|
-
for (const region of this.regions.values()) if (screenX >= region.x && screenX < region.x + region.width && screenY >= region.y && screenY < region.y + region.height) {
|
|
1515
|
-
if (!bestMatch || region.zIndex > bestMatch.zIndex) bestMatch = region;
|
|
1516
|
-
}
|
|
1517
|
-
return bestMatch?.target ?? null;
|
|
1518
|
-
}
|
|
1519
|
-
/**
|
|
1520
|
-
* Get all regions that contain a point, sorted by z-index (highest first).
|
|
1521
|
-
* Useful for debugging or when you need to know all overlapping elements.
|
|
1522
|
-
*
|
|
1523
|
-
* @param screenX - X position on screen (0-indexed column)
|
|
1524
|
-
* @param screenY - Y position on screen (0-indexed row)
|
|
1525
|
-
* @returns Array of matching regions, sorted by z-index descending
|
|
1526
|
-
*/
|
|
1527
|
-
hitTestAll(screenX, screenY) {
|
|
1528
|
-
const matches = [];
|
|
1529
|
-
for (const region of this.regions.values()) if (screenX >= region.x && screenX < region.x + region.width && screenY >= region.y && screenY < region.y + region.height) matches.push(region);
|
|
1530
|
-
return matches.sort((a, b) => b.zIndex - a.zIndex);
|
|
1531
|
-
}
|
|
1532
|
-
/**
|
|
1533
|
-
* Debug helper: get all registered regions.
|
|
1534
|
-
*/
|
|
1535
|
-
getAllRegions() {
|
|
1536
|
-
return new Map(this.regions);
|
|
1537
|
-
}
|
|
1538
|
-
};
|
|
1539
|
-
/**
|
|
1540
|
-
* Generate a unique ID for hit region registration.
|
|
1541
|
-
*/
|
|
1542
|
-
let hitRegionIdCounter = 0;
|
|
1543
|
-
function generateHitRegionId() {
|
|
1544
|
-
return `hit-${++hitRegionIdCounter}`;
|
|
1545
|
-
}
|
|
1546
|
-
/**
|
|
1547
|
-
* Reset the ID counter (useful for testing).
|
|
1548
|
-
*/
|
|
1549
|
-
function resetHitRegionIdCounter() {
|
|
1550
|
-
hitRegionIdCounter = 0;
|
|
1551
|
-
}
|
|
1552
|
-
/**
|
|
1553
|
-
* Recommended z-index values for different UI layers.
|
|
1554
|
-
*/
|
|
1555
|
-
const Z_INDEX = {
|
|
1556
|
-
/** Background elements */
|
|
1557
|
-
BACKGROUND: 0,
|
|
1558
|
-
/** Column headers */
|
|
1559
|
-
COLUMN_HEADER: 5,
|
|
1560
|
-
/** Cards in the main view */
|
|
1561
|
-
CARD: 10,
|
|
1562
|
-
/** Fold toggles (above cards for easier clicking) */
|
|
1563
|
-
FOLD_TOGGLE: 15,
|
|
1564
|
-
/** Links within cards */
|
|
1565
|
-
LINK: 20,
|
|
1566
|
-
/** Floating elements */
|
|
1567
|
-
FLOATING: 50,
|
|
1568
|
-
/** Modal dialogs */
|
|
1569
|
-
DIALOG: 100,
|
|
1570
|
-
/** Dropdown menus */
|
|
1571
|
-
DROPDOWN: 150,
|
|
1572
|
-
/** Tooltips */
|
|
1573
|
-
TOOLTIP: 200
|
|
1574
|
-
};
|
|
1575
|
-
createLogger("silvery:render");
|
|
1576
|
-
createLogger("@silvery/ag-react");
|
|
942
|
+
createLogger("silvery:render");
|
|
943
|
+
createLogger("@silvery/ag-react");
|
|
1577
944
|
//#endregion
|
|
1578
945
|
//#region packages/ag-term/src/adapters/canvas-adapter.ts
|
|
1579
946
|
const DEFAULT_CONFIG$1 = {
|
|
@@ -2216,23 +1583,13 @@ function findActiveCursorNode(root) {
|
|
|
2216
1583
|
* Supported by: iTerm2, Kitty, WezTerm, foot, Ghostty
|
|
2217
1584
|
*/
|
|
2218
1585
|
const OSC133 = {
|
|
2219
|
-
/** Mark prompt start (before user input) */
|
|
2220
1586
|
promptStart: "\x1B]133;A\x07",
|
|
2221
|
-
/** Mark prompt end (after user input, before command output) */
|
|
2222
1587
|
promptEnd: "\x1B]133;B\x07",
|
|
2223
|
-
/** Mark command output start */
|
|
2224
1588
|
commandStart: "\x1B]133;C\x07",
|
|
2225
|
-
/** Mark command output end (exit code defaults to 0 = success) */
|
|
2226
1589
|
commandEnd: (exitCode) => `\x1b]133;D;${exitCode ?? 0}\x07`
|
|
2227
1590
|
};
|
|
2228
1591
|
//#endregion
|
|
2229
1592
|
//#region packages/ag-term/src/kitty-detect.ts
|
|
2230
|
-
/**
|
|
2231
|
-
* Kitty keyboard protocol detection.
|
|
2232
|
-
*
|
|
2233
|
-
* Sends CSI ? u and parses the response to determine whether the terminal
|
|
2234
|
-
* supports the Kitty keyboard protocol and which flags it reports.
|
|
2235
|
-
*/
|
|
2236
1593
|
/** Regex to match a Kitty keyboard query response: CSI ? <flags> u */
|
|
2237
1594
|
const KITTY_RESPONSE_RE = /\x1b\[\?(\d+)u/;
|
|
2238
1595
|
/**
|
|
@@ -2253,16 +1610,45 @@ async function detectKittySupport(write, read, timeoutMs = 200) {
|
|
|
2253
1610
|
supported: false,
|
|
2254
1611
|
flags: 0
|
|
2255
1612
|
};
|
|
2256
|
-
const
|
|
2257
|
-
if (!
|
|
1613
|
+
const parsed = parseKittyResponse(data);
|
|
1614
|
+
if (!parsed) return {
|
|
2258
1615
|
supported: false,
|
|
2259
1616
|
flags: 0,
|
|
2260
1617
|
buffered: data
|
|
2261
1618
|
};
|
|
1619
|
+
const buffered = data.slice(0, parsed.start) + data.slice(parsed.end);
|
|
1620
|
+
return {
|
|
1621
|
+
...parsed.result,
|
|
1622
|
+
buffered: buffered || void 0
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
/** Query Kitty support through the session's canonical stdin owner. */
|
|
1626
|
+
async function detectKittyWithProbe(input, timeoutMs = 200) {
|
|
1627
|
+
return await input.probe({
|
|
1628
|
+
query: queryKittyKeyboard(),
|
|
1629
|
+
timeoutMs,
|
|
1630
|
+
parse: (acc) => {
|
|
1631
|
+
const parsed = parseKittyResponse(acc);
|
|
1632
|
+
return parsed ? {
|
|
1633
|
+
result: parsed.result,
|
|
1634
|
+
consumed: parsed.end
|
|
1635
|
+
} : null;
|
|
1636
|
+
}
|
|
1637
|
+
}) ?? {
|
|
1638
|
+
supported: false,
|
|
1639
|
+
flags: 0
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
function parseKittyResponse(data) {
|
|
1643
|
+
const match = KITTY_RESPONSE_RE.exec(data);
|
|
1644
|
+
if (!match) return null;
|
|
2262
1645
|
return {
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
1646
|
+
result: {
|
|
1647
|
+
supported: true,
|
|
1648
|
+
flags: parseInt(match[1], 10)
|
|
1649
|
+
},
|
|
1650
|
+
start: match.index,
|
|
1651
|
+
end: match.index + match[0].length
|
|
2266
1652
|
};
|
|
2267
1653
|
}
|
|
2268
1654
|
/**
|
|
@@ -2563,41 +1949,11 @@ async function queryCursorFromStdio(stdout, stdin, timeoutMs = 200) {
|
|
|
2563
1949
|
}
|
|
2564
1950
|
//#endregion
|
|
2565
1951
|
//#region packages/ag-term/src/ansi/width-detection.ts
|
|
2566
|
-
/**
|
|
2567
|
-
* DEC Width Mode Detection (xterm patch #407)
|
|
2568
|
-
*
|
|
2569
|
-
* Queries the terminal for its character width settings using DECRQM
|
|
2570
|
-
* (DEC Private Mode Request). This replaces guesswork with definitive
|
|
2571
|
-
* answers from the terminal itself.
|
|
2572
|
-
*
|
|
2573
|
-
* Modes:
|
|
2574
|
-
* - 1020: UTF-8 mode
|
|
2575
|
-
* - 1021: CJK ambiguous width (1 or 2 cells)
|
|
2576
|
-
* - 1022: Emoji width (1 or 2 cells)
|
|
2577
|
-
* - 1023: Private-use area width (1 or 2 cells)
|
|
2578
|
-
*
|
|
2579
|
-
* Protocol:
|
|
2580
|
-
* - Query: CSI ? {mode} $ p (DECRQM)
|
|
2581
|
-
* - Response: CSI ? {mode} ; {Ps} $ y (DECRPM)
|
|
2582
|
-
*
|
|
2583
|
-
* Where Ps is:
|
|
2584
|
-
* 1 = set (enabled / wide / 2-cell)
|
|
2585
|
-
* 2 = reset (disabled / narrow / 1-cell)
|
|
2586
|
-
* 0 = not recognized
|
|
2587
|
-
* 3 = permanently set
|
|
2588
|
-
* 4 = permanently reset
|
|
2589
|
-
*
|
|
2590
|
-
* @see https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
|
2591
|
-
*/
|
|
2592
1952
|
/** Well-known xterm width DEC mode numbers. */
|
|
2593
1953
|
const WidthMode = {
|
|
2594
|
-
/** UTF-8 mode */
|
|
2595
1954
|
UTF8: 1020,
|
|
2596
|
-
/** CJK ambiguous character width */
|
|
2597
1955
|
CJK_WIDTH: 1021,
|
|
2598
|
-
/** Emoji width */
|
|
2599
1956
|
EMOJI_WIDTH: 1022,
|
|
2600
|
-
/** Private-use area width */
|
|
2601
1957
|
PRIVATE_USE_WIDTH: 1023
|
|
2602
1958
|
};
|
|
2603
1959
|
/** Default configuration when detection fails or times out. */
|
|
@@ -2617,41 +1973,42 @@ function isSet(ps) {
|
|
|
2617
1973
|
return ps === 1 || ps === 3;
|
|
2618
1974
|
}
|
|
2619
1975
|
/**
|
|
2620
|
-
* Query
|
|
2621
|
-
*
|
|
1976
|
+
* Query width modes through the session's canonical stdin owner.
|
|
1977
|
+
*
|
|
1978
|
+
* Unlike `createWidthDetector`'s standalone transport adapter, this path
|
|
1979
|
+
* never attaches another `data` listener: responses are consumed by the
|
|
1980
|
+
* owner's probe lane before normal key/mouse parsing.
|
|
2622
1981
|
*/
|
|
2623
|
-
function
|
|
2624
|
-
return
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
write(`\x1b[?${mode}$p`);
|
|
2654
|
-
});
|
|
1982
|
+
function detectWidthConfigWithProbe(input, timeoutMs = 200) {
|
|
1983
|
+
return detectWidthConfig((mode) => input.probe({
|
|
1984
|
+
query: `\x1b[?${mode}$p`,
|
|
1985
|
+
timeoutMs,
|
|
1986
|
+
parse: (acc) => parseWidthModeResponse(acc, mode)
|
|
1987
|
+
}));
|
|
1988
|
+
}
|
|
1989
|
+
function parseWidthModeResponse(acc, expectedMode) {
|
|
1990
|
+
DECRPM_RE.lastIndex = 0;
|
|
1991
|
+
let match;
|
|
1992
|
+
while ((match = DECRPM_RE.exec(acc)) !== null) {
|
|
1993
|
+
if (parseInt(match[1], 10) !== expectedMode) continue;
|
|
1994
|
+
return {
|
|
1995
|
+
result: parseInt(match[2], 10),
|
|
1996
|
+
consumed: match.index + match[0].length
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
return null;
|
|
2000
|
+
}
|
|
2001
|
+
async function detectWidthConfig(query) {
|
|
2002
|
+
const utf8Ps = await query(WidthMode.UTF8);
|
|
2003
|
+
const cjkPs = await query(WidthMode.CJK_WIDTH);
|
|
2004
|
+
const emojiPs = await query(WidthMode.EMOJI_WIDTH);
|
|
2005
|
+
const puaPs = await query(WidthMode.PRIVATE_USE_WIDTH);
|
|
2006
|
+
return {
|
|
2007
|
+
utf8: utf8Ps !== null ? isSet(utf8Ps) : DEFAULT_WIDTH_CONFIG.utf8,
|
|
2008
|
+
cjkWidth: cjkPs !== null ? isSet(cjkPs) ? 2 : 1 : DEFAULT_WIDTH_CONFIG.cjkWidth,
|
|
2009
|
+
emojiWidth: emojiPs !== null ? isSet(emojiPs) ? 2 : 1 : DEFAULT_WIDTH_CONFIG.emojiWidth,
|
|
2010
|
+
privateUseWidth: puaPs !== null ? isSet(puaPs) ? 2 : 1 : DEFAULT_WIDTH_CONFIG.privateUseWidth
|
|
2011
|
+
};
|
|
2655
2012
|
}
|
|
2656
2013
|
/**
|
|
2657
2014
|
* Apply detected width configuration to terminal capabilities.
|
|
@@ -2672,52 +2029,6 @@ function applyWidthConfig(caps, config) {
|
|
|
2672
2029
|
maybeWideEmojis: config.emojiWidth === 2
|
|
2673
2030
|
};
|
|
2674
2031
|
}
|
|
2675
|
-
/**
|
|
2676
|
-
* Create a width detector that queries the terminal for DEC modes 1020-1023.
|
|
2677
|
-
*
|
|
2678
|
-
* @example
|
|
2679
|
-
* ```ts
|
|
2680
|
-
* const detector = createWidthDetector({
|
|
2681
|
-
* write: (data) => process.stdout.write(data),
|
|
2682
|
-
* onData: (handler) => {
|
|
2683
|
-
* process.stdin.on('data', (chunk) => handler(chunk.toString()))
|
|
2684
|
-
* return () => process.stdin.removeListener('data', handler)
|
|
2685
|
-
* },
|
|
2686
|
-
* })
|
|
2687
|
-
*
|
|
2688
|
-
* const config = await detector.detect()
|
|
2689
|
-
* console.log(config.emojiWidth) // 1 or 2
|
|
2690
|
-
* detector.dispose()
|
|
2691
|
-
* ```
|
|
2692
|
-
*/
|
|
2693
|
-
function createWidthDetector(options) {
|
|
2694
|
-
const { write, onData, timeoutMs = 200 } = options;
|
|
2695
|
-
let config = null;
|
|
2696
|
-
let disposed = false;
|
|
2697
|
-
return {
|
|
2698
|
-
get config() {
|
|
2699
|
-
return config;
|
|
2700
|
-
},
|
|
2701
|
-
async detect() {
|
|
2702
|
-
if (disposed) return config ?? { ...DEFAULT_WIDTH_CONFIG };
|
|
2703
|
-
if (config !== null) return config;
|
|
2704
|
-
const utf8Ps = await queryWidthMode(write, onData, WidthMode.UTF8, timeoutMs);
|
|
2705
|
-
const cjkPs = await queryWidthMode(write, onData, WidthMode.CJK_WIDTH, timeoutMs);
|
|
2706
|
-
const emojiPs = await queryWidthMode(write, onData, WidthMode.EMOJI_WIDTH, timeoutMs);
|
|
2707
|
-
const puaPs = await queryWidthMode(write, onData, WidthMode.PRIVATE_USE_WIDTH, timeoutMs);
|
|
2708
|
-
config = {
|
|
2709
|
-
utf8: utf8Ps !== null ? isSet(utf8Ps) : DEFAULT_WIDTH_CONFIG.utf8,
|
|
2710
|
-
cjkWidth: cjkPs !== null ? isSet(cjkPs) ? 2 : 1 : DEFAULT_WIDTH_CONFIG.cjkWidth,
|
|
2711
|
-
emojiWidth: emojiPs !== null ? isSet(emojiPs) ? 2 : 1 : DEFAULT_WIDTH_CONFIG.emojiWidth,
|
|
2712
|
-
privateUseWidth: puaPs !== null ? isSet(puaPs) ? 2 : 1 : DEFAULT_WIDTH_CONFIG.privateUseWidth
|
|
2713
|
-
};
|
|
2714
|
-
return config;
|
|
2715
|
-
},
|
|
2716
|
-
dispose() {
|
|
2717
|
-
disposed = true;
|
|
2718
|
-
}
|
|
2719
|
-
};
|
|
2720
|
-
}
|
|
2721
2032
|
//#endregion
|
|
2722
2033
|
//#region packages/ag-term/src/device-attrs.ts
|
|
2723
2034
|
/**
|
|
@@ -2903,17 +2214,11 @@ async function queryDeviceAttributes(stdout, stdin, timeoutMs = 200) {
|
|
|
2903
2214
|
const DECRPM_RESPONSE_RE = /\x1b\[\?(\d+);(\d+)\$y/;
|
|
2904
2215
|
/** Well-known DEC private mode constants. */
|
|
2905
2216
|
const DecMode = {
|
|
2906
|
-
/** DEC cursor visible (DECTCEM) */
|
|
2907
2217
|
CURSOR_VISIBLE: 25,
|
|
2908
|
-
/** Alternate screen buffer (DECSET 1049) */
|
|
2909
2218
|
ALT_SCREEN: 1049,
|
|
2910
|
-
/** Normal mouse tracking (X10) */
|
|
2911
2219
|
MOUSE_TRACKING: 1e3,
|
|
2912
|
-
/** Bracketed paste mode */
|
|
2913
2220
|
BRACKETED_PASTE: 2004,
|
|
2914
|
-
/** Synchronized output */
|
|
2915
2221
|
SYNC_OUTPUT: 2026,
|
|
2916
|
-
/** Focus reporting */
|
|
2917
2222
|
FOCUS_REPORTING: 1004
|
|
2918
2223
|
};
|
|
2919
2224
|
/**
|
|
@@ -3189,6 +2494,124 @@ function createInputEvents(stdin) {
|
|
|
3189
2494
|
} };
|
|
3190
2495
|
}
|
|
3191
2496
|
//#endregion
|
|
2497
|
+
//#region packages/ag-term/src/hit-registry-core.ts
|
|
2498
|
+
/**
|
|
2499
|
+
* Registry for managing hit regions.
|
|
2500
|
+
*
|
|
2501
|
+
* Components register their screen regions with targets, and the registry
|
|
2502
|
+
* resolves mouse clicks to the appropriate target based on position and z-index.
|
|
2503
|
+
*
|
|
2504
|
+
* @example
|
|
2505
|
+
* ```typescript
|
|
2506
|
+
* const registry = new HitRegistry();
|
|
2507
|
+
*
|
|
2508
|
+
* // Register a card region
|
|
2509
|
+
* registry.register('card-1', {
|
|
2510
|
+
* x: 10, y: 5, width: 30, height: 8,
|
|
2511
|
+
* target: { type: 'node', nodeId: 'abc123' },
|
|
2512
|
+
* zIndex: 10
|
|
2513
|
+
* });
|
|
2514
|
+
*
|
|
2515
|
+
* // Hit test a click
|
|
2516
|
+
* const target = registry.hitTest(15, 7);
|
|
2517
|
+
* // Returns { type: 'node', nodeId: 'abc123' }
|
|
2518
|
+
* ```
|
|
2519
|
+
*/
|
|
2520
|
+
var HitRegistry = class {
|
|
2521
|
+
regions = /* @__PURE__ */ new Map();
|
|
2522
|
+
/**
|
|
2523
|
+
* Register a hit region with a unique ID.
|
|
2524
|
+
*
|
|
2525
|
+
* @param id - Unique identifier for the region (used for unregistration)
|
|
2526
|
+
* @param region - The region definition including position, size, target, and z-index
|
|
2527
|
+
*/
|
|
2528
|
+
register(id, region) {
|
|
2529
|
+
this.regions.set(id, region);
|
|
2530
|
+
}
|
|
2531
|
+
/**
|
|
2532
|
+
* Unregister a hit region by ID.
|
|
2533
|
+
*
|
|
2534
|
+
* @param id - The ID used when registering the region
|
|
2535
|
+
*/
|
|
2536
|
+
unregister(id) {
|
|
2537
|
+
this.regions.delete(id);
|
|
2538
|
+
}
|
|
2539
|
+
/**
|
|
2540
|
+
* Clear all registered regions.
|
|
2541
|
+
* Useful when the UI is completely redrawn.
|
|
2542
|
+
*/
|
|
2543
|
+
clear() {
|
|
2544
|
+
this.regions.clear();
|
|
2545
|
+
}
|
|
2546
|
+
/**
|
|
2547
|
+
* Get the number of registered regions.
|
|
2548
|
+
* Useful for debugging.
|
|
2549
|
+
*/
|
|
2550
|
+
get size() {
|
|
2551
|
+
return this.regions.size;
|
|
2552
|
+
}
|
|
2553
|
+
/**
|
|
2554
|
+
* Test a screen position and return the highest z-index matching target.
|
|
2555
|
+
*
|
|
2556
|
+
* @param screenX - X position on screen (0-indexed column)
|
|
2557
|
+
* @param screenY - Y position on screen (0-indexed row)
|
|
2558
|
+
* @returns The target of the highest z-index region containing the point, or null if none
|
|
2559
|
+
*/
|
|
2560
|
+
hitTest(screenX, screenY) {
|
|
2561
|
+
let bestMatch = null;
|
|
2562
|
+
for (const region of this.regions.values()) if (screenX >= region.x && screenX < region.x + region.width && screenY >= region.y && screenY < region.y + region.height) {
|
|
2563
|
+
if (!bestMatch || region.zIndex > bestMatch.zIndex) bestMatch = region;
|
|
2564
|
+
}
|
|
2565
|
+
return bestMatch?.target ?? null;
|
|
2566
|
+
}
|
|
2567
|
+
/**
|
|
2568
|
+
* Get all regions that contain a point, sorted by z-index (highest first).
|
|
2569
|
+
* Useful for debugging or when you need to know all overlapping elements.
|
|
2570
|
+
*
|
|
2571
|
+
* @param screenX - X position on screen (0-indexed column)
|
|
2572
|
+
* @param screenY - Y position on screen (0-indexed row)
|
|
2573
|
+
* @returns Array of matching regions, sorted by z-index descending
|
|
2574
|
+
*/
|
|
2575
|
+
hitTestAll(screenX, screenY) {
|
|
2576
|
+
const matches = [];
|
|
2577
|
+
for (const region of this.regions.values()) if (screenX >= region.x && screenX < region.x + region.width && screenY >= region.y && screenY < region.y + region.height) matches.push(region);
|
|
2578
|
+
return matches.sort((a, b) => b.zIndex - a.zIndex);
|
|
2579
|
+
}
|
|
2580
|
+
/**
|
|
2581
|
+
* Debug helper: get all registered regions.
|
|
2582
|
+
*/
|
|
2583
|
+
getAllRegions() {
|
|
2584
|
+
return new Map(this.regions);
|
|
2585
|
+
}
|
|
2586
|
+
};
|
|
2587
|
+
/**
|
|
2588
|
+
* Generate a unique ID for hit region registration.
|
|
2589
|
+
*/
|
|
2590
|
+
let hitRegionIdCounter = 0;
|
|
2591
|
+
function generateHitRegionId() {
|
|
2592
|
+
return `hit-${++hitRegionIdCounter}`;
|
|
2593
|
+
}
|
|
2594
|
+
/**
|
|
2595
|
+
* Reset the ID counter (useful for testing).
|
|
2596
|
+
*/
|
|
2597
|
+
function resetHitRegionIdCounter() {
|
|
2598
|
+
hitRegionIdCounter = 0;
|
|
2599
|
+
}
|
|
2600
|
+
/**
|
|
2601
|
+
* Recommended z-index values for different UI layers.
|
|
2602
|
+
*/
|
|
2603
|
+
const Z_INDEX = {
|
|
2604
|
+
BACKGROUND: 0,
|
|
2605
|
+
COLUMN_HEADER: 5,
|
|
2606
|
+
CARD: 10,
|
|
2607
|
+
FOLD_TOGGLE: 15,
|
|
2608
|
+
LINK: 20,
|
|
2609
|
+
FLOATING: 50,
|
|
2610
|
+
DIALOG: 100,
|
|
2611
|
+
DROPDOWN: 150,
|
|
2612
|
+
TOOLTIP: 200
|
|
2613
|
+
};
|
|
2614
|
+
//#endregion
|
|
3192
2615
|
//#region packages/ag-term/src/non-tty.ts
|
|
3193
2616
|
/**
|
|
3194
2617
|
* Non-TTY Mode Support for Silvery
|
|
@@ -3461,468 +2884,1018 @@ function createPipeline(options = {}) {
|
|
|
3461
2884
|
};
|
|
3462
2885
|
}
|
|
3463
2886
|
//#endregion
|
|
3464
|
-
//#region packages/
|
|
2887
|
+
//#region packages/ag-term/src/scroll-utils.ts
|
|
3465
2888
|
/**
|
|
3466
|
-
*
|
|
3467
|
-
*
|
|
2889
|
+
* Scroll Utilities
|
|
2890
|
+
*
|
|
2891
|
+
* Shared functions for edge-based scrolling behavior across VirtualList,
|
|
2892
|
+
* HorizontalVirtualList, and other scroll-aware components.
|
|
3468
2893
|
*/
|
|
3469
|
-
|
|
3470
|
-
|
|
2894
|
+
/**
|
|
2895
|
+
* Calculate edge-based scroll offset.
|
|
2896
|
+
*
|
|
2897
|
+
* Only scrolls when cursor approaches the edge of the visible area.
|
|
2898
|
+
* This provides smoother scrolling by starting to scroll before hitting
|
|
2899
|
+
* the absolute edge, maintaining context around the selected item.
|
|
2900
|
+
*
|
|
2901
|
+
* ## Algorithm
|
|
2902
|
+
*
|
|
2903
|
+
* The viewport is divided into zones:
|
|
2904
|
+
* ```
|
|
2905
|
+
* |<padding>|<------ safe zone ------>|<padding>|
|
|
2906
|
+
* | scroll | no scroll needed | scroll |
|
|
2907
|
+
* | if < | | if > |
|
|
2908
|
+
* ```
|
|
2909
|
+
*
|
|
2910
|
+
* When the selected item enters a padding zone, the viewport scrolls
|
|
2911
|
+
* to keep the item visible with margin.
|
|
2912
|
+
*
|
|
2913
|
+
* ## Asymmetry Note
|
|
2914
|
+
*
|
|
2915
|
+
* The +1 in the "scroll down/right" case is intentional:
|
|
2916
|
+
* - Offset points to the TOP/LEFT of the viewport
|
|
2917
|
+
* - We want the selected item to be `padding` items from the BOTTOM/RIGHT
|
|
2918
|
+
* - Formula: `selectedIndex - visibleCount + padding + 1`
|
|
2919
|
+
*
|
|
2920
|
+
* Example: visibleCount=10, padding=2, selectedIndex=15
|
|
2921
|
+
* offset = 15 - 10 + 2 + 1 = 8
|
|
2922
|
+
* viewport shows items 8-17, selected item 15 is at position 7 (2 from bottom)
|
|
2923
|
+
*
|
|
2924
|
+
* @param selectedIndex - Currently selected item index
|
|
2925
|
+
* @param currentOffset - Current scroll offset (topmost/leftmost visible item)
|
|
2926
|
+
* @param visibleCount - Number of items visible in viewport
|
|
2927
|
+
* @param totalCount - Total number of items
|
|
2928
|
+
* @param padding - Items to keep visible before/after cursor (default: 1)
|
|
2929
|
+
* @returns New scroll offset
|
|
2930
|
+
*/
|
|
2931
|
+
function calcEdgeBasedScrollOffset(selectedIndex, currentOffset, visibleCount, totalCount, padding = 1) {
|
|
2932
|
+
if (totalCount <= visibleCount) return 0;
|
|
2933
|
+
const effectivePadding = padding * 2 >= visibleCount ? 0 : padding;
|
|
2934
|
+
const visibleStart = currentOffset;
|
|
2935
|
+
const visibleEnd = currentOffset + visibleCount - 1;
|
|
2936
|
+
const paddedStart = visibleStart + effectivePadding;
|
|
2937
|
+
const paddedEnd = visibleEnd - effectivePadding;
|
|
2938
|
+
let newOffset = currentOffset;
|
|
2939
|
+
if (selectedIndex < paddedStart) newOffset = Math.max(0, selectedIndex - effectivePadding);
|
|
2940
|
+
else if (effectivePadding === 0 && selectedIndex === paddedStart && currentOffset > 0 && visibleCount > padding) newOffset = Math.max(0, selectedIndex - padding);
|
|
2941
|
+
else if (selectedIndex > paddedEnd) newOffset = Math.min(totalCount - visibleCount, selectedIndex - visibleCount + effectivePadding + 1);
|
|
2942
|
+
return Math.max(0, Math.min(newOffset, totalCount - visibleCount));
|
|
3471
2943
|
}
|
|
2944
|
+
//#endregion
|
|
2945
|
+
//#region packages/ag-term/src/scroll-region.ts
|
|
3472
2946
|
/**
|
|
3473
|
-
*
|
|
3474
|
-
*
|
|
3475
|
-
*
|
|
2947
|
+
* Terminal scroll region (DECSTBM) utilities.
|
|
2948
|
+
*
|
|
2949
|
+
* Scroll regions tell the terminal to natively scroll content within
|
|
2950
|
+
* a defined row range, which is faster than re-rendering all cells.
|
|
2951
|
+
*
|
|
2952
|
+
* DECSTBM (DEC Set Top and Bottom Margins) is supported by most modern
|
|
2953
|
+
* terminal emulators: xterm, iTerm2, Kitty, Ghostty, WezTerm, etc.
|
|
3476
2954
|
*/
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
2955
|
+
const ESC = "\x1B";
|
|
2956
|
+
/** Set terminal scroll region (1-indexed top and bottom rows). */
|
|
2957
|
+
function setScrollRegion(stdout, top, bottom) {
|
|
2958
|
+
stdout.write(`${ESC}[${top};${bottom}r`);
|
|
2959
|
+
}
|
|
2960
|
+
/** Reset scroll region to full terminal. */
|
|
2961
|
+
function resetScrollRegion(stdout) {
|
|
2962
|
+
stdout.write(`${ESC}[r`);
|
|
2963
|
+
}
|
|
2964
|
+
/** Scroll content up by N lines within the current scroll region. */
|
|
2965
|
+
function scrollUp(stdout, lines = 1) {
|
|
2966
|
+
stdout.write(`${ESC}[${lines}S`);
|
|
2967
|
+
}
|
|
2968
|
+
/** Scroll content down by N lines within the current scroll region. */
|
|
2969
|
+
function scrollDown(stdout, lines = 1) {
|
|
2970
|
+
stdout.write(`${ESC}[${lines}T`);
|
|
2971
|
+
}
|
|
2972
|
+
/** Move cursor to a specific position (1-indexed row and column). */
|
|
2973
|
+
function moveCursor(stdout, row, col) {
|
|
2974
|
+
stdout.write(`${ESC}[${row};${col}H`);
|
|
3494
2975
|
}
|
|
3495
2976
|
/**
|
|
3496
|
-
*
|
|
3497
|
-
*
|
|
3498
|
-
*
|
|
2977
|
+
* Detect if the terminal likely supports DECSTBM scroll regions.
|
|
2978
|
+
*
|
|
2979
|
+
* Most modern terminals do (xterm, iTerm2, Kitty, Ghostty, WezTerm, etc.)
|
|
2980
|
+
* but some (e.g., Linux console) may not handle them correctly.
|
|
2981
|
+
*
|
|
2982
|
+
* Pass `emulator` (via `term.emulator` or a test fixture) when in scope.
|
|
2983
|
+
* Without it, this falls back to {@link createTerminalProfile} — the canonical
|
|
2984
|
+
* single-source-of-truth entry point in `@silvery/ansi/profile`. Direct
|
|
2985
|
+
* reads of terminal-signal env vars (TERM / TERM_PROGRAM / …) are banned
|
|
2986
|
+
* outside that module — see `scripts/lint-env-reads.ts`.
|
|
3499
2987
|
*/
|
|
3500
|
-
function
|
|
3501
|
-
const
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
if (
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
2988
|
+
function supportsScrollRegions(emulator) {
|
|
2989
|
+
const resolved = emulator ?? createTerminalProfile().emulator;
|
|
2990
|
+
const term = resolved.TERM;
|
|
2991
|
+
const termProgram = resolved.program;
|
|
2992
|
+
if (termProgram === "iTerm.app" || termProgram === "WezTerm" || termProgram === "ghostty" || termProgram === "Ghostty" || termProgram === "vscode") return true;
|
|
2993
|
+
if (term.includes("xterm") || term.includes("screen") || term.includes("tmux") || term.includes("kitty")) return true;
|
|
2994
|
+
if (term === "linux") return false;
|
|
2995
|
+
return term !== "";
|
|
2996
|
+
}
|
|
2997
|
+
//#endregion
|
|
2998
|
+
//#region packages/ag-term/src/pane-manager.ts
|
|
2999
|
+
/** Create a single-pane layout */
|
|
3000
|
+
function createLeaf(id) {
|
|
3510
3001
|
return {
|
|
3511
|
-
|
|
3512
|
-
|
|
3002
|
+
type: "leaf",
|
|
3003
|
+
id
|
|
3513
3004
|
};
|
|
3514
3005
|
}
|
|
3006
|
+
/** Get all leaf pane IDs in depth-first left-to-right order */
|
|
3007
|
+
function getPaneIds(layout) {
|
|
3008
|
+
if (layout.type === "leaf") return [layout.id];
|
|
3009
|
+
return [...getPaneIds(layout.first), ...getPaneIds(layout.second)];
|
|
3010
|
+
}
|
|
3011
|
+
/** Find the next/previous pane in tab order (depth-first left-to-right) */
|
|
3012
|
+
function getTabOrder(layout) {
|
|
3013
|
+
return getPaneIds(layout);
|
|
3014
|
+
}
|
|
3515
3015
|
/**
|
|
3516
|
-
*
|
|
3517
|
-
*
|
|
3518
|
-
* For line granularity: snaps to line boundaries.
|
|
3016
|
+
* Split a pane into two. Returns new layout tree with the target pane split.
|
|
3017
|
+
* The original pane becomes the first child; the new pane becomes the second.
|
|
3519
3018
|
*/
|
|
3520
|
-
function
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
if (granularity === "line") {
|
|
3536
|
-
const { startCol, endCol } = findLineBoundary(buffer, row);
|
|
3537
|
-
return row > anchor.row || row === anchor.row && col >= anchor.col ? {
|
|
3538
|
-
col: endCol,
|
|
3539
|
-
row
|
|
3540
|
-
} : {
|
|
3541
|
-
col: startCol,
|
|
3542
|
-
row
|
|
3019
|
+
function splitPane(layout, targetPaneId, direction, newPaneId, ratio = .5) {
|
|
3020
|
+
const clampedRatio = clampRatio(ratio);
|
|
3021
|
+
if (layout.type === "leaf") {
|
|
3022
|
+
if (layout.id === targetPaneId) return {
|
|
3023
|
+
type: "split",
|
|
3024
|
+
direction,
|
|
3025
|
+
ratio: clampedRatio,
|
|
3026
|
+
first: {
|
|
3027
|
+
type: "leaf",
|
|
3028
|
+
id: targetPaneId
|
|
3029
|
+
},
|
|
3030
|
+
second: {
|
|
3031
|
+
type: "leaf",
|
|
3032
|
+
id: newPaneId
|
|
3033
|
+
}
|
|
3543
3034
|
};
|
|
3035
|
+
return layout;
|
|
3544
3036
|
}
|
|
3037
|
+
const newFirst = splitPane(layout.first, targetPaneId, direction, newPaneId, ratio);
|
|
3038
|
+
const newSecond = splitPane(layout.second, targetPaneId, direction, newPaneId, ratio);
|
|
3039
|
+
if (newFirst === layout.first && newSecond === layout.second) return layout;
|
|
3545
3040
|
return {
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
}
|
|
3550
|
-
function createTerminalSelectionState() {
|
|
3551
|
-
return {
|
|
3552
|
-
range: null,
|
|
3553
|
-
selecting: false,
|
|
3554
|
-
source: null,
|
|
3555
|
-
granularity: "character",
|
|
3556
|
-
scope: null
|
|
3041
|
+
...layout,
|
|
3042
|
+
first: newFirst,
|
|
3043
|
+
second: newSecond
|
|
3557
3044
|
};
|
|
3558
3045
|
}
|
|
3559
3046
|
/**
|
|
3560
|
-
*
|
|
3047
|
+
* Remove a pane from the layout. The sibling takes the full space.
|
|
3048
|
+
* Returns null if the removed pane was the last one.
|
|
3561
3049
|
*/
|
|
3562
|
-
function
|
|
3563
|
-
if (
|
|
3564
|
-
|
|
3565
|
-
|
|
3050
|
+
function removePane(layout, paneId) {
|
|
3051
|
+
if (layout.type === "leaf") return layout.id === paneId ? null : layout;
|
|
3052
|
+
if (layout.first.type === "leaf" && layout.first.id === paneId) return layout.second;
|
|
3053
|
+
if (layout.second.type === "leaf" && layout.second.id === paneId) return layout.first;
|
|
3054
|
+
const newFirst = removePane(layout.first, paneId);
|
|
3055
|
+
const newSecond = removePane(layout.second, paneId);
|
|
3056
|
+
if (newFirst === null) return newSecond;
|
|
3057
|
+
if (newSecond === null) return newFirst;
|
|
3058
|
+
if (newFirst === layout.first && newSecond === layout.second) return layout;
|
|
3059
|
+
return {
|
|
3060
|
+
...layout,
|
|
3061
|
+
first: newFirst,
|
|
3062
|
+
second: newSecond
|
|
3566
3063
|
};
|
|
3064
|
+
}
|
|
3065
|
+
/** Swap two panes' positions in the layout */
|
|
3066
|
+
function swapPanes(layout, paneId1, paneId2) {
|
|
3067
|
+
if (layout.type === "leaf") {
|
|
3068
|
+
if (layout.id === paneId1) return {
|
|
3069
|
+
type: "leaf",
|
|
3070
|
+
id: paneId2
|
|
3071
|
+
};
|
|
3072
|
+
if (layout.id === paneId2) return {
|
|
3073
|
+
type: "leaf",
|
|
3074
|
+
id: paneId1
|
|
3075
|
+
};
|
|
3076
|
+
return layout;
|
|
3077
|
+
}
|
|
3078
|
+
const newFirst = swapPanes(layout.first, paneId1, paneId2);
|
|
3079
|
+
const newSecond = swapPanes(layout.second, paneId1, paneId2);
|
|
3080
|
+
if (newFirst === layout.first && newSecond === layout.second) return layout;
|
|
3567
3081
|
return {
|
|
3568
|
-
|
|
3569
|
-
|
|
3082
|
+
...layout,
|
|
3083
|
+
first: newFirst,
|
|
3084
|
+
second: newSecond
|
|
3570
3085
|
};
|
|
3571
3086
|
}
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
head: clampToScope(endCol, action.row, scope)
|
|
3595
|
-
},
|
|
3596
|
-
selecting: true,
|
|
3597
|
-
source: action.source ?? "mouse",
|
|
3598
|
-
granularity: "word",
|
|
3599
|
-
scope
|
|
3600
|
-
}, [{ type: "render" }]];
|
|
3601
|
-
}
|
|
3602
|
-
case "startLine": {
|
|
3603
|
-
const scope = action.scope ?? null;
|
|
3604
|
-
const { startCol, endCol } = findLineBoundary(action.buffer, action.row);
|
|
3605
|
-
return [{
|
|
3606
|
-
range: {
|
|
3607
|
-
anchor: clampToScope(startCol, action.row, scope),
|
|
3608
|
-
head: clampToScope(endCol, action.row, scope)
|
|
3609
|
-
},
|
|
3610
|
-
selecting: true,
|
|
3611
|
-
source: action.source ?? "mouse",
|
|
3612
|
-
granularity: "line",
|
|
3613
|
-
scope
|
|
3614
|
-
}, [{ type: "render" }]];
|
|
3615
|
-
}
|
|
3616
|
-
case "extend": {
|
|
3617
|
-
if (!state.selecting) return [state, []];
|
|
3618
|
-
const scope = "scope" in action ? action.scope ?? null : state.scope;
|
|
3619
|
-
const extended = extendByGranularity(action.col, action.row, state.range.anchor, state.granularity, action.buffer);
|
|
3620
|
-
const head = clampToScope(extended.col, extended.row, scope);
|
|
3621
|
-
return [{
|
|
3622
|
-
...state,
|
|
3623
|
-
range: {
|
|
3624
|
-
anchor: state.range.anchor,
|
|
3625
|
-
head
|
|
3626
|
-
},
|
|
3627
|
-
scope,
|
|
3628
|
-
selecting: true
|
|
3629
|
-
}, [{ type: "render" }]];
|
|
3630
|
-
}
|
|
3631
|
-
case "finish":
|
|
3632
|
-
if (!state.range) return [{
|
|
3633
|
-
...state,
|
|
3634
|
-
selecting: false
|
|
3635
|
-
}, []];
|
|
3636
|
-
return [{
|
|
3637
|
-
...state,
|
|
3638
|
-
range: state.range,
|
|
3639
|
-
selecting: false
|
|
3640
|
-
}, []];
|
|
3641
|
-
case "clear": {
|
|
3642
|
-
const hadRange = state.range !== null;
|
|
3643
|
-
return [createTerminalSelectionState(), hadRange ? [{ type: "render" }] : []];
|
|
3644
|
-
}
|
|
3087
|
+
/**
|
|
3088
|
+
* Resize a split: adjust the ratio of the nearest ancestor split
|
|
3089
|
+
* that contains the target pane as its first child.
|
|
3090
|
+
* Positive delta = grow (first child gets more), negative = shrink.
|
|
3091
|
+
*/
|
|
3092
|
+
function resizeSplit(layout, paneId, delta) {
|
|
3093
|
+
if (layout.type === "leaf") return layout;
|
|
3094
|
+
if (getPaneIds(layout.first).includes(paneId)) {
|
|
3095
|
+
const newRatio = clampRatio(layout.ratio + delta);
|
|
3096
|
+
if (newRatio === layout.ratio) return layout;
|
|
3097
|
+
return {
|
|
3098
|
+
...layout,
|
|
3099
|
+
ratio: newRatio
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
3102
|
+
if (getPaneIds(layout.second).includes(paneId)) {
|
|
3103
|
+
const newRatio = clampRatio(layout.ratio - delta);
|
|
3104
|
+
if (newRatio === layout.ratio) return layout;
|
|
3105
|
+
return {
|
|
3106
|
+
...layout,
|
|
3107
|
+
ratio: newRatio
|
|
3108
|
+
};
|
|
3645
3109
|
}
|
|
3110
|
+
return layout;
|
|
3646
3111
|
}
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3112
|
+
/**
|
|
3113
|
+
* Find the pane adjacent to the given pane in a direction.
|
|
3114
|
+
*
|
|
3115
|
+
* For left/right: looks for siblings in horizontal splits.
|
|
3116
|
+
* For up/down: looks for siblings in vertical splits.
|
|
3117
|
+
*
|
|
3118
|
+
* Returns null if no adjacent pane exists in that direction.
|
|
3119
|
+
*/
|
|
3120
|
+
function findAdjacentPane(layout, paneId, direction) {
|
|
3121
|
+
const path = findPath(layout, paneId);
|
|
3122
|
+
if (!path) return null;
|
|
3123
|
+
const splitDirection = direction === "left" || direction === "right" ? "horizontal" : "vertical";
|
|
3124
|
+
const goToSecond = direction === "right" || direction === "down";
|
|
3125
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
3126
|
+
const step = path[i];
|
|
3127
|
+
if (step.node.type !== "split") continue;
|
|
3128
|
+
if (step.node.direction !== splitDirection) continue;
|
|
3129
|
+
if (step.side === "first" && goToSecond) return firstLeaf(step.node.second);
|
|
3130
|
+
if (step.side === "second" && !goToSecond) return lastLeaf(step.node.first);
|
|
3131
|
+
}
|
|
3132
|
+
return null;
|
|
3133
|
+
}
|
|
3134
|
+
function clampRatio(ratio) {
|
|
3135
|
+
return Math.max(.1, Math.min(.9, ratio));
|
|
3136
|
+
}
|
|
3137
|
+
/** Get the first (leftmost/topmost) leaf ID */
|
|
3138
|
+
function firstLeaf(node) {
|
|
3139
|
+
if (node.type === "leaf") return node.id;
|
|
3140
|
+
return firstLeaf(node.first);
|
|
3141
|
+
}
|
|
3142
|
+
/** Get the last (rightmost/bottommost) leaf ID */
|
|
3143
|
+
function lastLeaf(node) {
|
|
3144
|
+
if (node.type === "leaf") return node.id;
|
|
3145
|
+
return lastLeaf(node.second);
|
|
3146
|
+
}
|
|
3147
|
+
/** Find the path from root to a leaf, recording which side we took at each split */
|
|
3148
|
+
function findPath(layout, paneId) {
|
|
3149
|
+
if (layout.type === "leaf") return layout.id === paneId ? [] : null;
|
|
3150
|
+
const firstPath = findPath(layout.first, paneId);
|
|
3151
|
+
if (firstPath !== null) return [{
|
|
3152
|
+
node: layout,
|
|
3153
|
+
side: "first"
|
|
3154
|
+
}, ...firstPath];
|
|
3155
|
+
const secondPath = findPath(layout.second, paneId);
|
|
3156
|
+
if (secondPath !== null) return [{
|
|
3157
|
+
node: layout,
|
|
3158
|
+
side: "second"
|
|
3159
|
+
}, ...secondPath];
|
|
3160
|
+
return null;
|
|
3161
|
+
}
|
|
3162
|
+
//#endregion
|
|
3163
|
+
//#region packages/ag-term/src/selection-renderer.ts
|
|
3164
|
+
function normalizeSelectionColor(color) {
|
|
3165
|
+
if (color === void 0) return void 0;
|
|
3166
|
+
if (typeof color === "string") return parseColor(color);
|
|
3167
|
+
return color;
|
|
3661
3168
|
}
|
|
3662
3169
|
/**
|
|
3663
|
-
*
|
|
3170
|
+
* Compute selection style changes for all cells within a selection range.
|
|
3664
3171
|
*
|
|
3665
|
-
*
|
|
3666
|
-
*
|
|
3667
|
-
*
|
|
3668
|
-
*
|
|
3669
|
-
* -
|
|
3670
|
-
*
|
|
3671
|
-
*
|
|
3672
|
-
*
|
|
3673
|
-
*
|
|
3674
|
-
*
|
|
3172
|
+
* Returns a sparse list of cell changes (fg/bg only). The caller applies these
|
|
3173
|
+
* to the buffer before the output phase diffs it. This approach:
|
|
3174
|
+
* - Composes correctly with existing cell styles
|
|
3175
|
+
* - Handles already-inverted content (swaps back to normal instead of double-inverting)
|
|
3176
|
+
* - Respects SELECTABLE_FLAG per cell (skip non-selectable)
|
|
3177
|
+
* - Works with the normal diff/output renderer (no separate ANSI pass)
|
|
3178
|
+
*
|
|
3179
|
+
* @param buffer The rendered buffer (post-render-phase)
|
|
3180
|
+
* @param selection Current selection range, or null
|
|
3181
|
+
* @param theme Optional selection theme colors
|
|
3182
|
+
* @param respectSelectableFlag When true, skip cells without SELECTABLE_FLAG
|
|
3675
3183
|
*/
|
|
3676
|
-
function
|
|
3677
|
-
|
|
3678
|
-
const
|
|
3679
|
-
const
|
|
3680
|
-
const scope = options?.scope;
|
|
3681
|
-
const parts = [];
|
|
3184
|
+
function composeSelectionCells(buffer, selection, theme, respectSelectableFlag = false, scope) {
|
|
3185
|
+
if (!selection) return [];
|
|
3186
|
+
const { startRow, startCol, endRow, endCol } = normalizeRange(selection);
|
|
3187
|
+
const changes = [];
|
|
3682
3188
|
for (let row = startRow; row <= endRow; row++) {
|
|
3683
|
-
const meta = rowMeta?.[row];
|
|
3684
3189
|
let colStart = row === startRow ? startCol : 0;
|
|
3685
3190
|
let colEnd = row === endRow ? endCol : buffer.width - 1;
|
|
3191
|
+
const rowMeta = buffer.getRowMeta(row);
|
|
3192
|
+
if (rowMeta.lastContentCol >= 0) colEnd = Math.min(colEnd, rowMeta.lastContentCol);
|
|
3193
|
+
if (colStart > colEnd) continue;
|
|
3686
3194
|
if (scope) {
|
|
3687
3195
|
colStart = Math.max(colStart, scope.left);
|
|
3688
3196
|
colEnd = Math.min(colEnd, scope.right);
|
|
3689
|
-
if (colStart > colEnd)
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3197
|
+
if (colStart > colEnd) continue;
|
|
3198
|
+
}
|
|
3199
|
+
const firstContentCol = findFirstContentCol(buffer, row, colStart, colEnd, respectSelectableFlag);
|
|
3200
|
+
if (firstContentCol === null) continue;
|
|
3201
|
+
colStart = Math.max(colStart, firstContentCol);
|
|
3202
|
+
for (let col = colStart; col <= colEnd; col++) {
|
|
3203
|
+
if (buffer.isCellContinuation(col, row)) continue;
|
|
3204
|
+
if (respectSelectableFlag && !buffer.isCellSelectable(col, row)) continue;
|
|
3205
|
+
const cellFg = buffer.getCellFg(col, row);
|
|
3206
|
+
const cellBg = buffer.getCellBg(col, row);
|
|
3207
|
+
let newFg;
|
|
3208
|
+
let newBg;
|
|
3209
|
+
let inverseAttr = false;
|
|
3210
|
+
if (theme?.selectionBg != null) {
|
|
3211
|
+
newFg = theme.selectionFg ?? cellFg;
|
|
3212
|
+
newBg = theme.selectionBg;
|
|
3213
|
+
} else if (cellFg == null || cellBg == null) {
|
|
3214
|
+
newFg = cellFg;
|
|
3215
|
+
newBg = cellBg;
|
|
3216
|
+
inverseAttr = true;
|
|
3217
|
+
} else {
|
|
3218
|
+
newFg = cellBg;
|
|
3219
|
+
newBg = cellFg;
|
|
3220
|
+
}
|
|
3221
|
+
changes.push({
|
|
3222
|
+
col,
|
|
3223
|
+
row,
|
|
3224
|
+
fg: newFg,
|
|
3225
|
+
bg: newBg,
|
|
3226
|
+
inverseAttr
|
|
3227
|
+
});
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
return changes;
|
|
3231
|
+
}
|
|
3232
|
+
function findFirstContentCol(buffer, row, colStart, colEnd, respectSelectableFlag) {
|
|
3233
|
+
for (let col = colStart; col <= colEnd; col++) {
|
|
3234
|
+
if (buffer.isCellContinuation(col, row)) continue;
|
|
3235
|
+
if (respectSelectableFlag && !buffer.isCellSelectable(col, row)) continue;
|
|
3236
|
+
if (buffer.getCell(col, row).char.trim().length > 0) return col;
|
|
3237
|
+
}
|
|
3238
|
+
return null;
|
|
3239
|
+
}
|
|
3240
|
+
/**
|
|
3241
|
+
* Apply selection style changes to a buffer.
|
|
3242
|
+
*
|
|
3243
|
+
* Modifies the buffer in-place by setting fg/bg on affected cells.
|
|
3244
|
+
* Call this after the render phase and before the output phase.
|
|
3245
|
+
*
|
|
3246
|
+
* @param buffer The rendered buffer to modify
|
|
3247
|
+
* @param changes Cell changes from composeSelectionCells
|
|
3248
|
+
*/
|
|
3249
|
+
function applySelectionToBuffer(buffer, changes) {
|
|
3250
|
+
for (const change of changes) {
|
|
3251
|
+
const cell = buffer.getCell(change.col, change.row);
|
|
3252
|
+
if (change.inverseAttr) buffer.setCell(change.col, change.row, {
|
|
3253
|
+
...cell,
|
|
3254
|
+
attrs: {
|
|
3255
|
+
...cell.attrs,
|
|
3256
|
+
inverse: !cell.attrs.inverse
|
|
3257
|
+
}
|
|
3258
|
+
});
|
|
3259
|
+
else buffer.setCell(change.col, change.row, {
|
|
3260
|
+
...cell,
|
|
3261
|
+
fg: normalizeSelectionColor(change.fg) ?? null,
|
|
3262
|
+
bg: normalizeSelectionColor(change.bg) ?? null
|
|
3263
|
+
});
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
/**
|
|
3267
|
+
* Compute search-highlight style changes for a list of highlight ranges.
|
|
3268
|
+
*
|
|
3269
|
+
* Returns a sparse list of cell changes (fg/bg only) — apply them with
|
|
3270
|
+
* `applySelectionToBuffer` (the apply step is style-source-agnostic — it
|
|
3271
|
+
* just sets fg/bg or toggles inverse on the cells you give it).
|
|
3272
|
+
*
|
|
3273
|
+
* Default behaviour mirrors the legacy `renderSearchHighlights` that wrote
|
|
3274
|
+
* `\x1b[7m...\x1b[27m` past the buffer: cells with default fg/bg get the
|
|
3275
|
+
* inverse-attr toggle (so the terminal handles the SGR 7 swap at display).
|
|
3276
|
+
* Cells with explicit fg/bg get a direct fg↔bg swap. A theme can override
|
|
3277
|
+
* with explicit `selectionFg` / `selectionBg` tokens.
|
|
3278
|
+
*
|
|
3279
|
+
* Tracking: km-silvery.delete-search-overlay-ansi
|
|
3280
|
+
*/
|
|
3281
|
+
function composeSearchHighlightCells(buffer, highlights, theme) {
|
|
3282
|
+
if (highlights.length === 0) return [];
|
|
3283
|
+
const changes = [];
|
|
3284
|
+
for (const h of highlights) {
|
|
3285
|
+
const row = h.screenRow;
|
|
3286
|
+
if (row < 0 || row >= buffer.height) continue;
|
|
3287
|
+
const colStart = Math.max(0, h.startCol);
|
|
3288
|
+
const colEnd = Math.min(buffer.width - 1, h.endCol);
|
|
3289
|
+
if (colStart > colEnd) continue;
|
|
3290
|
+
for (let col = colStart; col <= colEnd; col++) {
|
|
3291
|
+
if (buffer.isCellContinuation(col, row)) continue;
|
|
3292
|
+
const cellFg = buffer.getCellFg(col, row);
|
|
3293
|
+
const cellBg = buffer.getCellBg(col, row);
|
|
3294
|
+
let newFg;
|
|
3295
|
+
let newBg;
|
|
3296
|
+
let inverseAttr = false;
|
|
3297
|
+
if (theme?.selectionBg != null) {
|
|
3298
|
+
newFg = theme.selectionFg ?? cellFg;
|
|
3299
|
+
newBg = theme.selectionBg;
|
|
3300
|
+
} else if (cellFg == null || cellBg == null) {
|
|
3301
|
+
newFg = cellFg;
|
|
3302
|
+
newBg = cellBg;
|
|
3303
|
+
inverseAttr = true;
|
|
3304
|
+
} else {
|
|
3305
|
+
newFg = cellBg;
|
|
3306
|
+
newBg = cellFg;
|
|
3307
|
+
}
|
|
3308
|
+
changes.push({
|
|
3309
|
+
col,
|
|
3310
|
+
row,
|
|
3311
|
+
fg: newFg,
|
|
3312
|
+
bg: newBg,
|
|
3313
|
+
inverseAttr
|
|
3314
|
+
});
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
return changes;
|
|
3318
|
+
}
|
|
3319
|
+
//#endregion
|
|
3320
|
+
//#region packages/ag-term/src/virtual-scrollback.ts
|
|
3321
|
+
/**
|
|
3322
|
+
* Virtual scrollback buffer for storing historical rendered content.
|
|
3323
|
+
*
|
|
3324
|
+
* Used by virtual inline mode to maintain scrollable history while
|
|
3325
|
+
* rendering in altscreen (which normally has no scrollback).
|
|
3326
|
+
*
|
|
3327
|
+
* Implementation uses a circular buffer for O(1) push and bounded memory.
|
|
3328
|
+
*/
|
|
3329
|
+
function createVirtualScrollback(options) {
|
|
3330
|
+
const maxLines = options?.maxLines ?? 1e4;
|
|
3331
|
+
const ansiLines = new Array(maxLines);
|
|
3332
|
+
const plainLines = new Array(maxLines);
|
|
3333
|
+
let head = 0;
|
|
3334
|
+
let count = 0;
|
|
3335
|
+
return {
|
|
3336
|
+
push(lines) {
|
|
3337
|
+
for (const line of lines) {
|
|
3338
|
+
ansiLines[head] = line;
|
|
3339
|
+
plainLines[head] = stripAnsi(line);
|
|
3340
|
+
head = (head + 1) % maxLines;
|
|
3341
|
+
if (count < maxLines) count++;
|
|
3342
|
+
}
|
|
3343
|
+
},
|
|
3344
|
+
getVisibleRows(offset, rowCount) {
|
|
3345
|
+
const result = [];
|
|
3346
|
+
for (let i = 0; i < rowCount; i++) {
|
|
3347
|
+
const logicalIndex = count - offset - rowCount + i;
|
|
3348
|
+
if (logicalIndex < 0 || logicalIndex >= count) result.push("");
|
|
3349
|
+
else {
|
|
3350
|
+
const physical = (head - count + logicalIndex + maxLines) % maxLines;
|
|
3351
|
+
result.push(ansiLines[physical]);
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
return result;
|
|
3355
|
+
},
|
|
3356
|
+
get totalLines() {
|
|
3357
|
+
return count;
|
|
3358
|
+
},
|
|
3359
|
+
search(query) {
|
|
3360
|
+
if (!query) return [];
|
|
3361
|
+
const lowerQuery = query.toLowerCase();
|
|
3362
|
+
const matches = [];
|
|
3363
|
+
for (let i = 0; i < count; i++) if (plainLines[(head - count + i + maxLines) % maxLines].toLowerCase().includes(lowerQuery)) matches.push(i);
|
|
3364
|
+
return matches;
|
|
3365
|
+
},
|
|
3366
|
+
clear() {
|
|
3367
|
+
head = 0;
|
|
3368
|
+
count = 0;
|
|
3369
|
+
}
|
|
3370
|
+
};
|
|
3371
|
+
}
|
|
3372
|
+
//#endregion
|
|
3373
|
+
//#region packages/ag-term/src/history-buffer.ts
|
|
3374
|
+
/**
|
|
3375
|
+
* Ring buffer for frozen scrollback items.
|
|
3376
|
+
*
|
|
3377
|
+
* Each item represents a rendered list entry (card, row, etc.) with its
|
|
3378
|
+
* ANSI snapshot, split rows, and plain-text rows for searching.
|
|
3379
|
+
* When total rows or cached ANSI bytes exceed capacity, the oldest items are
|
|
3380
|
+
* evicted. The byte count tracks the serialized string payload; it is a
|
|
3381
|
+
* deterministic cache budget, not an exact process heap measurement.
|
|
3382
|
+
*/
|
|
3383
|
+
function createHistoryItem(key, ansi, width) {
|
|
3384
|
+
const rows = ansi.split("\n");
|
|
3385
|
+
return {
|
|
3386
|
+
key,
|
|
3387
|
+
ansi,
|
|
3388
|
+
rows,
|
|
3389
|
+
plainTextRows: rows.map((r) => stripAnsi(r)),
|
|
3390
|
+
width
|
|
3391
|
+
};
|
|
3392
|
+
}
|
|
3393
|
+
function createHistoryBuffer(options = 1e4) {
|
|
3394
|
+
const capacity = typeof options === "number" ? options : options.capacityRows ?? 1e4;
|
|
3395
|
+
const capacityBytes = typeof options === "number" ? null : normalizeCapacityBytes(options.capacityBytes);
|
|
3396
|
+
let items = [];
|
|
3397
|
+
let _totalRows = 0;
|
|
3398
|
+
let _totalBytes = 0;
|
|
3399
|
+
function evict() {
|
|
3400
|
+
while (items.length > 0 && (_totalRows > capacity || capacityBytes !== null && _totalBytes > capacityBytes)) {
|
|
3401
|
+
const removed = items.shift();
|
|
3402
|
+
_totalRows -= removed.rows.length;
|
|
3403
|
+
_totalBytes -= estimateHistoryItemBytes(removed);
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
/** Walk items to find which item contains the given document row. */
|
|
3407
|
+
function resolveRow(row) {
|
|
3408
|
+
if (row < 0 || row >= _totalRows) return null;
|
|
3409
|
+
let cumulative = 0;
|
|
3410
|
+
for (let i = 0; i < items.length; i++) {
|
|
3411
|
+
const itemRows = items[i].rows.length;
|
|
3412
|
+
if (row < cumulative + itemRows) return {
|
|
3413
|
+
itemIndex: i,
|
|
3414
|
+
localRow: row - cumulative
|
|
3415
|
+
};
|
|
3416
|
+
cumulative += itemRows;
|
|
3417
|
+
}
|
|
3418
|
+
return null;
|
|
3419
|
+
}
|
|
3420
|
+
return {
|
|
3421
|
+
push(item) {
|
|
3422
|
+
items.push(item);
|
|
3423
|
+
_totalRows += item.rows.length;
|
|
3424
|
+
_totalBytes += estimateHistoryItemBytes(item);
|
|
3425
|
+
evict();
|
|
3426
|
+
},
|
|
3427
|
+
get totalRows() {
|
|
3428
|
+
return _totalRows;
|
|
3429
|
+
},
|
|
3430
|
+
get totalBytes() {
|
|
3431
|
+
return _totalBytes;
|
|
3432
|
+
},
|
|
3433
|
+
get itemCount() {
|
|
3434
|
+
return items.length;
|
|
3435
|
+
},
|
|
3436
|
+
get capacity() {
|
|
3437
|
+
return capacity;
|
|
3438
|
+
},
|
|
3439
|
+
get capacityBytes() {
|
|
3440
|
+
return capacityBytes;
|
|
3441
|
+
},
|
|
3442
|
+
getRows(startRow, count) {
|
|
3443
|
+
const result = [];
|
|
3444
|
+
for (let r = startRow; r < startRow + count; r++) {
|
|
3445
|
+
const resolved = resolveRow(r);
|
|
3446
|
+
if (resolved) result.push(items[resolved.itemIndex].rows[resolved.localRow]);
|
|
3447
|
+
else result.push("");
|
|
3448
|
+
}
|
|
3449
|
+
return result;
|
|
3450
|
+
},
|
|
3451
|
+
getPlainTextRows(startRow, count) {
|
|
3452
|
+
const result = [];
|
|
3453
|
+
for (let r = startRow; r < startRow + count; r++) {
|
|
3454
|
+
const resolved = resolveRow(r);
|
|
3455
|
+
if (resolved) result.push(items[resolved.itemIndex].plainTextRows[resolved.localRow]);
|
|
3456
|
+
else result.push("");
|
|
3457
|
+
}
|
|
3458
|
+
return result;
|
|
3459
|
+
},
|
|
3460
|
+
search(query) {
|
|
3461
|
+
if (!query) return [];
|
|
3462
|
+
const lowerQuery = query.toLowerCase();
|
|
3463
|
+
const matches = [];
|
|
3464
|
+
let rowOffset = 0;
|
|
3465
|
+
for (const item of items) {
|
|
3466
|
+
for (let r = 0; r < item.plainTextRows.length; r++) if (item.plainTextRows[r].toLowerCase().includes(lowerQuery)) matches.push(rowOffset + r);
|
|
3467
|
+
rowOffset += item.rows.length;
|
|
3468
|
+
}
|
|
3469
|
+
return matches;
|
|
3470
|
+
},
|
|
3471
|
+
getItemAtRow(row) {
|
|
3472
|
+
const resolved = resolveRow(row);
|
|
3473
|
+
if (!resolved) return null;
|
|
3474
|
+
return {
|
|
3475
|
+
item: items[resolved.itemIndex],
|
|
3476
|
+
localRow: resolved.localRow
|
|
3477
|
+
};
|
|
3478
|
+
},
|
|
3479
|
+
clear() {
|
|
3480
|
+
items = [];
|
|
3481
|
+
_totalRows = 0;
|
|
3482
|
+
_totalBytes = 0;
|
|
3483
|
+
}
|
|
3484
|
+
};
|
|
3485
|
+
}
|
|
3486
|
+
function normalizeCapacityBytes(value) {
|
|
3487
|
+
return Number.isFinite(value) && value !== void 0 && value > 0 ? Math.floor(value) : null;
|
|
3488
|
+
}
|
|
3489
|
+
const utf8Encoder = new TextEncoder();
|
|
3490
|
+
function estimateHistoryItemBytes(item) {
|
|
3491
|
+
return utf8Encoder.encode(item.ansi).byteLength;
|
|
3492
|
+
}
|
|
3493
|
+
//#endregion
|
|
3494
|
+
//#region packages/ag-term/src/viewport-compositor.ts
|
|
3495
|
+
function composeViewport(config) {
|
|
3496
|
+
const { history, viewportHeight, scrollOffset } = config;
|
|
3497
|
+
const totalHistory = history.totalRows;
|
|
3498
|
+
if (scrollOffset <= 0 || totalHistory === 0) return {
|
|
3499
|
+
overlayRows: [],
|
|
3500
|
+
overlayRowCount: 0,
|
|
3501
|
+
liveRowsVisible: viewportHeight,
|
|
3502
|
+
isScrolledUp: false,
|
|
3503
|
+
totalHeight: totalHistory + viewportHeight
|
|
3504
|
+
};
|
|
3505
|
+
const startRow = Math.max(0, totalHistory - Math.min(scrollOffset, totalHistory));
|
|
3506
|
+
const rowsToShow = Math.min(viewportHeight, totalHistory - startRow);
|
|
3507
|
+
return {
|
|
3508
|
+
overlayRows: history.getRows(startRow, rowsToShow),
|
|
3509
|
+
overlayRowCount: rowsToShow,
|
|
3510
|
+
liveRowsVisible: viewportHeight - rowsToShow,
|
|
3511
|
+
isScrolledUp: true,
|
|
3512
|
+
totalHeight: totalHistory + viewportHeight
|
|
3513
|
+
};
|
|
3514
|
+
}
|
|
3515
|
+
//#endregion
|
|
3516
|
+
//#region packages/ag-term/src/list-document.ts
|
|
3517
|
+
function createListDocument(history, getLiveItems) {
|
|
3518
|
+
function liveRowCount() {
|
|
3519
|
+
let total = 0;
|
|
3520
|
+
for (const block of getLiveItems()) total += block.rows.length;
|
|
3521
|
+
return total;
|
|
3522
|
+
}
|
|
3523
|
+
return {
|
|
3524
|
+
get totalRows() {
|
|
3525
|
+
return history.totalRows + liveRowCount();
|
|
3526
|
+
},
|
|
3527
|
+
get frozenRows() {
|
|
3528
|
+
return history.totalRows;
|
|
3529
|
+
},
|
|
3530
|
+
get liveRows() {
|
|
3531
|
+
return liveRowCount();
|
|
3532
|
+
},
|
|
3533
|
+
getRows(startRow, count) {
|
|
3534
|
+
const frozen = history.totalRows;
|
|
3535
|
+
const result = [];
|
|
3536
|
+
for (let r = startRow; r < startRow + count; r++) if (r < 0 || r >= this.totalRows) result.push("");
|
|
3537
|
+
else if (r < frozen) result.push(...history.getRows(r, 1));
|
|
3538
|
+
else {
|
|
3539
|
+
let liveRow = r - frozen;
|
|
3540
|
+
let found = false;
|
|
3541
|
+
for (const block of getLiveItems()) {
|
|
3542
|
+
if (liveRow < block.rows.length) {
|
|
3543
|
+
result.push(block.rows[liveRow]);
|
|
3544
|
+
found = true;
|
|
3545
|
+
break;
|
|
3546
|
+
}
|
|
3547
|
+
liveRow -= block.rows.length;
|
|
3548
|
+
}
|
|
3549
|
+
if (!found) result.push("");
|
|
3550
|
+
}
|
|
3551
|
+
return result;
|
|
3552
|
+
},
|
|
3553
|
+
getPlainTextRows(startRow, count) {
|
|
3554
|
+
const frozen = history.totalRows;
|
|
3555
|
+
const result = [];
|
|
3556
|
+
for (let r = startRow; r < startRow + count; r++) if (r < 0 || r >= this.totalRows) result.push("");
|
|
3557
|
+
else if (r < frozen) result.push(...history.getPlainTextRows(r, 1));
|
|
3558
|
+
else {
|
|
3559
|
+
let liveRow = r - frozen;
|
|
3560
|
+
let found = false;
|
|
3561
|
+
for (const block of getLiveItems()) {
|
|
3562
|
+
if (liveRow < block.plainTextRows.length) {
|
|
3563
|
+
result.push(block.plainTextRows[liveRow]);
|
|
3564
|
+
found = true;
|
|
3565
|
+
break;
|
|
3566
|
+
}
|
|
3567
|
+
liveRow -= block.plainTextRows.length;
|
|
3693
3568
|
}
|
|
3694
|
-
|
|
3569
|
+
if (!found) result.push("");
|
|
3695
3570
|
}
|
|
3571
|
+
return result;
|
|
3572
|
+
},
|
|
3573
|
+
getSource(row) {
|
|
3574
|
+
const frozen = history.totalRows;
|
|
3575
|
+
if (row < 0 || row >= this.totalRows) return null;
|
|
3576
|
+
if (row < frozen) {
|
|
3577
|
+
const hit = history.getItemAtRow(row);
|
|
3578
|
+
if (!hit) return null;
|
|
3579
|
+
return {
|
|
3580
|
+
type: "frozen",
|
|
3581
|
+
itemKey: hit.item.key,
|
|
3582
|
+
localRow: hit.localRow
|
|
3583
|
+
};
|
|
3584
|
+
}
|
|
3585
|
+
const liveItems = getLiveItems();
|
|
3586
|
+
let liveRow = row - frozen;
|
|
3587
|
+
for (const block of liveItems) {
|
|
3588
|
+
if (liveRow < block.rows.length) return {
|
|
3589
|
+
type: "live",
|
|
3590
|
+
itemIndex: block.itemIndex,
|
|
3591
|
+
localRow: liveRow
|
|
3592
|
+
};
|
|
3593
|
+
liveRow -= block.rows.length;
|
|
3594
|
+
}
|
|
3595
|
+
return null;
|
|
3596
|
+
},
|
|
3597
|
+
search(query) {
|
|
3598
|
+
if (!query) return [];
|
|
3599
|
+
const lowerQuery = query.toLowerCase();
|
|
3600
|
+
const matches = [];
|
|
3601
|
+
const frozen = history.totalRows;
|
|
3602
|
+
const frozenRowMatches = history.search(query);
|
|
3603
|
+
for (const row of frozenRowMatches) {
|
|
3604
|
+
const line = history.getPlainTextRows(row, 1)[0].toLowerCase();
|
|
3605
|
+
let pos = line.indexOf(lowerQuery);
|
|
3606
|
+
while (pos !== -1) {
|
|
3607
|
+
matches.push({
|
|
3608
|
+
row,
|
|
3609
|
+
startCol: pos,
|
|
3610
|
+
endCol: pos + query.length
|
|
3611
|
+
});
|
|
3612
|
+
pos = line.indexOf(lowerQuery, pos + 1);
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
let rowOffset = 0;
|
|
3616
|
+
for (const block of getLiveItems()) {
|
|
3617
|
+
for (let i = 0; i < block.plainTextRows.length; i++) {
|
|
3618
|
+
const line = block.plainTextRows[i].toLowerCase();
|
|
3619
|
+
let pos = line.indexOf(lowerQuery);
|
|
3620
|
+
while (pos !== -1) {
|
|
3621
|
+
matches.push({
|
|
3622
|
+
row: frozen + rowOffset + i,
|
|
3623
|
+
startCol: pos,
|
|
3624
|
+
endCol: pos + query.length
|
|
3625
|
+
});
|
|
3626
|
+
pos = line.indexOf(lowerQuery, pos + 1);
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
rowOffset += block.plainTextRows.length;
|
|
3630
|
+
}
|
|
3631
|
+
return matches;
|
|
3696
3632
|
}
|
|
3697
|
-
|
|
3698
|
-
let line = "";
|
|
3699
|
-
for (let col = colStart; col <= colEnd; col++) {
|
|
3700
|
-
if (buffer.isCellContinuation(col, row)) continue;
|
|
3701
|
-
if (respectSelectable && !buffer.isCellSelectable(col, row)) continue;
|
|
3702
|
-
line += buffer.getCellChar(col, row);
|
|
3703
|
-
}
|
|
3704
|
-
if (!meta || meta.lastContentCol < 0) line = line.replace(/\s+$/, "");
|
|
3705
|
-
if (meta?.softWrapped && row < endRow) {
|
|
3706
|
-
parts.push(line);
|
|
3707
|
-
if (meta.wrapJoinSpace) parts.push(" ");
|
|
3708
|
-
} else {
|
|
3709
|
-
parts.push(line);
|
|
3710
|
-
if (row < endRow) parts.push("\n");
|
|
3711
|
-
}
|
|
3712
|
-
}
|
|
3713
|
-
return parts.join("");
|
|
3633
|
+
};
|
|
3714
3634
|
}
|
|
3715
3635
|
//#endregion
|
|
3716
|
-
//#region packages/ag-term/src/
|
|
3717
|
-
function
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
}
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
const { startRow, startCol, endRow, endCol } = normalizeRange(selection);
|
|
3740
|
-
const changes = [];
|
|
3741
|
-
for (let row = startRow; row <= endRow; row++) {
|
|
3742
|
-
let colStart = row === startRow ? startCol : 0;
|
|
3743
|
-
let colEnd = row === endRow ? endCol : buffer.width - 1;
|
|
3744
|
-
const rowMeta = buffer.getRowMeta(row);
|
|
3745
|
-
if (rowMeta.lastContentCol >= 0) colEnd = Math.min(colEnd, rowMeta.lastContentCol);
|
|
3746
|
-
if (colStart > colEnd) continue;
|
|
3747
|
-
if (scope) {
|
|
3748
|
-
colStart = Math.max(colStart, scope.left);
|
|
3749
|
-
colEnd = Math.min(colEnd, scope.right);
|
|
3750
|
-
if (colStart > colEnd) continue;
|
|
3751
|
-
}
|
|
3752
|
-
const firstContentCol = findFirstContentCol(buffer, row, colStart, colEnd, respectSelectableFlag);
|
|
3753
|
-
if (firstContentCol === null) continue;
|
|
3754
|
-
colStart = Math.max(colStart, firstContentCol);
|
|
3755
|
-
for (let col = colStart; col <= colEnd; col++) {
|
|
3756
|
-
if (buffer.isCellContinuation(col, row)) continue;
|
|
3757
|
-
if (respectSelectableFlag && !buffer.isCellSelectable(col, row)) continue;
|
|
3758
|
-
const cellFg = buffer.getCellFg(col, row);
|
|
3759
|
-
const cellBg = buffer.getCellBg(col, row);
|
|
3760
|
-
let newFg;
|
|
3761
|
-
let newBg;
|
|
3762
|
-
let inverseAttr = false;
|
|
3763
|
-
if (theme?.selectionBg != null) {
|
|
3764
|
-
newFg = theme.selectionFg ?? cellFg;
|
|
3765
|
-
newBg = theme.selectionBg;
|
|
3766
|
-
} else if (cellFg == null || cellBg == null) {
|
|
3767
|
-
newFg = cellFg;
|
|
3768
|
-
newBg = cellBg;
|
|
3769
|
-
inverseAttr = true;
|
|
3770
|
-
} else {
|
|
3771
|
-
newFg = cellBg;
|
|
3772
|
-
newBg = cellFg;
|
|
3636
|
+
//#region packages/ag-term/src/text-surface.ts
|
|
3637
|
+
function createTextSurface(config) {
|
|
3638
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
3639
|
+
function notify() {
|
|
3640
|
+
for (const fn of listeners) fn();
|
|
3641
|
+
}
|
|
3642
|
+
return {
|
|
3643
|
+
get id() {
|
|
3644
|
+
return config.id;
|
|
3645
|
+
},
|
|
3646
|
+
get document() {
|
|
3647
|
+
return config.document;
|
|
3648
|
+
},
|
|
3649
|
+
getText(startRow, startCol, endRow, endCol) {
|
|
3650
|
+
const rows = config.document.getRows(startRow, endRow - startRow + 1);
|
|
3651
|
+
const lines = [];
|
|
3652
|
+
for (let i = 0; i < rows.length; i++) {
|
|
3653
|
+
const plain = stripAnsi(rows[i]);
|
|
3654
|
+
const row = startRow + i;
|
|
3655
|
+
if (row === startRow && row === endRow) lines.push(plain.slice(startCol, endCol));
|
|
3656
|
+
else if (row === startRow) lines.push(plain.slice(startCol));
|
|
3657
|
+
else if (row === endRow) lines.push(plain.slice(0, endCol));
|
|
3658
|
+
else lines.push(plain);
|
|
3773
3659
|
}
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3660
|
+
return lines.join("\n");
|
|
3661
|
+
},
|
|
3662
|
+
search(query) {
|
|
3663
|
+
return config.document.search(query);
|
|
3664
|
+
},
|
|
3665
|
+
hitTest(viewportRow, viewportCol) {
|
|
3666
|
+
const docRow = config.viewportToDocument(viewportRow);
|
|
3667
|
+
if (docRow < 0 || docRow >= config.document.totalRows) return null;
|
|
3668
|
+
return {
|
|
3669
|
+
row: docRow,
|
|
3670
|
+
col: viewportCol
|
|
3671
|
+
};
|
|
3672
|
+
},
|
|
3673
|
+
reveal(row) {
|
|
3674
|
+
config.onReveal(row);
|
|
3675
|
+
notify();
|
|
3676
|
+
},
|
|
3677
|
+
notifyContentChange() {
|
|
3678
|
+
notify();
|
|
3679
|
+
},
|
|
3680
|
+
subscribe(listener) {
|
|
3681
|
+
listeners.add(listener);
|
|
3682
|
+
return () => {
|
|
3683
|
+
listeners.delete(listener);
|
|
3684
|
+
};
|
|
3685
|
+
},
|
|
3686
|
+
get capabilities() {
|
|
3687
|
+
return config.capabilities;
|
|
3781
3688
|
}
|
|
3782
|
-
}
|
|
3783
|
-
return changes;
|
|
3784
|
-
}
|
|
3785
|
-
function findFirstContentCol(buffer, row, colStart, colEnd, respectSelectableFlag) {
|
|
3786
|
-
for (let col = colStart; col <= colEnd; col++) {
|
|
3787
|
-
if (buffer.isCellContinuation(col, row)) continue;
|
|
3788
|
-
if (respectSelectableFlag && !buffer.isCellSelectable(col, row)) continue;
|
|
3789
|
-
if (buffer.getCell(col, row).char.trim().length > 0) return col;
|
|
3790
|
-
}
|
|
3791
|
-
return null;
|
|
3689
|
+
};
|
|
3792
3690
|
}
|
|
3691
|
+
//#endregion
|
|
3692
|
+
//#region packages/ag-term/src/search-overlay.ts
|
|
3793
3693
|
/**
|
|
3794
|
-
*
|
|
3694
|
+
* Find all case-insensitive occurrences of `query` inside `text` and return
|
|
3695
|
+
* their character offsets. Empty query or empty text returns `[]`.
|
|
3795
3696
|
*
|
|
3796
|
-
*
|
|
3797
|
-
*
|
|
3697
|
+
* This is the canonical silvery search-match algorithm. The same logic runs
|
|
3698
|
+
* inside `ListView`'s registered Searchable (to find matches across items)
|
|
3699
|
+
* and is exposed to consumers that render multi-segment items — a LogRow
|
|
3700
|
+
* whose searchable text is a concatenation of field values, but whose visual
|
|
3701
|
+
* rendering splits those fields across separate Text nodes, needs per-segment
|
|
3702
|
+
* ranges to highlight without re-implementing the semantics.
|
|
3798
3703
|
*
|
|
3799
|
-
*
|
|
3800
|
-
*
|
|
3704
|
+
* Ranges are returned in ascending `start` order; overlapping matches are
|
|
3705
|
+
* not produced (`indexOf(..., start = last.start + 1)` advances past each
|
|
3706
|
+
* match start, not past its full length, preserving overlapping runs of
|
|
3707
|
+
* short queries — e.g. `"aa"` in `"aaaa"` yields `[0..2], [1..3], [2..4]`).
|
|
3708
|
+
*
|
|
3709
|
+
* Offsets are CHARACTER offsets into the input string. They are not column
|
|
3710
|
+
* offsets — multi-column wide glyphs are counted as one character here, the
|
|
3711
|
+
* same way the SearchProvider's match-row computation does.
|
|
3801
3712
|
*/
|
|
3802
|
-
function
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
fg: normalizeSelectionColor(change.fg) ?? null,
|
|
3815
|
-
bg: normalizeSelectionColor(change.bg) ?? null
|
|
3713
|
+
function computeMatchRanges(text, query) {
|
|
3714
|
+
if (query === "" || text === "") return [];
|
|
3715
|
+
const lowerText = text.toLowerCase();
|
|
3716
|
+
const lowerQuery = query.toLowerCase();
|
|
3717
|
+
const ranges = [];
|
|
3718
|
+
let cursor = 0;
|
|
3719
|
+
while (cursor < lowerText.length) {
|
|
3720
|
+
const found = lowerText.indexOf(lowerQuery, cursor);
|
|
3721
|
+
if (found === -1) break;
|
|
3722
|
+
ranges.push({
|
|
3723
|
+
start: found,
|
|
3724
|
+
end: found + query.length
|
|
3816
3725
|
});
|
|
3726
|
+
cursor = found + 1;
|
|
3817
3727
|
}
|
|
3728
|
+
return ranges;
|
|
3729
|
+
}
|
|
3730
|
+
function createSearchState() {
|
|
3731
|
+
return {
|
|
3732
|
+
active: false,
|
|
3733
|
+
query: "",
|
|
3734
|
+
matches: [],
|
|
3735
|
+
currentMatch: -1,
|
|
3736
|
+
cursorPosition: 0
|
|
3737
|
+
};
|
|
3818
3738
|
}
|
|
3819
3739
|
/**
|
|
3820
|
-
*
|
|
3821
|
-
*
|
|
3822
|
-
* Returns a sparse list of cell changes (fg/bg only) — apply them with
|
|
3823
|
-
* `applySelectionToBuffer` (the apply step is style-source-agnostic — it
|
|
3824
|
-
* just sets fg/bg or toggles inverse on the cells you give it).
|
|
3825
|
-
*
|
|
3826
|
-
* Default behaviour mirrors the legacy `renderSearchHighlights` that wrote
|
|
3827
|
-
* `\x1b[7m...\x1b[27m` past the buffer: cells with default fg/bg get the
|
|
3828
|
-
* inverse-attr toggle (so the terminal handles the SGR 7 swap at display).
|
|
3829
|
-
* Cells with explicit fg/bg get a direct fg↔bg swap. A theme can override
|
|
3830
|
-
* with explicit `selectionFg` / `selectionBg` tokens.
|
|
3831
|
-
*
|
|
3832
|
-
* Tracking: km-silvery.delete-search-overlay-ansi
|
|
3740
|
+
* Update search state. searchFn is called when query changes to find matches.
|
|
3833
3741
|
*/
|
|
3834
|
-
function
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3742
|
+
function searchUpdate(action, state, searchFn) {
|
|
3743
|
+
switch (action.type) {
|
|
3744
|
+
case "open": return [{
|
|
3745
|
+
...state,
|
|
3746
|
+
active: true,
|
|
3747
|
+
query: "",
|
|
3748
|
+
matches: [],
|
|
3749
|
+
currentMatch: -1,
|
|
3750
|
+
cursorPosition: 0
|
|
3751
|
+
}, [{ type: "render" }]];
|
|
3752
|
+
case "close": return [{
|
|
3753
|
+
...state,
|
|
3754
|
+
active: false,
|
|
3755
|
+
query: "",
|
|
3756
|
+
cursorPosition: 0
|
|
3757
|
+
}, [{ type: "render" }]];
|
|
3758
|
+
case "input": {
|
|
3759
|
+
const query = state.query.slice(0, state.cursorPosition) + action.char + state.query.slice(state.cursorPosition);
|
|
3760
|
+
const cursorPosition = state.cursorPosition + 1;
|
|
3761
|
+
const matches = searchFn ? searchFn(query) : [];
|
|
3762
|
+
const currentMatch = matches.length > 0 ? 0 : -1;
|
|
3763
|
+
const effects = [{ type: "render" }];
|
|
3764
|
+
if (currentMatch >= 0) effects.push({
|
|
3765
|
+
type: "scrollTo",
|
|
3766
|
+
row: matches[0].row
|
|
3767
|
+
});
|
|
3768
|
+
return [{
|
|
3769
|
+
...state,
|
|
3770
|
+
query,
|
|
3771
|
+
cursorPosition,
|
|
3772
|
+
matches,
|
|
3773
|
+
currentMatch
|
|
3774
|
+
}, effects];
|
|
3775
|
+
}
|
|
3776
|
+
case "backspace": {
|
|
3777
|
+
if (state.cursorPosition === 0) return [state, []];
|
|
3778
|
+
const query = state.query.slice(0, state.cursorPosition - 1) + state.query.slice(state.cursorPosition);
|
|
3779
|
+
const cursorPosition = state.cursorPosition - 1;
|
|
3780
|
+
const matches = searchFn ? searchFn(query) : [];
|
|
3781
|
+
const currentMatch = matches.length > 0 ? 0 : -1;
|
|
3782
|
+
const effects = [{ type: "render" }];
|
|
3783
|
+
if (currentMatch >= 0) effects.push({
|
|
3784
|
+
type: "scrollTo",
|
|
3785
|
+
row: matches[0].row
|
|
3786
|
+
});
|
|
3787
|
+
return [{
|
|
3788
|
+
...state,
|
|
3789
|
+
query,
|
|
3790
|
+
cursorPosition,
|
|
3791
|
+
matches,
|
|
3792
|
+
currentMatch
|
|
3793
|
+
}, effects];
|
|
3794
|
+
}
|
|
3795
|
+
case "nextMatch": {
|
|
3796
|
+
if (state.matches.length === 0) return [state, []];
|
|
3797
|
+
const currentMatch = (state.currentMatch + 1) % state.matches.length;
|
|
3798
|
+
return [{
|
|
3799
|
+
...state,
|
|
3800
|
+
currentMatch
|
|
3801
|
+
}, [{
|
|
3802
|
+
type: "scrollTo",
|
|
3803
|
+
row: state.matches[currentMatch].row
|
|
3804
|
+
}, { type: "render" }]];
|
|
3805
|
+
}
|
|
3806
|
+
case "prevMatch": {
|
|
3807
|
+
if (state.matches.length === 0) return [state, []];
|
|
3808
|
+
const currentMatch = (state.currentMatch - 1 + state.matches.length) % state.matches.length;
|
|
3809
|
+
return [{
|
|
3810
|
+
...state,
|
|
3811
|
+
currentMatch
|
|
3812
|
+
}, [{
|
|
3813
|
+
type: "scrollTo",
|
|
3814
|
+
row: state.matches[currentMatch].row
|
|
3815
|
+
}, { type: "render" }]];
|
|
3816
|
+
}
|
|
3817
|
+
case "cursorLeft":
|
|
3818
|
+
if (state.cursorPosition === 0) return [state, []];
|
|
3819
|
+
return [{
|
|
3820
|
+
...state,
|
|
3821
|
+
cursorPosition: state.cursorPosition - 1
|
|
3822
|
+
}, []];
|
|
3823
|
+
case "cursorRight":
|
|
3824
|
+
if (state.cursorPosition >= state.query.length) return [state, []];
|
|
3825
|
+
return [{
|
|
3826
|
+
...state,
|
|
3827
|
+
cursorPosition: state.cursorPosition + 1
|
|
3828
|
+
}, []];
|
|
3829
|
+
case "cursorToStart":
|
|
3830
|
+
if (state.cursorPosition === 0) return [state, []];
|
|
3831
|
+
return [{
|
|
3832
|
+
...state,
|
|
3833
|
+
cursorPosition: 0
|
|
3834
|
+
}, []];
|
|
3835
|
+
case "cursorToEnd":
|
|
3836
|
+
if (state.cursorPosition >= state.query.length) return [state, []];
|
|
3837
|
+
return [{
|
|
3838
|
+
...state,
|
|
3839
|
+
cursorPosition: state.query.length
|
|
3840
|
+
}, []];
|
|
3841
|
+
case "deleteWordBack": {
|
|
3842
|
+
if (state.cursorPosition === 0) return [state, []];
|
|
3843
|
+
let pos = state.cursorPosition;
|
|
3844
|
+
while (pos > 0 && /\s/.test(state.query[pos - 1] ?? "")) pos--;
|
|
3845
|
+
while (pos > 0 && !/\s/.test(state.query[pos - 1] ?? "")) pos--;
|
|
3846
|
+
const query = state.query.slice(0, pos) + state.query.slice(state.cursorPosition);
|
|
3847
|
+
const cursorPosition = pos;
|
|
3848
|
+
const matches = searchFn ? searchFn(query) : [];
|
|
3849
|
+
const currentMatch = matches.length > 0 ? 0 : -1;
|
|
3850
|
+
const effects = [{ type: "render" }];
|
|
3851
|
+
if (currentMatch >= 0) effects.push({
|
|
3852
|
+
type: "scrollTo",
|
|
3853
|
+
row: matches[0].row
|
|
3854
|
+
});
|
|
3855
|
+
return [{
|
|
3856
|
+
...state,
|
|
3857
|
+
query,
|
|
3858
|
+
cursorPosition,
|
|
3859
|
+
matches,
|
|
3860
|
+
currentMatch
|
|
3861
|
+
}, effects];
|
|
3862
|
+
}
|
|
3863
|
+
case "deleteToStart": {
|
|
3864
|
+
if (state.cursorPosition === 0) return [state, []];
|
|
3865
|
+
const query = state.query.slice(state.cursorPosition);
|
|
3866
|
+
const cursorPosition = 0;
|
|
3867
|
+
const matches = searchFn ? searchFn(query) : [];
|
|
3868
|
+
const currentMatch = matches.length > 0 ? 0 : -1;
|
|
3869
|
+
const effects = [{ type: "render" }];
|
|
3870
|
+
if (currentMatch >= 0) effects.push({
|
|
3871
|
+
type: "scrollTo",
|
|
3872
|
+
row: matches[0].row
|
|
3867
3873
|
});
|
|
3874
|
+
return [{
|
|
3875
|
+
...state,
|
|
3876
|
+
query,
|
|
3877
|
+
cursorPosition,
|
|
3878
|
+
matches,
|
|
3879
|
+
currentMatch
|
|
3880
|
+
}, effects];
|
|
3868
3881
|
}
|
|
3869
3882
|
}
|
|
3870
|
-
return changes;
|
|
3871
3883
|
}
|
|
3872
|
-
//#endregion
|
|
3873
|
-
//#region packages/ag-term/src/virtual-scrollback.ts
|
|
3874
3884
|
/**
|
|
3875
|
-
*
|
|
3876
|
-
*
|
|
3877
|
-
* Used by virtual inline mode to maintain scrollable history while
|
|
3878
|
-
* rendering in altscreen (which normally has no scrollback).
|
|
3885
|
+
* Render the search bar as a plain (no ANSI) string padded to `cols` width.
|
|
3886
|
+
* Format: " / query [2/15]" or " / query [no matches]" — padded with spaces.
|
|
3879
3887
|
*
|
|
3880
|
-
*
|
|
3888
|
+
* Caller decides how to display it — wrap with `\x1b[7m...\x1b[27m` for the
|
|
3889
|
+
* legacy overlay path, or stamp into a buffer's cells with the inverse
|
|
3890
|
+
* attribute for the compose+apply path
|
|
3891
|
+
* (see `applySearchBarToPaintBuffer` in `runtime/renderer.ts`).
|
|
3881
3892
|
*/
|
|
3882
|
-
function
|
|
3883
|
-
const
|
|
3884
|
-
const
|
|
3885
|
-
|
|
3886
|
-
let head = 0;
|
|
3887
|
-
let count = 0;
|
|
3888
|
-
return {
|
|
3889
|
-
push(lines) {
|
|
3890
|
-
for (const line of lines) {
|
|
3891
|
-
ansiLines[head] = line;
|
|
3892
|
-
plainLines[head] = stripAnsi(line);
|
|
3893
|
-
head = (head + 1) % maxLines;
|
|
3894
|
-
if (count < maxLines) count++;
|
|
3895
|
-
}
|
|
3896
|
-
},
|
|
3897
|
-
getVisibleRows(offset, rowCount) {
|
|
3898
|
-
const result = [];
|
|
3899
|
-
for (let i = 0; i < rowCount; i++) {
|
|
3900
|
-
const logicalIndex = count - offset - rowCount + i;
|
|
3901
|
-
if (logicalIndex < 0 || logicalIndex >= count) result.push("");
|
|
3902
|
-
else {
|
|
3903
|
-
const physical = (head - count + logicalIndex + maxLines) % maxLines;
|
|
3904
|
-
result.push(ansiLines[physical]);
|
|
3905
|
-
}
|
|
3906
|
-
}
|
|
3907
|
-
return result;
|
|
3908
|
-
},
|
|
3909
|
-
get totalLines() {
|
|
3910
|
-
return count;
|
|
3911
|
-
},
|
|
3912
|
-
search(query) {
|
|
3913
|
-
if (!query) return [];
|
|
3914
|
-
const lowerQuery = query.toLowerCase();
|
|
3915
|
-
const matches = [];
|
|
3916
|
-
for (let i = 0; i < count; i++) if (plainLines[(head - count + i + maxLines) % maxLines].toLowerCase().includes(lowerQuery)) matches.push(i);
|
|
3917
|
-
return matches;
|
|
3918
|
-
},
|
|
3919
|
-
clear() {
|
|
3920
|
-
head = 0;
|
|
3921
|
-
count = 0;
|
|
3922
|
-
}
|
|
3923
|
-
};
|
|
3893
|
+
function renderSearchBarPlain(state, cols) {
|
|
3894
|
+
const prefix = " / ";
|
|
3895
|
+
const matchInfo = state.matches.length > 0 ? ` [${state.currentMatch + 1}/${state.matches.length}]` : state.query ? " [no matches]" : "";
|
|
3896
|
+
return (prefix + state.query + matchInfo).padEnd(cols);
|
|
3924
3897
|
}
|
|
3925
3898
|
//#endregion
|
|
3926
|
-
export {
|
|
3899
|
+
export { DecMode as $, withMeasurer as A, createTerminalSelectionState as At, resolveNonTTYMode as B, resetScrollRegion as C, CursorProvider as Ct, supportsScrollRegions as D, createDragEvent as Dt, setScrollRegion as E, useCursor as Et, inspectTree as F, createInputEvents as G, Z_INDEX as H, isInspectorEnabled as I, resolveFromTerm as J, isTerm as K, countLines as L, disableInspector as M, terminalSelectionUpdate as Mt, enableInspector as N, createFocusManager as Nt, calcEdgeBasedScrollOffset as O, createDragState as Ot, inspectFrame as P, queryTextAreaSize as Q, createOutputTransformer as R, moveCursor as S, createCanvasAdapter as St, scrollUp as T, resetCursorState as Tt, generateHitRegionId as U, HitRegistry as V, resetHitRegionIdCounter as W, queryCellSize as X, resolveTermDef as Y, queryTextAreaPixels as Z, getTabOrder as _, findActiveCursorNode as _t, createTextSurface as a, queryTerminalVersion as at, splitPane as b, injectDOMStyles as bt, createHistoryBuffer as c, detectWidthConfigWithProbe as ct, applySelectionToBuffer as d, TERMTEST_SECTIONS as dt, queryMode as et, composeSearchHighlightCells as f, runTermtest as ft, getPaneIds as g, OSC133 as gt, findAdjacentPane as h, detectKittyWithProbe as ht, searchUpdate as i, querySecondaryDA as it, autoEnableInspector as j, extractText as jt, createPipeline as k, findDropTarget as kt, createHistoryItem as l, queryCursorFromStdio as lt, createLeaf as m, detectKittySupport as mt, createSearchState as n, queryDeviceAttributes as nt, createListDocument as o, queryTertiaryDA as ot, composeSelectionCells as p, detectKittyFromStdio as pt, isTermDef as q, renderSearchBarPlain as r, queryPrimaryDA as rt, composeViewport as s, applyWidthConfig as st, computeMatchRanges as t, queryModes as tt, createVirtualScrollback as u, queryCursorPosition as ut, removePane as v, DOMRenderBuffer as vt, scrollDown as w, createCursorStore as wt, swapPanes as x, CanvasRenderBuffer as xt, resizeSplit as y, createDOMAdapter as yt, isTTY as z };
|
|
3927
3900
|
|
|
3928
|
-
//# sourceMappingURL=src-
|
|
3901
|
+
//# sourceMappingURL=src-DbqcSrhy.mjs.map
|