@jrichman/ink 6.6.8 → 7.0.0-beta
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/Box.js +7 -4
- package/build/components/Box.js.map +1 -1
- package/build/components/StaticRender.d.ts +1 -1
- package/build/components/StaticRender.js +19 -7
- package/build/components/StaticRender.js.map +1 -1
- package/build/dom.d.ts +8 -3
- package/build/dom.js +43 -13
- package/build/dom.js.map +1 -1
- package/build/get-max-width.js +1 -1
- package/build/get-max-width.js.map +1 -1
- package/build/ink.d.ts +1 -0
- package/build/ink.js +61 -5
- package/build/ink.js.map +1 -1
- package/build/measure-element.js +25 -16
- package/build/measure-element.js.map +1 -1
- package/build/measure-text.js +8 -3
- package/build/measure-text.js.map +1 -1
- package/build/output.d.ts +60 -2
- package/build/output.js +259 -66
- package/build/output.js.map +1 -1
- package/build/reconciler.js +36 -6
- package/build/reconciler.js.map +1 -1
- package/build/render-background.js +2 -2
- package/build/render-background.js.map +1 -1
- package/build/render-border.js +2 -2
- package/build/render-border.js.map +1 -1
- package/build/render-container.js +2 -4
- package/build/render-container.js.map +1 -1
- package/build/render-node-to-output.js +163 -40
- package/build/render-node-to-output.js.map +1 -1
- package/build/render-sticky.d.ts +3 -3
- package/build/render-sticky.js +18 -18
- package/build/render-sticky.js.map +1 -1
- package/build/renderer.d.ts +1 -0
- package/build/renderer.js +58 -14
- package/build/renderer.js.map +1 -1
- package/build/resize-observer.js +4 -4
- package/build/resize-observer.js.map +1 -1
- package/build/scroll.js +3 -3
- package/build/scroll.js.map +1 -1
- package/build/serialization.js +1 -1
- package/build/serialization.js.map +1 -1
- package/build/squash-text-nodes.js +15 -6
- package/build/squash-text-nodes.js.map +1 -1
- package/build/styled-line.d.ts +8 -0
- package/build/styled-line.js +438 -141
- package/build/styled-line.js.map +1 -1
- package/build/terminal-buffer.d.ts +1 -0
- package/build/terminal-buffer.js +198 -51
- package/build/terminal-buffer.js.map +1 -1
- package/build/tokenize.d.ts +1 -0
- package/build/tokenize.js +146 -0
- package/build/tokenize.js.map +1 -1
- package/build/worker/animation-controller.js +1 -1
- package/build/worker/animation-controller.js.map +1 -1
- package/build/worker/canvas.js +15 -3
- package/build/worker/canvas.js.map +1 -1
- package/build/worker/compositor.js +2 -1
- package/build/worker/compositor.js.map +1 -1
- package/build/worker/render-worker.d.ts +15 -5
- package/build/worker/render-worker.js +34 -28
- package/build/worker/render-worker.js.map +1 -1
- package/build/worker/scene-manager.d.ts +1 -1
- package/build/worker/scene-manager.js +28 -23
- package/build/worker/scene-manager.js.map +1 -1
- package/build/worker/scroll-optimizer.d.ts +1 -1
- package/build/worker/scroll-optimizer.js +3 -3
- package/build/worker/scroll-optimizer.js.map +1 -1
- package/build/worker/terminal-writer.d.ts +0 -1
- package/build/worker/terminal-writer.js +13 -8
- package/build/worker/terminal-writer.js.map +1 -1
- package/build/worker/worker-entry.js +1 -2
- package/build/worker/worker-entry.js.map +1 -1
- package/package.json +3 -3
- package/readme.md +27 -8
- package/build/wrap-text.d.ts +0 -6
- package/build/wrap-text.js +0 -120
- package/build/wrap-text.js.map +0 -1
package/build/styled-line.js
CHANGED
|
@@ -6,6 +6,33 @@
|
|
|
6
6
|
import { FULL_WIDTH_MASK, INVERSE_MASK } from './tokenize.js';
|
|
7
7
|
const OFFSET_MASK = 0x3f_ff_ff_ff;
|
|
8
8
|
const FULL_WIDTH_FLAG = 0x40_00_00_00;
|
|
9
|
+
function hasAnyStyles(formatFlags, fgColor, bgColor, link) {
|
|
10
|
+
return ((formatFlags & ~FULL_WIDTH_MASK) !== 0 ||
|
|
11
|
+
fgColor !== undefined ||
|
|
12
|
+
bgColor !== undefined ||
|
|
13
|
+
link !== undefined);
|
|
14
|
+
}
|
|
15
|
+
function spanHasStyles(span) {
|
|
16
|
+
return hasAnyStyles(span.formatFlags, span.fgColor, span.bgColor, span.link);
|
|
17
|
+
}
|
|
18
|
+
function spansHaveStyles(spans) {
|
|
19
|
+
if (!spans)
|
|
20
|
+
return false;
|
|
21
|
+
for (const s of spans) {
|
|
22
|
+
if (spanHasStyles(s))
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
function isDefaultCharData(charData, length) {
|
|
28
|
+
if (!charData)
|
|
29
|
+
return true;
|
|
30
|
+
for (let i = 0; i < length; i++) {
|
|
31
|
+
if (charData[i] !== i)
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
9
36
|
export class StyledLine {
|
|
10
37
|
static empty(length) {
|
|
11
38
|
if (length <= 0) {
|
|
@@ -18,17 +45,10 @@ export class StyledLine {
|
|
|
18
45
|
const line = new StyledLine();
|
|
19
46
|
line.length = length;
|
|
20
47
|
line.text = ' '.repeat(length);
|
|
21
|
-
line.charData = Array.from({ length });
|
|
22
|
-
for (let i = 0; i < length; i++) {
|
|
23
|
-
line.charData[i] = i;
|
|
24
|
-
}
|
|
25
|
-
line.spans = [{ length, formatFlags: 0 }];
|
|
26
48
|
line._cachedTrimmedLength = 0;
|
|
27
49
|
if (StyledLine.emptyCache.size > 100) {
|
|
28
50
|
StyledLine.emptyCache.clear();
|
|
29
51
|
}
|
|
30
|
-
Object.freeze(line.spans[0]);
|
|
31
|
-
Object.freeze(line.spans);
|
|
32
52
|
Object.freeze(line);
|
|
33
53
|
StyledLine.emptyCache.set(length, line);
|
|
34
54
|
return line.clone();
|
|
@@ -44,12 +64,50 @@ export class StyledLine {
|
|
|
44
64
|
charData;
|
|
45
65
|
spans;
|
|
46
66
|
_cachedTrimmedLength;
|
|
67
|
+
_spansDirty = false;
|
|
47
68
|
constructor() {
|
|
48
69
|
this.length = 0;
|
|
49
70
|
}
|
|
71
|
+
padTo(targetLength) {
|
|
72
|
+
if (targetLength <= this.length)
|
|
73
|
+
return;
|
|
74
|
+
this._cachedTrimmedLength = undefined;
|
|
75
|
+
this.ensureInitialized();
|
|
76
|
+
const diff = targetLength - this.length;
|
|
77
|
+
if (this.charData === undefined) {
|
|
78
|
+
this.text += ' '.repeat(diff);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const offset = this.text.length;
|
|
82
|
+
this.text += ' '.repeat(diff);
|
|
83
|
+
for (let i = 0; i < diff; i++) {
|
|
84
|
+
this.charData.push(offset + i);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (this.spans !== undefined) {
|
|
88
|
+
const lastSpan = this.spans.at(-1);
|
|
89
|
+
if (lastSpan &&
|
|
90
|
+
lastSpan.formatFlags === 0 &&
|
|
91
|
+
lastSpan.fgColor === undefined &&
|
|
92
|
+
lastSpan.bgColor === undefined &&
|
|
93
|
+
lastSpan.link === undefined) {
|
|
94
|
+
lastSpan.length += diff;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
this.spans.push({
|
|
98
|
+
length: diff,
|
|
99
|
+
formatFlags: 0,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
this.length = targetLength;
|
|
104
|
+
}
|
|
50
105
|
getValue(index) {
|
|
51
106
|
if (this.text === undefined || index < 0 || index >= this.length)
|
|
52
107
|
return '';
|
|
108
|
+
if (this.charData === undefined) {
|
|
109
|
+
return this.text[index];
|
|
110
|
+
}
|
|
53
111
|
const start = this.charData[index] & OFFSET_MASK;
|
|
54
112
|
const end = index + 1 < this.length
|
|
55
113
|
? this.charData[index + 1] & OFFSET_MASK
|
|
@@ -73,13 +131,12 @@ export class StyledLine {
|
|
|
73
131
|
return (this.charData[index] & FULL_WIDTH_FLAG) !== 0;
|
|
74
132
|
}
|
|
75
133
|
hasStyles(index) {
|
|
134
|
+
if (this.spans === undefined)
|
|
135
|
+
return false;
|
|
76
136
|
const span = this.getSpan(index);
|
|
77
137
|
if (!span)
|
|
78
138
|
return false;
|
|
79
|
-
return (
|
|
80
|
-
span.fgColor !== undefined ||
|
|
81
|
-
span.bgColor !== undefined ||
|
|
82
|
-
span.link !== undefined);
|
|
139
|
+
return spanHasStyles(span);
|
|
83
140
|
}
|
|
84
141
|
getFormatFlags(index) {
|
|
85
142
|
let flags = this.getSpan(index)?.formatFlags ?? 0;
|
|
@@ -102,6 +159,7 @@ export class StyledLine {
|
|
|
102
159
|
return;
|
|
103
160
|
this._cachedTrimmedLength = undefined;
|
|
104
161
|
this.ensureInitialized();
|
|
162
|
+
this.ensureSpans();
|
|
105
163
|
this.splitSpansAt(index);
|
|
106
164
|
this.splitSpansAt(index + 1);
|
|
107
165
|
let current = 0;
|
|
@@ -117,13 +175,14 @@ export class StyledLine {
|
|
|
117
175
|
}
|
|
118
176
|
current += span.length;
|
|
119
177
|
}
|
|
120
|
-
this.
|
|
178
|
+
this._spansDirty = true;
|
|
121
179
|
}
|
|
122
180
|
setBackgroundColor(index, color) {
|
|
123
181
|
if (index < 0 || index >= this.length)
|
|
124
182
|
return;
|
|
125
183
|
this._cachedTrimmedLength = undefined;
|
|
126
184
|
this.ensureInitialized();
|
|
185
|
+
this.ensureSpans();
|
|
127
186
|
this.splitSpansAt(index);
|
|
128
187
|
this.splitSpansAt(index + 1);
|
|
129
188
|
let current = 0;
|
|
@@ -134,13 +193,14 @@ export class StyledLine {
|
|
|
134
193
|
}
|
|
135
194
|
current += span.length;
|
|
136
195
|
}
|
|
137
|
-
this.
|
|
196
|
+
this._spansDirty = true;
|
|
138
197
|
}
|
|
139
198
|
setForegroundColor(index, color) {
|
|
140
199
|
if (index < 0 || index >= this.length)
|
|
141
200
|
return;
|
|
142
201
|
this._cachedTrimmedLength = undefined;
|
|
143
202
|
this.ensureInitialized();
|
|
203
|
+
this.ensureSpans();
|
|
144
204
|
this.splitSpansAt(index);
|
|
145
205
|
this.splitSpansAt(index + 1);
|
|
146
206
|
let current = 0;
|
|
@@ -151,7 +211,26 @@ export class StyledLine {
|
|
|
151
211
|
}
|
|
152
212
|
current += span.length;
|
|
153
213
|
}
|
|
154
|
-
this.
|
|
214
|
+
this._spansDirty = true;
|
|
215
|
+
}
|
|
216
|
+
replaceAt(index, chars) {
|
|
217
|
+
if (chars.length === 0 || index >= this.length)
|
|
218
|
+
return;
|
|
219
|
+
this.ensureInitialized();
|
|
220
|
+
chars.ensureInitialized();
|
|
221
|
+
const start = Math.max(0, index);
|
|
222
|
+
const end = Math.min(this.length, start + chars.length);
|
|
223
|
+
const replacementLen = end - start;
|
|
224
|
+
const slicedChars = chars.length > replacementLen ? chars.slice(0, replacementLen) : chars;
|
|
225
|
+
const left = this.slice(0, start);
|
|
226
|
+
const right = this.slice(end);
|
|
227
|
+
const combined = left.combine(slicedChars, right);
|
|
228
|
+
this.length = combined.length;
|
|
229
|
+
this.text = combined.text;
|
|
230
|
+
this.charData = combined.charData;
|
|
231
|
+
this.spans = combined.spans;
|
|
232
|
+
this._spansDirty = combined._spansDirty;
|
|
233
|
+
this._cachedTrimmedLength = undefined;
|
|
155
234
|
}
|
|
156
235
|
// eslint-disable-next-line max-params
|
|
157
236
|
setChar(index, value, formatFlags, fgColor, bgColor, link) {
|
|
@@ -161,26 +240,105 @@ export class StyledLine {
|
|
|
161
240
|
this.ensureInitialized();
|
|
162
241
|
const isFullWidth = (formatFlags & FULL_WIDTH_MASK) !== 0;
|
|
163
242
|
const cleanFormatFlags = formatFlags & ~FULL_WIDTH_MASK;
|
|
164
|
-
const
|
|
165
|
-
const end = index + 1 < this.length
|
|
166
|
-
? this.charData[index + 1] & OFFSET_MASK
|
|
167
|
-
: this.text.length;
|
|
168
|
-
const oldLen = end - start;
|
|
243
|
+
const hasStyles = hasAnyStyles(formatFlags, fgColor, bgColor, link);
|
|
169
244
|
const newLen = value.length;
|
|
170
|
-
if (
|
|
171
|
-
|
|
245
|
+
if (this.charData === undefined) {
|
|
246
|
+
if (newLen === 1 && !isFullWidth) {
|
|
247
|
+
this.text =
|
|
248
|
+
this.text.slice(0, index) + value + this.text.slice(index + 1);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
this.ensureCharData();
|
|
252
|
+
}
|
|
172
253
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
this.
|
|
254
|
+
if (this.charData !== undefined) {
|
|
255
|
+
const start = this.charData[index] & OFFSET_MASK;
|
|
256
|
+
const end = index + 1 < this.length
|
|
257
|
+
? this.charData[index + 1] & OFFSET_MASK
|
|
258
|
+
: this.text.length;
|
|
259
|
+
const oldLen = end - start;
|
|
260
|
+
if (oldLen === newLen) {
|
|
261
|
+
this.text = this.text.slice(0, start) + value + this.text.slice(end);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
this.text = this.text.slice(0, start) + value + this.text.slice(end);
|
|
265
|
+
const diff = newLen - oldLen;
|
|
266
|
+
for (let i = index + 1; i < this.length; i++) {
|
|
267
|
+
const data = this.charData[i];
|
|
268
|
+
const oldOffset = data & OFFSET_MASK;
|
|
269
|
+
const fw = data & FULL_WIDTH_FLAG;
|
|
270
|
+
this.charData[i] = (oldOffset + diff) | fw;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
this.charData[index] = start | (isFullWidth ? FULL_WIDTH_FLAG : 0);
|
|
274
|
+
}
|
|
275
|
+
if (this.spans === undefined) {
|
|
276
|
+
if (!hasStyles) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
this.ensureSpans();
|
|
280
|
+
}
|
|
281
|
+
// Fast path: Find the span containing this index
|
|
282
|
+
let currentOffset = 0;
|
|
283
|
+
let spanIndex = -1;
|
|
284
|
+
let span;
|
|
285
|
+
for (let i = 0; i < this.spans.length; i++) {
|
|
286
|
+
const s = this.spans[i];
|
|
287
|
+
if (currentOffset <= index && currentOffset + s.length > index) {
|
|
288
|
+
spanIndex = i;
|
|
289
|
+
span = s;
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
currentOffset += s.length;
|
|
293
|
+
}
|
|
294
|
+
if (span) {
|
|
295
|
+
const isMatch = span.formatFlags === cleanFormatFlags &&
|
|
296
|
+
span.fgColor === fgColor &&
|
|
297
|
+
span.bgColor === bgColor &&
|
|
298
|
+
span.link === link;
|
|
299
|
+
if (isMatch) {
|
|
300
|
+
return; // Style already matches, no need to touch spans
|
|
301
|
+
}
|
|
302
|
+
// If it's the very first character of the span, check if we can merge with the previous span
|
|
303
|
+
if (index === currentOffset && spanIndex > 0) {
|
|
304
|
+
const prevSpan = this.spans[spanIndex - 1];
|
|
305
|
+
const prevMatch = prevSpan.formatFlags === cleanFormatFlags &&
|
|
306
|
+
prevSpan.fgColor === fgColor &&
|
|
307
|
+
prevSpan.bgColor === bgColor &&
|
|
308
|
+
prevSpan.link === link;
|
|
309
|
+
if (prevMatch) {
|
|
310
|
+
prevSpan.length += 1;
|
|
311
|
+
if (span.length === 1) {
|
|
312
|
+
this.spans.splice(spanIndex, 1);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
span.length -= 1;
|
|
316
|
+
}
|
|
317
|
+
this._spansDirty = true;
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// If it's the very last character of the span, check if we can merge with the next span
|
|
322
|
+
if (index === currentOffset + span.length - 1 &&
|
|
323
|
+
spanIndex < this.spans.length - 1) {
|
|
324
|
+
const nextSpan = this.spans[spanIndex + 1];
|
|
325
|
+
const nextMatch = nextSpan.formatFlags === cleanFormatFlags &&
|
|
326
|
+
nextSpan.fgColor === fgColor &&
|
|
327
|
+
nextSpan.bgColor === bgColor &&
|
|
328
|
+
nextSpan.link === link;
|
|
329
|
+
if (nextMatch) {
|
|
330
|
+
nextSpan.length += 1;
|
|
331
|
+
if (span.length === 1) {
|
|
332
|
+
this.spans.splice(spanIndex, 1);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
span.length -= 1;
|
|
336
|
+
}
|
|
337
|
+
this._spansDirty = true;
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
181
340
|
}
|
|
182
341
|
}
|
|
183
|
-
this.charData[index] = start | (isFullWidth ? FULL_WIDTH_FLAG : 0);
|
|
184
342
|
this.splitSpansAt(index);
|
|
185
343
|
this.splitSpansAt(index + 1);
|
|
186
344
|
let current = 0;
|
|
@@ -198,7 +356,7 @@ export class StyledLine {
|
|
|
198
356
|
}
|
|
199
357
|
current += span.length;
|
|
200
358
|
}
|
|
201
|
-
this.
|
|
359
|
+
this._spansDirty = true;
|
|
202
360
|
}
|
|
203
361
|
// eslint-disable-next-line max-params
|
|
204
362
|
pushChar(value, formatFlags, fgColor, bgColor, link) {
|
|
@@ -206,9 +364,28 @@ export class StyledLine {
|
|
|
206
364
|
this.ensureInitialized();
|
|
207
365
|
const isFullWidth = (formatFlags & FULL_WIDTH_MASK) !== 0;
|
|
208
366
|
const cleanFormatFlags = formatFlags & ~FULL_WIDTH_MASK;
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
this.charData
|
|
367
|
+
const hasStyles = hasAnyStyles(formatFlags, fgColor, bgColor, link);
|
|
368
|
+
const newLen = value.length;
|
|
369
|
+
if (this.charData === undefined) {
|
|
370
|
+
if (newLen === 1 && !isFullWidth) {
|
|
371
|
+
this.text += value;
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
this.ensureCharData();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (this.charData !== undefined) {
|
|
378
|
+
const offset = this.text.length;
|
|
379
|
+
this.text += value;
|
|
380
|
+
this.charData.push(offset | (isFullWidth ? FULL_WIDTH_FLAG : 0));
|
|
381
|
+
}
|
|
382
|
+
if (this.spans === undefined) {
|
|
383
|
+
if (!hasStyles) {
|
|
384
|
+
this.length++;
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
this.ensureSpans();
|
|
388
|
+
}
|
|
212
389
|
const lastSpan = this.spans.at(-1);
|
|
213
390
|
if (lastSpan &&
|
|
214
391
|
lastSpan.formatFlags === cleanFormatFlags &&
|
|
@@ -229,19 +406,17 @@ export class StyledLine {
|
|
|
229
406
|
this.length++;
|
|
230
407
|
}
|
|
231
408
|
clone() {
|
|
232
|
-
|
|
233
|
-
return new StyledLine();
|
|
409
|
+
this.ensureSpansMerged();
|
|
234
410
|
const result = new StyledLine();
|
|
235
411
|
result.length = this.length;
|
|
236
412
|
result.text = this.text;
|
|
237
|
-
result.charData = [...this.charData];
|
|
238
|
-
result.spans = this.spans.map(span => ({ ...span }));
|
|
413
|
+
result.charData = this.charData ? [...this.charData] : undefined;
|
|
414
|
+
result.spans = this.spans ? this.spans.map(span => ({ ...span })) : undefined;
|
|
239
415
|
result._cachedTrimmedLength = this._cachedTrimmedLength;
|
|
240
416
|
return result;
|
|
241
417
|
}
|
|
242
418
|
slice(start, end) {
|
|
243
|
-
|
|
244
|
-
return new StyledLine();
|
|
419
|
+
this.ensureSpansMerged();
|
|
245
420
|
const actualStart = Math.max(0, start);
|
|
246
421
|
const actualEnd = end === undefined ? this.length : Math.min(this.length, end);
|
|
247
422
|
if (actualStart >= actualEnd)
|
|
@@ -251,40 +426,53 @@ export class StyledLine {
|
|
|
251
426
|
}
|
|
252
427
|
const result = new StyledLine();
|
|
253
428
|
result.length = actualEnd - actualStart;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
: this.text.length;
|
|
259
|
-
result.text = this.text.slice(textStart, textEnd);
|
|
260
|
-
for (let i = 0; i < result.length; i++) {
|
|
261
|
-
const oldData = this.charData[actualStart + i];
|
|
262
|
-
const oldOffset = oldData & OFFSET_MASK;
|
|
263
|
-
const fw = oldData & FULL_WIDTH_FLAG;
|
|
264
|
-
result.charData[i] = (oldOffset - textStart) | fw;
|
|
429
|
+
if (this.charData === undefined) {
|
|
430
|
+
if (this.text !== undefined) {
|
|
431
|
+
result.text = this.text.slice(actualStart, actualEnd);
|
|
432
|
+
}
|
|
265
433
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
434
|
+
else {
|
|
435
|
+
result.charData = Array.from({ length: result.length });
|
|
436
|
+
const textStart = this.charData[actualStart] & OFFSET_MASK;
|
|
437
|
+
const textEnd = actualEnd < this.length
|
|
438
|
+
? this.charData[actualEnd] & OFFSET_MASK
|
|
439
|
+
: this.text.length;
|
|
440
|
+
result.text = this.text.slice(textStart, textEnd);
|
|
441
|
+
for (let i = 0; i < result.length; i++) {
|
|
442
|
+
const oldData = this.charData[actualStart + i];
|
|
443
|
+
const oldOffset = oldData & OFFSET_MASK;
|
|
444
|
+
const fw = oldData & FULL_WIDTH_FLAG;
|
|
445
|
+
result.charData[i] = (oldOffset - textStart) | fw;
|
|
278
446
|
}
|
|
279
|
-
current += span.length;
|
|
280
|
-
if (current >= actualEnd)
|
|
281
|
-
break;
|
|
282
447
|
}
|
|
283
|
-
|
|
284
|
-
|
|
448
|
+
if (this.spans !== undefined) {
|
|
449
|
+
const newSpans = [];
|
|
450
|
+
let current = 0;
|
|
451
|
+
for (const span of this.spans) {
|
|
452
|
+
const spanStart = current;
|
|
453
|
+
const spanEnd = current + span.length;
|
|
454
|
+
const intersectStart = Math.max(actualStart, spanStart);
|
|
455
|
+
const intersectEnd = Math.min(actualEnd, spanEnd);
|
|
456
|
+
if (intersectStart < intersectEnd) {
|
|
457
|
+
newSpans.push({
|
|
458
|
+
...span,
|
|
459
|
+
length: intersectEnd - intersectStart,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
current += span.length;
|
|
463
|
+
if (current >= actualEnd)
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
result.spans = newSpans;
|
|
467
|
+
result.mergeSpans();
|
|
468
|
+
}
|
|
285
469
|
return result;
|
|
286
470
|
}
|
|
287
471
|
combine(...others) {
|
|
472
|
+
this.ensureSpansMerged();
|
|
473
|
+
for (const other of others) {
|
|
474
|
+
other.ensureSpansMerged();
|
|
475
|
+
}
|
|
288
476
|
if (others.length === 0)
|
|
289
477
|
return this.clone();
|
|
290
478
|
const allLines = [this, ...others].filter(l => l.length > 0);
|
|
@@ -299,61 +487,103 @@ export class StyledLine {
|
|
|
299
487
|
const result = new StyledLine();
|
|
300
488
|
result.length = totalChars;
|
|
301
489
|
result.text = allLines.map(l => l.getText()).join('');
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
490
|
+
const anyHasCharData = allLines.some(l => l.charData !== undefined);
|
|
491
|
+
if (anyHasCharData) {
|
|
492
|
+
result.charData = Array.from({ length: totalChars });
|
|
493
|
+
let currentChar = 0;
|
|
494
|
+
let currentOffset = 0;
|
|
495
|
+
for (const line of allLines) {
|
|
496
|
+
const lineCharData = line.charData;
|
|
497
|
+
const lineText = line.getText();
|
|
498
|
+
if (lineCharData) {
|
|
499
|
+
for (let i = 0; i < line.length; i++) {
|
|
500
|
+
const data = lineCharData[i];
|
|
501
|
+
const offset = data & OFFSET_MASK;
|
|
502
|
+
const fw = data & FULL_WIDTH_FLAG;
|
|
503
|
+
result.charData[currentChar + i] = (currentOffset + offset) | fw;
|
|
504
|
+
}
|
|
314
505
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
506
|
+
else {
|
|
507
|
+
for (let i = 0; i < line.length; i++) {
|
|
508
|
+
result.charData[currentChar + i] = currentOffset + i;
|
|
509
|
+
}
|
|
319
510
|
}
|
|
511
|
+
currentChar += line.length;
|
|
512
|
+
currentOffset += lineText.length;
|
|
320
513
|
}
|
|
321
|
-
currentChar += line.length;
|
|
322
|
-
currentOffset += lineText.length;
|
|
323
514
|
}
|
|
324
|
-
|
|
325
|
-
|
|
515
|
+
const anyHasSpans = allLines.some(l => l.spans !== undefined);
|
|
516
|
+
if (anyHasSpans) {
|
|
517
|
+
result.spans = allLines.flatMap(l => l.getSpans().map(s => ({ ...s })));
|
|
518
|
+
result.mergeSpans();
|
|
519
|
+
}
|
|
326
520
|
return result;
|
|
327
521
|
}
|
|
328
522
|
getTrimmedLength() {
|
|
329
|
-
if (this.
|
|
523
|
+
if (this._cachedTrimmedLength !== undefined) {
|
|
524
|
+
return this._cachedTrimmedLength;
|
|
525
|
+
}
|
|
526
|
+
if (this.length === 0) {
|
|
527
|
+
this._cachedTrimmedLength = 0;
|
|
330
528
|
return 0;
|
|
331
|
-
|
|
529
|
+
}
|
|
530
|
+
if (this.text === undefined) {
|
|
531
|
+
this._cachedTrimmedLength = 0;
|
|
332
532
|
return 0;
|
|
533
|
+
}
|
|
534
|
+
this.ensureSpansMerged();
|
|
333
535
|
let currentIdx = this.length - 1;
|
|
536
|
+
let trimmedLength = 0;
|
|
334
537
|
if (this.spans) {
|
|
335
538
|
for (let s = this.spans.length - 1; s >= 0; s--) {
|
|
336
539
|
const span = this.spans[s];
|
|
337
|
-
const hasStyles = (span
|
|
338
|
-
span.fgColor !== undefined ||
|
|
339
|
-
span.bgColor !== undefined ||
|
|
340
|
-
span.link !== undefined;
|
|
540
|
+
const hasStyles = spanHasStyles(span);
|
|
341
541
|
if (hasStyles) {
|
|
342
|
-
|
|
542
|
+
trimmedLength = currentIdx + 1;
|
|
543
|
+
break;
|
|
343
544
|
}
|
|
344
545
|
for (let i = 0; i < span.length; i++) {
|
|
546
|
+
if (this.charData) {
|
|
547
|
+
const start = this.charData[currentIdx] & OFFSET_MASK;
|
|
548
|
+
const end = currentIdx + 1 < this.length
|
|
549
|
+
? this.charData[currentIdx + 1] & OFFSET_MASK
|
|
550
|
+
: this.text.length;
|
|
551
|
+
if (end - start !== 1 || this.text[start] !== ' ') {
|
|
552
|
+
trimmedLength = currentIdx + 1;
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
else if (this.text[currentIdx] !== ' ') {
|
|
557
|
+
trimmedLength = currentIdx + 1;
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
currentIdx--;
|
|
561
|
+
}
|
|
562
|
+
if (trimmedLength > 0)
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
while (currentIdx >= 0) {
|
|
568
|
+
if (this.charData) {
|
|
345
569
|
const start = this.charData[currentIdx] & OFFSET_MASK;
|
|
346
570
|
const end = currentIdx + 1 < this.length
|
|
347
571
|
? this.charData[currentIdx + 1] & OFFSET_MASK
|
|
348
572
|
: this.text.length;
|
|
349
573
|
if (end - start !== 1 || this.text[start] !== ' ') {
|
|
350
|
-
|
|
574
|
+
trimmedLength = currentIdx + 1;
|
|
575
|
+
break;
|
|
351
576
|
}
|
|
352
|
-
currentIdx--;
|
|
353
577
|
}
|
|
578
|
+
else if (this.text[currentIdx] !== ' ') {
|
|
579
|
+
trimmedLength = currentIdx + 1;
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
currentIdx--;
|
|
354
583
|
}
|
|
355
584
|
}
|
|
356
|
-
|
|
585
|
+
this._cachedTrimmedLength = trimmedLength;
|
|
586
|
+
return trimmedLength;
|
|
357
587
|
}
|
|
358
588
|
trimEnd() {
|
|
359
589
|
const trimmedLength = this.getTrimmedLength();
|
|
@@ -370,24 +600,44 @@ export class StyledLine {
|
|
|
370
600
|
return true;
|
|
371
601
|
if (this.getText() !== other.getText())
|
|
372
602
|
return false;
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
603
|
+
this.ensureSpansMerged();
|
|
604
|
+
other.ensureSpansMerged();
|
|
605
|
+
const thisSpans = this.internalGetSpans();
|
|
606
|
+
const otherSpans = other.internalGetSpans();
|
|
607
|
+
if (thisSpans === undefined && otherSpans !== undefined) {
|
|
608
|
+
if (spansHaveStyles(otherSpans))
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
else if (thisSpans !== undefined && otherSpans === undefined) {
|
|
612
|
+
if (spansHaveStyles(thisSpans))
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
else if (thisSpans !== undefined && otherSpans !== undefined) {
|
|
616
|
+
if (thisSpans.length !== otherSpans.length)
|
|
385
617
|
return false;
|
|
618
|
+
for (let i = 0; i < thisSpans.length; i++) {
|
|
619
|
+
const sp1 = thisSpans[i];
|
|
620
|
+
const sp2 = otherSpans[i];
|
|
621
|
+
if (sp1.length !== sp2.length ||
|
|
622
|
+
sp1.formatFlags !== sp2.formatFlags ||
|
|
623
|
+
sp1.fgColor !== sp2.fgColor ||
|
|
624
|
+
sp1.bgColor !== sp2.bgColor ||
|
|
625
|
+
sp1.link !== sp2.link) {
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
386
628
|
}
|
|
387
629
|
}
|
|
388
|
-
const thisCharData = this.
|
|
389
|
-
const otherCharData = other.
|
|
390
|
-
if (thisCharData && otherCharData) {
|
|
630
|
+
const thisCharData = this.internalGetCharData();
|
|
631
|
+
const otherCharData = other.internalGetCharData();
|
|
632
|
+
if (thisCharData === undefined && otherCharData !== undefined) {
|
|
633
|
+
if (!isDefaultCharData(otherCharData, this.length))
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
else if (thisCharData !== undefined && otherCharData === undefined) {
|
|
637
|
+
if (!isDefaultCharData(thisCharData, this.length))
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
else if (thisCharData !== undefined && otherCharData !== undefined) {
|
|
391
641
|
for (let i = 0; i < this.length; i++) {
|
|
392
642
|
if (thisCharData[i] !== otherCharData[i])
|
|
393
643
|
return false;
|
|
@@ -399,7 +649,12 @@ export class StyledLine {
|
|
|
399
649
|
return this.text ?? '';
|
|
400
650
|
}
|
|
401
651
|
getSpans() {
|
|
402
|
-
|
|
652
|
+
this.ensureSpansMerged();
|
|
653
|
+
if (this.spans !== undefined)
|
|
654
|
+
return this.spans;
|
|
655
|
+
if (this.length > 0)
|
|
656
|
+
return [{ length: this.length, formatFlags: 0 }];
|
|
657
|
+
return [];
|
|
403
658
|
}
|
|
404
659
|
getValues() {
|
|
405
660
|
return Array.from({ length: this.length }, (_, i) => this.getValue(i));
|
|
@@ -435,18 +690,36 @@ export class StyledLine {
|
|
|
435
690
|
}
|
|
436
691
|
}
|
|
437
692
|
}
|
|
693
|
+
internalGetCharData() {
|
|
694
|
+
return this.charData;
|
|
695
|
+
}
|
|
696
|
+
internalGetSpans() {
|
|
697
|
+
return this.spans;
|
|
698
|
+
}
|
|
699
|
+
ensureSpansMerged() {
|
|
700
|
+
if (this._spansDirty) {
|
|
701
|
+
this.mergeSpans();
|
|
702
|
+
this._spansDirty = false;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
438
705
|
ensureInitialized() {
|
|
706
|
+
if (this.text === undefined) {
|
|
707
|
+
this.text = this.length > 0 ? ' '.repeat(this.length) : '';
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
ensureCharData() {
|
|
711
|
+
this.ensureInitialized();
|
|
439
712
|
if (this.charData === undefined) {
|
|
440
|
-
this.text = '';
|
|
441
713
|
this.charData = Array.from({ length: this.length });
|
|
714
|
+
for (let i = 0; i < this.length; i++) {
|
|
715
|
+
this.charData[i] = i;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
ensureSpans() {
|
|
720
|
+
if (this.spans === undefined) {
|
|
442
721
|
this.spans =
|
|
443
722
|
this.length > 0 ? [{ length: this.length, formatFlags: 0 }] : [];
|
|
444
|
-
if (this.length > 0 && this.text.length === 0) {
|
|
445
|
-
this.text = ' '.repeat(this.length);
|
|
446
|
-
for (let i = 0; i < this.length; i++) {
|
|
447
|
-
this.charData[i] = i;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
723
|
}
|
|
451
724
|
}
|
|
452
725
|
applyValuesAndSpans(values, spans) {
|
|
@@ -454,36 +727,60 @@ export class StyledLine {
|
|
|
454
727
|
const visibleChars = values.length;
|
|
455
728
|
this.length = visibleChars;
|
|
456
729
|
this.text = values.join('');
|
|
457
|
-
|
|
458
|
-
let currentOffset = 0;
|
|
459
|
-
let spanIdx = 0;
|
|
460
|
-
let spanPos = 0;
|
|
730
|
+
let needsCharData = false;
|
|
461
731
|
for (let i = 0; i < visibleChars; i++) {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
732
|
+
if (values[i].length !== 1) {
|
|
733
|
+
needsCharData = true;
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (!needsCharData && spans.length > 0) {
|
|
738
|
+
for (const span of spans) {
|
|
466
739
|
if ((span.formatFlags & FULL_WIDTH_MASK) !== 0) {
|
|
467
|
-
|
|
740
|
+
needsCharData = true;
|
|
741
|
+
break;
|
|
468
742
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if (needsCharData) {
|
|
746
|
+
this.charData = Array.from({ length: this.length });
|
|
747
|
+
let currentOffset = 0;
|
|
748
|
+
let spanIdx = 0;
|
|
749
|
+
let spanPos = 0;
|
|
750
|
+
for (let i = 0; i < visibleChars; i++) {
|
|
751
|
+
const val = values[i];
|
|
752
|
+
let isFullWidth = false;
|
|
753
|
+
if (spans.length > 0 && spanIdx < spans.length) {
|
|
754
|
+
const span = spans[spanIdx];
|
|
755
|
+
if ((span.formatFlags & FULL_WIDTH_MASK) !== 0) {
|
|
756
|
+
isFullWidth = true;
|
|
757
|
+
}
|
|
758
|
+
spanPos++;
|
|
759
|
+
if (spanPos >= span.length) {
|
|
760
|
+
spanIdx++;
|
|
761
|
+
spanPos = 0;
|
|
762
|
+
}
|
|
473
763
|
}
|
|
764
|
+
this.charData[i] = currentOffset | (isFullWidth ? FULL_WIDTH_FLAG : 0);
|
|
765
|
+
currentOffset += val.length;
|
|
474
766
|
}
|
|
475
|
-
this.charData[i] = currentOffset | (isFullWidth ? FULL_WIDTH_FLAG : 0);
|
|
476
|
-
currentOffset += val.length;
|
|
477
767
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
768
|
+
const hasStyles = spansHaveStyles(spans);
|
|
769
|
+
if (hasStyles) {
|
|
770
|
+
this.spans = spans.map(s => ({
|
|
771
|
+
...s,
|
|
772
|
+
formatFlags: s.formatFlags & ~FULL_WIDTH_MASK,
|
|
773
|
+
}));
|
|
774
|
+
this._spansDirty = true;
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
this.spans = undefined;
|
|
778
|
+
}
|
|
483
779
|
}
|
|
484
780
|
splitSpansAt(index) {
|
|
485
781
|
if (this.spans === undefined || index <= 0 || index >= this.length)
|
|
486
782
|
return;
|
|
783
|
+
this.ensureSpansMerged();
|
|
487
784
|
let current = 0;
|
|
488
785
|
for (let i = 0; i < this.spans.length; i++) {
|
|
489
786
|
const span = this.spans[i];
|