@nuvin/ink 7.0.2-alpha → 7.1.1-alpha
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/build/components/App.d.ts +3 -1
- package/build/components/App.js +4 -2
- package/build/components/App.js.map +1 -1
- package/build/components/Text.d.ts +9 -1
- package/build/components/Text.js +2 -2
- package/build/components/Text.js.map +1 -1
- package/build/components/TextSelectionContext.d.ts +6 -0
- package/build/components/TextSelectionContext.js +7 -0
- package/build/components/TextSelectionContext.js.map +1 -0
- package/build/dom.d.ts +1 -0
- package/build/dom.js.map +1 -1
- package/build/hooks/use-text-selection-actions.d.ts +10 -0
- package/build/hooks/use-text-selection-actions.js +26 -0
- package/build/hooks/use-text-selection-actions.js.map +1 -0
- package/build/hooks/use-text-selection.d.ts +9 -0
- package/build/hooks/use-text-selection.js +26 -0
- package/build/hooks/use-text-selection.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/ink.d.ts +1 -0
- package/build/ink.js +12 -2
- package/build/ink.js.map +1 -1
- package/build/output.d.ts +9 -1
- package/build/output.js +31 -4
- package/build/output.js.map +1 -1
- package/build/paint-selection.d.ts +12 -2
- package/build/paint-selection.js +45 -98
- package/build/paint-selection.js.map +1 -1
- package/build/reconciler.js +8 -0
- package/build/reconciler.js.map +1 -1
- package/build/render-node-to-output.js +8 -2
- package/build/render-node-to-output.js.map +1 -1
- package/build/renderer.d.ts +2 -1
- package/build/renderer.js +13 -2
- package/build/renderer.js.map +1 -1
- package/build/text-selection-controller.d.ts +116 -0
- package/build/text-selection-controller.js +340 -0
- package/build/text-selection-controller.js.map +1 -0
- package/build/text-selection.d.ts +41 -0
- package/build/text-selection.js +101 -0
- package/build/text-selection.js.map +1 -0
- package/package.json +1 -1
- package/readme.md +47 -0
- package/build/current-frame.d.ts +0 -27
- package/build/current-frame.js +0 -73
- package/build/current-frame.js.map +0 -1
- package/build/hooks/use-selection-overlay.d.ts +0 -3
- package/build/hooks/use-selection-overlay.js +0 -14
- package/build/hooks/use-selection-overlay.js.map +0 -1
- package/build/selection.d.ts +0 -28
- package/build/selection.js +0 -9
- package/build/selection.js.map +0 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { type TextSelectionPoint, type TextSelectionSpan } from './text-selection.js';
|
|
2
|
+
/**
|
|
3
|
+
Live viewport of the selection container, in Ink output grid cells.
|
|
4
|
+
*/
|
|
5
|
+
export type SelectionViewport = {
|
|
6
|
+
top: number;
|
|
7
|
+
left: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
/** Vertical scroll offset in rendered rows: content row shown at `top`. */
|
|
11
|
+
scrollY: number;
|
|
12
|
+
};
|
|
13
|
+
export type TextSelectionSnapshot = {
|
|
14
|
+
/** Content coordinates (see TextSelectionPoint). */
|
|
15
|
+
anchor: TextSelectionPoint | null;
|
|
16
|
+
focus: TextSelectionPoint | null;
|
|
17
|
+
isDragging: boolean;
|
|
18
|
+
text: string;
|
|
19
|
+
isEmpty: boolean;
|
|
20
|
+
};
|
|
21
|
+
export type TextSelectionActions = {
|
|
22
|
+
/**
|
|
23
|
+
Points passed to start/update are 0-based SCREEN cells on the Ink output
|
|
24
|
+
grid; the controller converts to content space via the viewport provider.
|
|
25
|
+
*/
|
|
26
|
+
start: (screenPoint: {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
}) => void;
|
|
30
|
+
update: (screenPoint: {
|
|
31
|
+
x: number;
|
|
32
|
+
y: number;
|
|
33
|
+
}) => void;
|
|
34
|
+
finish: () => void;
|
|
35
|
+
clear: () => void;
|
|
36
|
+
/**
|
|
37
|
+
Registered by the mouse bridge. Providers stack: the most recently
|
|
38
|
+
registered one is active (e.g. a modal's selection area over the main
|
|
39
|
+
list's), and the returned unregister function restores the previous one.
|
|
40
|
+
A provider returning null means "bounds not measurable this frame".
|
|
41
|
+
*/
|
|
42
|
+
registerViewportProvider: (provider: () => SelectionViewport | null) => () => void;
|
|
43
|
+
/**
|
|
44
|
+
Single-slot convenience over registerViewportProvider: replaces the
|
|
45
|
+
provider set by the previous call (null unregisters it). Prefer
|
|
46
|
+
registerViewportProvider where mounts can overlap.
|
|
47
|
+
*/
|
|
48
|
+
setViewportProvider: (provider: (() => SelectionViewport | null) | null) => void;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
Selection actions plus an imperative snapshot read. Unlike the snapshot from
|
|
52
|
+
`useTextSelection()`, holding this handle does NOT subscribe the component to
|
|
53
|
+
selection changes — use it where selection state is only needed at event time
|
|
54
|
+
(mouse bridges, key handlers) so drags never re-render the host component.
|
|
55
|
+
*/
|
|
56
|
+
export type TextSelectionHandle = TextSelectionActions & {
|
|
57
|
+
getSnapshot: () => TextSelectionSnapshot;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
Framework-agnostic selection state owned by an Ink instance. Selection points
|
|
61
|
+
live in content space (container-relative rows that survive scrolling); the
|
|
62
|
+
viewport provider converts screen ↔ content at event time and paint time.
|
|
63
|
+
*/
|
|
64
|
+
export declare class TextSelectionController {
|
|
65
|
+
/** Wired by Ink to schedule a repaint when the highlight must change. */
|
|
66
|
+
onInvalidate?: () => void;
|
|
67
|
+
private anchor;
|
|
68
|
+
private focus;
|
|
69
|
+
private isDragging;
|
|
70
|
+
private text;
|
|
71
|
+
private readonly rowCache;
|
|
72
|
+
private readonly viewportProviders;
|
|
73
|
+
private legacyProviderUnregister;
|
|
74
|
+
private lastViewport;
|
|
75
|
+
private readonly listeners;
|
|
76
|
+
private snapshot;
|
|
77
|
+
private notifyScheduled;
|
|
78
|
+
subscribe: (listener: () => void) => (() => void);
|
|
79
|
+
getSnapshot: () => TextSelectionSnapshot;
|
|
80
|
+
registerViewportProvider: (provider: () => SelectionViewport | null) => (() => void);
|
|
81
|
+
setViewportProvider: (provider: (() => SelectionViewport | null) | null) => void;
|
|
82
|
+
start: (screenPoint: {
|
|
83
|
+
x: number;
|
|
84
|
+
y: number;
|
|
85
|
+
}) => void;
|
|
86
|
+
update: (screenPoint: {
|
|
87
|
+
x: number;
|
|
88
|
+
y: number;
|
|
89
|
+
}) => void;
|
|
90
|
+
finish: () => void;
|
|
91
|
+
clear: () => void;
|
|
92
|
+
/**
|
|
93
|
+
Called from the renderer's paint hook on every render of the main output.
|
|
94
|
+
Caches visible rows under content-row keys. Never invalidates synchronously
|
|
95
|
+
— it runs during a render pass and must not schedule another render.
|
|
96
|
+
|
|
97
|
+
While dragging, cache entries update freely (auto-scroll depends on it).
|
|
98
|
+
After `finish()`, the cache is frozen: existing entries are never
|
|
99
|
+
overwritten, so extracted text is immutable. If a visible row inside the
|
|
100
|
+
selected range no longer matches its frozen entry (content reflowed
|
|
101
|
+
underneath the selection), the selection auto-clears.
|
|
102
|
+
*/
|
|
103
|
+
captureRows: (plainRows: readonly string[], maskRows?: ReadonlyArray<readonly boolean[]>) => void;
|
|
104
|
+
/**
|
|
105
|
+
Convert the selection's content spans to screen spans using the current
|
|
106
|
+
viewport. Rows outside the viewport or the grid are dropped (they are
|
|
107
|
+
still part of the extracted text).
|
|
108
|
+
*/
|
|
109
|
+
getPaintSpans: (outputWidth: number, outputHeight: number) => TextSelectionSpan[];
|
|
110
|
+
private resolveViewport;
|
|
111
|
+
private toContentPoint;
|
|
112
|
+
/** Returns true when the extracted text changed. */
|
|
113
|
+
private recomputeText;
|
|
114
|
+
private emit;
|
|
115
|
+
private scheduleNotify;
|
|
116
|
+
}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-restricted-types --
|
|
2
|
+
The public selection API uses null (matching React ref idioms and the
|
|
3
|
+
mouse bridge in ink-input); keep it consistent throughout this file. */
|
|
4
|
+
import stringWidth from 'string-width';
|
|
5
|
+
import { extractTextFromRows, getSelectionSpans, } from './text-selection.js';
|
|
6
|
+
const fullGridViewport = {
|
|
7
|
+
top: 0,
|
|
8
|
+
left: 0,
|
|
9
|
+
width: Number.POSITIVE_INFINITY,
|
|
10
|
+
height: Number.POSITIVE_INFINITY,
|
|
11
|
+
scrollY: 0,
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
Framework-agnostic selection state owned by an Ink instance. Selection points
|
|
15
|
+
live in content space (container-relative rows that survive scrolling); the
|
|
16
|
+
viewport provider converts screen ↔ content at event time and paint time.
|
|
17
|
+
*/
|
|
18
|
+
export class TextSelectionController {
|
|
19
|
+
/** Wired by Ink to schedule a repaint when the highlight must change. */
|
|
20
|
+
onInvalidate;
|
|
21
|
+
anchor = null;
|
|
22
|
+
focus = null;
|
|
23
|
+
isDragging = false;
|
|
24
|
+
text = '';
|
|
25
|
+
rowCache = new Map();
|
|
26
|
+
// Stack of registered providers; the last entry is active. Stacking lets
|
|
27
|
+
// an overlay (tool detail modal) take over selection while mounted and
|
|
28
|
+
// hand coordinates back to the underlying list on unmount.
|
|
29
|
+
viewportProviders = [];
|
|
30
|
+
legacyProviderUnregister = null;
|
|
31
|
+
// Last viewport a registered provider returned. Reused for one-frame gaps
|
|
32
|
+
// where the provider returns null (e.g. bounds momentarily 0 mid-re-render),
|
|
33
|
+
// so the highlight doesn't flash to full-grid coordinates and the frozen
|
|
34
|
+
// row cache isn't compared against a differently-sliced viewport.
|
|
35
|
+
lastViewport = null;
|
|
36
|
+
listeners = new Set();
|
|
37
|
+
snapshot;
|
|
38
|
+
notifyScheduled = false;
|
|
39
|
+
subscribe = (listener) => {
|
|
40
|
+
this.listeners.add(listener);
|
|
41
|
+
return () => {
|
|
42
|
+
this.listeners.delete(listener);
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
getSnapshot = () => {
|
|
46
|
+
this.snapshot ??= {
|
|
47
|
+
anchor: this.anchor,
|
|
48
|
+
focus: this.focus,
|
|
49
|
+
isDragging: this.isDragging,
|
|
50
|
+
text: this.text,
|
|
51
|
+
isEmpty: this.text.length === 0,
|
|
52
|
+
};
|
|
53
|
+
return this.snapshot;
|
|
54
|
+
};
|
|
55
|
+
registerViewportProvider = (provider) => {
|
|
56
|
+
this.viewportProviders.push(provider);
|
|
57
|
+
// The coordinate space changed under any live selection; drop it
|
|
58
|
+
// rather than paint it through the wrong viewport, and forget the
|
|
59
|
+
// previous provider's cached viewport.
|
|
60
|
+
this.lastViewport = null;
|
|
61
|
+
if (this.anchor !== null) {
|
|
62
|
+
this.clear();
|
|
63
|
+
}
|
|
64
|
+
return () => {
|
|
65
|
+
const index = this.viewportProviders.indexOf(provider);
|
|
66
|
+
if (index === -1) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.viewportProviders.splice(index, 1);
|
|
70
|
+
this.lastViewport = null;
|
|
71
|
+
if (this.anchor !== null) {
|
|
72
|
+
this.clear();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
setViewportProvider = (provider) => {
|
|
77
|
+
this.legacyProviderUnregister?.();
|
|
78
|
+
this.legacyProviderUnregister = provider
|
|
79
|
+
? this.registerViewportProvider(provider)
|
|
80
|
+
: null;
|
|
81
|
+
};
|
|
82
|
+
start = (screenPoint) => {
|
|
83
|
+
const point = this.toContentPoint(screenPoint);
|
|
84
|
+
this.anchor = point;
|
|
85
|
+
this.focus = point;
|
|
86
|
+
this.isDragging = true;
|
|
87
|
+
this.recomputeText();
|
|
88
|
+
this.emit();
|
|
89
|
+
this.onInvalidate?.();
|
|
90
|
+
};
|
|
91
|
+
update = (screenPoint) => {
|
|
92
|
+
if (!this.anchor) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const point = this.toContentPoint(screenPoint);
|
|
96
|
+
const focusChanged = this.focus?.x !== point.x || this.focus.y !== point.y;
|
|
97
|
+
this.focus = point;
|
|
98
|
+
const textChanged = this.recomputeText();
|
|
99
|
+
if (focusChanged || textChanged) {
|
|
100
|
+
this.emit();
|
|
101
|
+
this.onInvalidate?.();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
finish = () => {
|
|
105
|
+
if (!this.isDragging) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
this.isDragging = false;
|
|
109
|
+
this.emit();
|
|
110
|
+
};
|
|
111
|
+
clear = () => {
|
|
112
|
+
const hadSelection = this.anchor !== null || this.text.length > 0 || this.isDragging;
|
|
113
|
+
this.anchor = null;
|
|
114
|
+
this.focus = null;
|
|
115
|
+
this.isDragging = false;
|
|
116
|
+
this.text = '';
|
|
117
|
+
this.rowCache.clear();
|
|
118
|
+
if (hadSelection) {
|
|
119
|
+
this.emit();
|
|
120
|
+
this.onInvalidate?.();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
Called from the renderer's paint hook on every render of the main output.
|
|
125
|
+
Caches visible rows under content-row keys. Never invalidates synchronously
|
|
126
|
+
— it runs during a render pass and must not schedule another render.
|
|
127
|
+
|
|
128
|
+
While dragging, cache entries update freely (auto-scroll depends on it).
|
|
129
|
+
After `finish()`, the cache is frozen: existing entries are never
|
|
130
|
+
overwritten, so extracted text is immutable. If a visible row inside the
|
|
131
|
+
selected range no longer matches its frozen entry (content reflowed
|
|
132
|
+
underneath the selection), the selection auto-clears.
|
|
133
|
+
*/
|
|
134
|
+
captureRows = (plainRows, maskRows) => {
|
|
135
|
+
const resolved = this.resolveViewport();
|
|
136
|
+
// A registered provider that returns null means the container's bounds are
|
|
137
|
+
// not measurable this frame (transient during a re-render). Capturing or
|
|
138
|
+
// comparing against the full-grid fallback would corrupt the cache and
|
|
139
|
+
// spuriously auto-clear a finished selection. Skip the frame instead.
|
|
140
|
+
if (!resolved.available && this.anchor !== null) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const { viewport } = resolved;
|
|
144
|
+
const top = Math.max(0, viewport.top);
|
|
145
|
+
const left = Math.max(0, viewport.left);
|
|
146
|
+
const bottom = Math.min(plainRows.length, viewport.top + viewport.height);
|
|
147
|
+
const hasSelection = this.anchor !== null && this.focus !== null;
|
|
148
|
+
const frozen = hasSelection && !this.isDragging;
|
|
149
|
+
// Without a selection there is nothing to preserve: reset the cache to
|
|
150
|
+
// the currently visible rows so a subsequent start() has fresh data and
|
|
151
|
+
// stale rows from earlier scroll positions don't accumulate.
|
|
152
|
+
if (!hasSelection) {
|
|
153
|
+
this.rowCache.clear();
|
|
154
|
+
}
|
|
155
|
+
let mismatch = false;
|
|
156
|
+
const range = hasSelection
|
|
157
|
+
? (() => {
|
|
158
|
+
const [a, b] = [this.anchor, this.focus];
|
|
159
|
+
return a.y <= b.y ? { min: a.y, max: b.y } : { min: b.y, max: a.y };
|
|
160
|
+
})()
|
|
161
|
+
: undefined;
|
|
162
|
+
for (let y = top; y < bottom; y++) {
|
|
163
|
+
const contentY = viewport.scrollY + (y - viewport.top);
|
|
164
|
+
const text = sliceRow(plainRows[y] ?? '', left, viewport.width);
|
|
165
|
+
const mask = maskRows
|
|
166
|
+
? sliceMask(maskRows[y], left, viewport.width)
|
|
167
|
+
: undefined;
|
|
168
|
+
const row = { text, mask };
|
|
169
|
+
const existing = this.rowCache.get(contentY);
|
|
170
|
+
if (frozen && existing) {
|
|
171
|
+
if (range &&
|
|
172
|
+
contentY >= range.min &&
|
|
173
|
+
contentY <= range.max &&
|
|
174
|
+
existing.text !== text) {
|
|
175
|
+
mismatch = true;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
this.rowCache.set(contentY, row);
|
|
181
|
+
}
|
|
182
|
+
if (mismatch) {
|
|
183
|
+
// Content reflowed underneath a finished selection: clearing is the
|
|
184
|
+
// only honest option. Defer the notification outside the render pass.
|
|
185
|
+
this.anchor = null;
|
|
186
|
+
this.focus = null;
|
|
187
|
+
this.isDragging = false;
|
|
188
|
+
this.text = '';
|
|
189
|
+
this.rowCache.clear();
|
|
190
|
+
this.snapshot = undefined;
|
|
191
|
+
this.scheduleNotify(true);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (hasSelection && !frozen && this.recomputeText()) {
|
|
195
|
+
this.scheduleNotify(false);
|
|
196
|
+
}
|
|
197
|
+
else if (hasSelection &&
|
|
198
|
+
frozen &&
|
|
199
|
+
this.text.length === 0 && // Rows may have arrived only after the gesture ended.
|
|
200
|
+
this.recomputeText()) {
|
|
201
|
+
this.scheduleNotify(false);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
Convert the selection's content spans to screen spans using the current
|
|
206
|
+
viewport. Rows outside the viewport or the grid are dropped (they are
|
|
207
|
+
still part of the extracted text).
|
|
208
|
+
*/
|
|
209
|
+
getPaintSpans = (outputWidth, outputHeight) => {
|
|
210
|
+
if (!this.anchor || !this.focus) {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
const { viewport } = this.resolveViewport();
|
|
214
|
+
const spans = getSelectionSpans(this.anchor, this.focus, y => this.rowCache.get(y));
|
|
215
|
+
const left = Math.max(0, viewport.left);
|
|
216
|
+
const right = Math.min(outputWidth, viewport.left + viewport.width);
|
|
217
|
+
const screenTop = Math.max(0, viewport.top);
|
|
218
|
+
const screenBottom = Math.min(outputHeight, viewport.top + viewport.height);
|
|
219
|
+
const result = [];
|
|
220
|
+
for (const span of spans) {
|
|
221
|
+
const screenY = viewport.top + (span.y - viewport.scrollY);
|
|
222
|
+
if (screenY < screenTop || screenY >= screenBottom) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const x1 = Math.max(left, viewport.left + span.x1);
|
|
226
|
+
const x2 = Math.min(right, viewport.left + span.x2);
|
|
227
|
+
if (x2 <= x1) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
result.push({ y: screenY, x1, x2 });
|
|
231
|
+
}
|
|
232
|
+
return result;
|
|
233
|
+
};
|
|
234
|
+
resolveViewport() {
|
|
235
|
+
const provider = this.viewportProviders.at(-1);
|
|
236
|
+
if (!provider) {
|
|
237
|
+
// No scroll container: the whole grid is the viewport.
|
|
238
|
+
return { viewport: fullGridViewport, available: true };
|
|
239
|
+
}
|
|
240
|
+
const current = provider();
|
|
241
|
+
if (current) {
|
|
242
|
+
this.lastViewport = current;
|
|
243
|
+
return { viewport: current, available: true };
|
|
244
|
+
}
|
|
245
|
+
// Provider registered but bounds unavailable this frame. Reuse the last
|
|
246
|
+
// known viewport so the highlight stays put; report unavailable so
|
|
247
|
+
// captureRows skips the frame.
|
|
248
|
+
return {
|
|
249
|
+
viewport: this.lastViewport ?? fullGridViewport,
|
|
250
|
+
available: false,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
toContentPoint(screenPoint) {
|
|
254
|
+
const { viewport } = this.resolveViewport();
|
|
255
|
+
const maxX = viewport.width === Number.POSITIVE_INFINITY
|
|
256
|
+
? Number.POSITIVE_INFINITY
|
|
257
|
+
: viewport.left + viewport.width - 1;
|
|
258
|
+
const maxY = viewport.height === Number.POSITIVE_INFINITY
|
|
259
|
+
? Number.POSITIVE_INFINITY
|
|
260
|
+
: viewport.top + viewport.height - 1;
|
|
261
|
+
const x = Math.min(Math.max(screenPoint.x, viewport.left), maxX);
|
|
262
|
+
const y = Math.min(Math.max(screenPoint.y, viewport.top), maxY);
|
|
263
|
+
return {
|
|
264
|
+
x: x - viewport.left,
|
|
265
|
+
y: y - viewport.top + viewport.scrollY,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/** Returns true when the extracted text changed. */
|
|
269
|
+
recomputeText() {
|
|
270
|
+
const next = this.anchor && this.focus
|
|
271
|
+
? extractTextFromRows(this.anchor, this.focus, y => this.rowCache.get(y))
|
|
272
|
+
: '';
|
|
273
|
+
if (next === this.text) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
this.text = next;
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
emit() {
|
|
280
|
+
this.snapshot = undefined;
|
|
281
|
+
for (const listener of this.listeners) {
|
|
282
|
+
listener();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
scheduleNotify(invalidate) {
|
|
286
|
+
this.snapshot = undefined;
|
|
287
|
+
if (this.notifyScheduled) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
this.notifyScheduled = true;
|
|
291
|
+
queueMicrotask(() => {
|
|
292
|
+
this.notifyScheduled = false;
|
|
293
|
+
for (const listener of this.listeners) {
|
|
294
|
+
listener();
|
|
295
|
+
}
|
|
296
|
+
if (invalidate) {
|
|
297
|
+
this.onInvalidate?.();
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Slice a row by display cells, not string indices, so wide characters keep
|
|
303
|
+
// the text aligned with the cell-indexed mask. A wide glyph straddling the
|
|
304
|
+
// viewport edge is replaced by spaces for the cells that remain visible.
|
|
305
|
+
const sliceRow = (row, left, width) => {
|
|
306
|
+
if (left === 0 && width === Number.POSITIVE_INFINITY) {
|
|
307
|
+
return row.trimEnd();
|
|
308
|
+
}
|
|
309
|
+
const right = width === Number.POSITIVE_INFINITY
|
|
310
|
+
? Number.POSITIVE_INFINITY
|
|
311
|
+
: left + width;
|
|
312
|
+
let cell = 0;
|
|
313
|
+
let result = '';
|
|
314
|
+
for (const char of row) {
|
|
315
|
+
const charWidth = Math.max(1, stringWidth(char));
|
|
316
|
+
const start = cell;
|
|
317
|
+
const end = cell + charWidth;
|
|
318
|
+
cell = end;
|
|
319
|
+
if (end <= left) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (start >= right) {
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
result +=
|
|
326
|
+
start >= left && end <= right
|
|
327
|
+
? char
|
|
328
|
+
: ' '.repeat(Math.min(end, right) - Math.max(start, left));
|
|
329
|
+
}
|
|
330
|
+
return result.trimEnd();
|
|
331
|
+
};
|
|
332
|
+
const sliceMask = (mask, left, width) => {
|
|
333
|
+
if (!mask) {
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
return width === Number.POSITIVE_INFINITY
|
|
337
|
+
? mask.slice(left)
|
|
338
|
+
: mask.slice(left, left + width);
|
|
339
|
+
};
|
|
340
|
+
//# sourceMappingURL=text-selection-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-selection-controller.js","sourceRoot":"","sources":["../src/text-selection-controller.ts"],"names":[],"mappings":"AAAA;;0EAE0E;AAC1E,OAAO,WAAW,MAAM,cAAc,CAAC;AACvC,OAAO,EACN,mBAAmB,EACnB,iBAAiB,GAIjB,MAAM,qBAAqB,CAAC;AA6D7B,MAAM,gBAAgB,GAAsB;IAC3C,GAAG,EAAE,CAAC;IACN,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,MAAM,CAAC,iBAAiB;IAC/B,MAAM,EAAE,MAAM,CAAC,iBAAiB;IAChC,OAAO,EAAE,CAAC;CACV,CAAC;AAEF;;;;EAIE;AACF,MAAM,OAAO,uBAAuB;IACnC,yEAAyE;IACzE,YAAY,CAAc;IAElB,MAAM,GAA8B,IAAI,CAAC;IACzC,KAAK,GAA8B,IAAI,CAAC;IACxC,UAAU,GAAG,KAAK,CAAC;IACnB,IAAI,GAAG,EAAE,CAAC;IACD,QAAQ,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC5D,yEAAyE;IACzE,uEAAuE;IACvE,2DAA2D;IAC1C,iBAAiB,GACjC,EAAE,CAAC;IAEI,wBAAwB,GAAwB,IAAI,CAAC;IAC7D,0EAA0E;IAC1E,6EAA6E;IAC7E,yEAAyE;IACzE,kEAAkE;IAC1D,YAAY,GAA6B,IAAI,CAAC;IAErC,SAAS,GAAG,IAAI,GAAG,EAAc,CAAC;IAC3C,QAAQ,CAAoC;IAC5C,eAAe,GAAG,KAAK,CAAC;IAEhC,SAAS,GAAG,CAAC,QAAoB,EAAgB,EAAE;QAClD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,GAAG,EAAE;YACX,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC,CAAC;IACH,CAAC,CAAC;IAEF,WAAW,GAAG,GAA0B,EAAE;QACzC,IAAI,CAAC,QAAQ,KAAK;YACjB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;SAC/B,CAAC;QAEF,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC,CAAC;IAEF,wBAAwB,GAAG,CAC1B,QAAwC,EACzB,EAAE;QACjB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtC,iEAAiE;QACjE,kEAAkE;QAClE,uCAAuC;QACvC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;YAC1B,IAAI,CAAC,KAAK,EAAE,CAAC;QACd,CAAC;QAED,OAAO,GAAG,EAAE;YACX,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YACvD,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;gBAClB,OAAO;YACR,CAAC;YAED,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YACxC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;YACzB,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;gBAC1B,IAAI,CAAC,KAAK,EAAE,CAAC;YACd,CAAC;QACF,CAAC,CAAC;IACH,CAAC,CAAC;IAEF,mBAAmB,GAAG,CACrB,QAAiD,EAC1C,EAAE;QACT,IAAI,CAAC,wBAAwB,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC,wBAAwB,GAAG,QAAQ;YACvC,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,QAAQ,CAAC;YACzC,CAAC,CAAC,IAAI,CAAC;IACT,CAAC,CAAC;IAEF,KAAK,GAAG,CAAC,WAAmC,EAAQ,EAAE;QACrD,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QAC/C,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;IACvB,CAAC,CAAC;IAEF,MAAM,GAAG,CAAC,WAAmC,EAAQ,EAAE;QACtD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YAClB,OAAO;QACR,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QAC/C,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC;QAC3E,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QAEzC,IAAI,YAAY,IAAI,WAAW,EAAE,CAAC;YACjC,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;QACvB,CAAC;IACF,CAAC,CAAC;IAEF,MAAM,GAAG,GAAS,EAAE;QACnB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACtB,OAAO;QACR,CAAC;QAED,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QACxB,IAAI,CAAC,IAAI,EAAE,CAAC;IACb,CAAC,CAAC;IAEF,KAAK,GAAG,GAAS,EAAE;QAClB,MAAM,YAAY,GACjB,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC;QACjE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;QACf,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QAEtB,IAAI,YAAY,EAAE,CAAC;YAClB,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;QACvB,CAAC;IACF,CAAC,CAAC;IAEF;;;;;;;;;;MAUE;IACF,WAAW,GAAG,CACb,SAA4B,EAC5B,QAA4C,EACrC,EAAE;QACT,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QAExC,2EAA2E;QAC3E,yEAAyE;QACzE,uEAAuE;QACvE,sEAAsE;QACtE,IAAI,CAAC,QAAQ,CAAC,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;YACjD,OAAO;QACR,CAAC;QAED,MAAM,EAAC,QAAQ,EAAC,GAAG,QAAQ,CAAC;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAE1E,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC;QACjE,MAAM,MAAM,GAAG,YAAY,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC;QAEhD,uEAAuE;QACvE,wEAAwE;QACxE,6DAA6D;QAC7D,IAAI,CAAC,YAAY,EAAE,CAAC;YACnB,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;QAED,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,MAAM,KAAK,GAAG,YAAY;YACzB,CAAC,CAAC,CAAC,GAAG,EAAE;gBACN,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAO,EAAE,IAAI,CAAC,KAAM,CAAC,CAAC;gBAC3C,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,EAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAC,CAAC;YACjE,CAAC,CAAC,EAAE;YACL,CAAC,CAAC,SAAS,CAAC;QAEb,KAAK,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;YACvD,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;YAChE,MAAM,IAAI,GAAG,QAAQ;gBACpB,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC;gBAC9C,CAAC,CAAC,SAAS,CAAC;YACb,MAAM,GAAG,GAAiB,EAAC,IAAI,EAAE,IAAI,EAAC,CAAC;YAEvC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAE7C,IAAI,MAAM,IAAI,QAAQ,EAAE,CAAC;gBACxB,IACC,KAAK;oBACL,QAAQ,IAAI,KAAK,CAAC,GAAG;oBACrB,QAAQ,IAAI,KAAK,CAAC,GAAG;oBACrB,QAAQ,CAAC,IAAI,KAAK,IAAI,EACrB,CAAC;oBACF,QAAQ,GAAG,IAAI,CAAC;oBAChB,MAAM;gBACP,CAAC;gBAED,SAAS;YACV,CAAC;YAED,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QAClC,CAAC;QAED,IAAI,QAAQ,EAAE,CAAC;YACd,oEAAoE;YACpE,sEAAsE;YACtE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACnB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAClB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;YACxB,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;YACf,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACtB,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;YAC1B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;YAC1B,OAAO;QACR,CAAC;QAED,IAAI,YAAY,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;YACrD,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;aAAM,IACN,YAAY;YACZ,MAAM;YACN,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,sDAAsD;YAChF,IAAI,CAAC,aAAa,EAAE,EACnB,CAAC;YACF,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACF,CAAC,CAAC;IAEF;;;;MAIE;IACF,aAAa,GAAG,CACf,WAAmB,EACnB,YAAoB,EACE,EAAE;QACxB,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACjC,OAAO,EAAE,CAAC;QACX,CAAC;QAED,MAAM,EAAC,QAAQ,EAAC,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAC5D,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CACpB,CAAC;QAEF,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,QAAQ,CAAC,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QACpE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC5C,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,QAAQ,CAAC,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAE5E,MAAM,MAAM,GAAwB,EAAE,CAAC;QAEvC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;YAC3D,IAAI,OAAO,GAAG,SAAS,IAAI,OAAO,IAAI,YAAY,EAAE,CAAC;gBACpD,SAAS;YACV,CAAC;YAED,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;YACnD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;YACpD,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;gBACd,SAAS;YACV,CAAC;YAED,MAAM,CAAC,IAAI,CAAC,EAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAC,CAAC,CAAC;QACnC,CAAC;QAED,OAAO,MAAM,CAAC;IACf,CAAC,CAAC;IAEM,eAAe;QAItB,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACf,uDAAuD;YACvD,OAAO,EAAC,QAAQ,EAAE,gBAAgB,EAAE,SAAS,EAAE,IAAI,EAAC,CAAC;QACtD,CAAC;QAED,MAAM,OAAO,GAAG,QAAQ,EAAE,CAAC;QAC3B,IAAI,OAAO,EAAE,CAAC;YACb,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC;YAC5B,OAAO,EAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAC,CAAC;QAC7C,CAAC;QAED,wEAAwE;QACxE,mEAAmE;QACnE,+BAA+B;QAC/B,OAAO;YACN,QAAQ,EAAE,IAAI,CAAC,YAAY,IAAI,gBAAgB;YAC/C,SAAS,EAAE,KAAK;SAChB,CAAC;IACH,CAAC;IAEO,cAAc,CAAC,WAGtB;QACA,MAAM,EAAC,QAAQ,EAAC,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QAC1C,MAAM,IAAI,GACT,QAAQ,CAAC,KAAK,KAAK,MAAM,CAAC,iBAAiB;YAC1C,CAAC,CAAC,MAAM,CAAC,iBAAiB;YAC1B,CAAC,CAAC,QAAQ,CAAC,IAAI,GAAG,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC;QACvC,MAAM,IAAI,GACT,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,iBAAiB;YAC3C,CAAC,CAAC,MAAM,CAAC,iBAAiB;YAC1B,CAAC,CAAC,QAAQ,CAAC,GAAG,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;QAEvC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;QACjE,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;QAEhE,OAAO;YACN,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,IAAI;YACpB,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,GAAG,GAAG,QAAQ,CAAC,OAAO;SACtC,CAAC;IACH,CAAC;IAED,oDAAoD;IAC5C,aAAa;QACpB,MAAM,IAAI,GACT,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK;YACxB,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CACjD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CACpB;YACF,CAAC,CAAC,EAAE,CAAC;QAEP,IAAI,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;YACxB,OAAO,KAAK,CAAC;QACd,CAAC;QAED,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,OAAO,IAAI,CAAC;IACb,CAAC;IAEO,IAAI;QACX,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;QAC1B,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACvC,QAAQ,EAAE,CAAC;QACZ,CAAC;IACF,CAAC;IAEO,cAAc,CAAC,UAAmB;QACzC,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;QAE1B,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAC1B,OAAO;QACR,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC5B,cAAc,CAAC,GAAG,EAAE;YACnB,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;YAC7B,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;gBACvC,QAAQ,EAAE,CAAC;YACZ,CAAC;YAED,IAAI,UAAU,EAAE,CAAC;gBAChB,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACvB,CAAC;QACF,CAAC,CAAC,CAAC;IACJ,CAAC;CACD;AAED,4EAA4E;AAC5E,2EAA2E;AAC3E,yEAAyE;AACzE,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAE,IAAY,EAAE,KAAa,EAAU,EAAE;IACrE,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,MAAM,CAAC,iBAAiB,EAAE,CAAC;QACtD,OAAO,GAAG,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;IAED,MAAM,KAAK,GACV,KAAK,KAAK,MAAM,CAAC,iBAAiB;QACjC,CAAC,CAAC,MAAM,CAAC,iBAAiB;QAC1B,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC;IACjB,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,MAAM,GAAG,EAAE,CAAC;IAEhB,KAAK,MAAM,IAAI,IAAI,GAAG,EAAE,CAAC;QACxB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QACjD,MAAM,KAAK,GAAG,IAAI,CAAC;QACnB,MAAM,GAAG,GAAG,IAAI,GAAG,SAAS,CAAC;QAC7B,IAAI,GAAG,GAAG,CAAC;QAEX,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;YACjB,SAAS;QACV,CAAC;QAED,IAAI,KAAK,IAAI,KAAK,EAAE,CAAC;YACpB,MAAM;QACP,CAAC;QAED,MAAM;YACL,KAAK,IAAI,IAAI,IAAI,GAAG,IAAI,KAAK;gBAC5B,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC;AACzB,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,CACjB,IAAoC,EACpC,IAAY,EACZ,KAAa,EACoB,EAAE;IACnC,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,OAAO,KAAK,KAAK,MAAM,CAAC,iBAAiB;QACxC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;QAClB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC,CAAC;AACnC,CAAC,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
A point in container content space: `x` is cells from the container's content
|
|
3
|
+
left edge, `y` is the content row index (survives scrolling).
|
|
4
|
+
*/
|
|
5
|
+
export type TextSelectionPoint = {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
};
|
|
9
|
+
export type TextSelectionSpan = {
|
|
10
|
+
y: number;
|
|
11
|
+
x1: number;
|
|
12
|
+
x2: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
A single cached row of rendered content. `mask` flags which display cells are
|
|
16
|
+
selectable (written by `<Text>` and not opted out); a missing mask means every
|
|
17
|
+
cell is selectable.
|
|
18
|
+
*/
|
|
19
|
+
export type SelectionRow = {
|
|
20
|
+
text: string;
|
|
21
|
+
mask?: readonly boolean[];
|
|
22
|
+
};
|
|
23
|
+
export type RowLookup = (y: number) => SelectionRow | undefined;
|
|
24
|
+
export declare function normalizeSelection(anchor: TextSelectionPoint, focus: TextSelectionPoint): {
|
|
25
|
+
start: TextSelectionPoint;
|
|
26
|
+
end: TextSelectionPoint;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
One past the last printable selectable cell of the row. Trailing whitespace
|
|
30
|
+
and trailing chrome cells are excluded.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getRowEnd(row: SelectionRow): number;
|
|
33
|
+
export declare function getSelectionSpans(anchor: TextSelectionPoint, focus: TextSelectionPoint, getRow: RowLookup): TextSelectionSpan[];
|
|
34
|
+
/**
|
|
35
|
+
Extract the selectable text of `row` between display cells `[from, to)`.
|
|
36
|
+
Non-selectable runs at the edges are dropped; interior non-selectable runs
|
|
37
|
+
collapse to a single space. Wide glyphs are included when the range overlaps
|
|
38
|
+
either of their cells.
|
|
39
|
+
*/
|
|
40
|
+
export declare function sliceSelectableCells(row: SelectionRow, from: number, to: number): string;
|
|
41
|
+
export declare function extractTextFromRows(anchor: TextSelectionPoint, focus: TextSelectionPoint, getRow: RowLookup): string;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import stringWidth from 'string-width';
|
|
2
|
+
export function normalizeSelection(anchor, focus) {
|
|
3
|
+
if (anchor.y < focus.y || (anchor.y === focus.y && anchor.x <= focus.x)) {
|
|
4
|
+
return { start: anchor, end: focus };
|
|
5
|
+
}
|
|
6
|
+
return { start: focus, end: anchor };
|
|
7
|
+
}
|
|
8
|
+
const isCharSelectable = (mask, charStart, charEnd) => {
|
|
9
|
+
if (!mask) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
for (let cell = charStart; cell < charEnd; cell++) {
|
|
13
|
+
if (mask[cell] === false) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
One past the last printable selectable cell of the row. Trailing whitespace
|
|
21
|
+
and trailing chrome cells are excluded.
|
|
22
|
+
*/
|
|
23
|
+
export function getRowEnd(row) {
|
|
24
|
+
const { text, mask } = row;
|
|
25
|
+
let cell = 0;
|
|
26
|
+
let end = 0;
|
|
27
|
+
for (const char of text) {
|
|
28
|
+
const width = Math.max(1, stringWidth(char));
|
|
29
|
+
const charStart = cell;
|
|
30
|
+
const charEnd = cell + width;
|
|
31
|
+
cell = charEnd;
|
|
32
|
+
if (/^\s*$/.test(char)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (isCharSelectable(mask, charStart, charEnd)) {
|
|
36
|
+
end = charEnd;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return end;
|
|
40
|
+
}
|
|
41
|
+
export function getSelectionSpans(anchor, focus, getRow) {
|
|
42
|
+
const { start, end } = normalizeSelection(anchor, focus);
|
|
43
|
+
if (start.x === end.x && start.y === end.y) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
const spans = [];
|
|
47
|
+
for (let { y } = start; y <= end.y; y++) {
|
|
48
|
+
const rowEnd = getRowEnd(getRow(y) ?? { text: '' });
|
|
49
|
+
let x1 = y === start.y ? start.x : 0;
|
|
50
|
+
let x2 = y === end.y ? end.x : rowEnd;
|
|
51
|
+
x1 = Math.max(0, Math.min(x1, rowEnd));
|
|
52
|
+
x2 = Math.max(0, Math.min(x2, rowEnd));
|
|
53
|
+
// Emit empty spans (x1 === x2) so blank lines survive extraction.
|
|
54
|
+
spans.push({ y, x1, x2 });
|
|
55
|
+
}
|
|
56
|
+
return spans;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
Extract the selectable text of `row` between display cells `[from, to)`.
|
|
60
|
+
Non-selectable runs at the edges are dropped; interior non-selectable runs
|
|
61
|
+
collapse to a single space. Wide glyphs are included when the range overlaps
|
|
62
|
+
either of their cells.
|
|
63
|
+
*/
|
|
64
|
+
export function sliceSelectableCells(row, from, to) {
|
|
65
|
+
if (to <= from) {
|
|
66
|
+
return '';
|
|
67
|
+
}
|
|
68
|
+
const { text, mask } = row;
|
|
69
|
+
let cell = 0;
|
|
70
|
+
let result = '';
|
|
71
|
+
let pendingGap = false;
|
|
72
|
+
for (const char of text) {
|
|
73
|
+
const width = Math.max(1, stringWidth(char));
|
|
74
|
+
const charStart = cell;
|
|
75
|
+
const charEnd = cell + width;
|
|
76
|
+
cell = charEnd;
|
|
77
|
+
if (charEnd <= from) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (charStart >= to) {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
if (isCharSelectable(mask, charStart, charEnd)) {
|
|
84
|
+
if (pendingGap && result.length > 0) {
|
|
85
|
+
result += ' ';
|
|
86
|
+
}
|
|
87
|
+
pendingGap = false;
|
|
88
|
+
result += char;
|
|
89
|
+
}
|
|
90
|
+
else if (result.length > 0) {
|
|
91
|
+
pendingGap = true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
export function extractTextFromRows(anchor, focus, getRow) {
|
|
97
|
+
return getSelectionSpans(anchor, focus, getRow)
|
|
98
|
+
.map(span => sliceSelectableCells(getRow(span.y) ?? { text: '' }, span.x1, span.x2))
|
|
99
|
+
.join('\n');
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=text-selection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-selection.js","sourceRoot":"","sources":["../src/text-selection.ts"],"names":[],"mappings":"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"}
|