@jrichman/ink 6.6.9 → 7.0.0-beta.1
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 +42 -12
- 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 +161 -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 +418 -137
- package/build/styled-line.js.map +1 -1
- package/build/terminal-buffer.d.ts +1 -0
- package/build/terminal-buffer.js +173 -48
- 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 +14 -4
- package/build/worker/render-worker.js +32 -26
- package/build/worker/render-worker.js.map +1 -1
- package/build/worker/scene-manager.js +23 -8
- 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 +25 -11
- package/build/worker/terminal-writer.js.map +1 -1
- package/package.json +2 -2
- 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,49 +487,72 @@ 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
523
|
if (this.length === 0)
|
|
330
524
|
return 0;
|
|
331
|
-
if (this.text === undefined
|
|
525
|
+
if (this.text === undefined)
|
|
332
526
|
return 0;
|
|
527
|
+
this.ensureSpansMerged();
|
|
333
528
|
let currentIdx = this.length - 1;
|
|
334
529
|
if (this.spans) {
|
|
335
530
|
for (let s = this.spans.length - 1; s >= 0; s--) {
|
|
336
531
|
const span = this.spans[s];
|
|
337
|
-
const hasStyles = (span
|
|
338
|
-
span.fgColor !== undefined ||
|
|
339
|
-
span.bgColor !== undefined ||
|
|
340
|
-
span.link !== undefined;
|
|
532
|
+
const hasStyles = spanHasStyles(span);
|
|
341
533
|
if (hasStyles) {
|
|
342
534
|
return currentIdx + 1;
|
|
343
535
|
}
|
|
344
536
|
for (let i = 0; i < span.length; i++) {
|
|
537
|
+
if (this.charData) {
|
|
538
|
+
const start = this.charData[currentIdx] & OFFSET_MASK;
|
|
539
|
+
const end = currentIdx + 1 < this.length
|
|
540
|
+
? this.charData[currentIdx + 1] & OFFSET_MASK
|
|
541
|
+
: this.text.length;
|
|
542
|
+
if (end - start !== 1 || this.text[start] !== ' ') {
|
|
543
|
+
return currentIdx + 1;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
else if (this.text[currentIdx] !== ' ') {
|
|
547
|
+
return currentIdx + 1;
|
|
548
|
+
}
|
|
549
|
+
currentIdx--;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
while (currentIdx >= 0) {
|
|
555
|
+
if (this.charData) {
|
|
345
556
|
const start = this.charData[currentIdx] & OFFSET_MASK;
|
|
346
557
|
const end = currentIdx + 1 < this.length
|
|
347
558
|
? this.charData[currentIdx + 1] & OFFSET_MASK
|
|
@@ -349,8 +560,11 @@ export class StyledLine {
|
|
|
349
560
|
if (end - start !== 1 || this.text[start] !== ' ') {
|
|
350
561
|
return currentIdx + 1;
|
|
351
562
|
}
|
|
352
|
-
currentIdx--;
|
|
353
563
|
}
|
|
564
|
+
else if (this.text[currentIdx] !== ' ') {
|
|
565
|
+
return currentIdx + 1;
|
|
566
|
+
}
|
|
567
|
+
currentIdx--;
|
|
354
568
|
}
|
|
355
569
|
}
|
|
356
570
|
return 0;
|
|
@@ -370,24 +584,44 @@ export class StyledLine {
|
|
|
370
584
|
return true;
|
|
371
585
|
if (this.getText() !== other.getText())
|
|
372
586
|
return false;
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
587
|
+
this.ensureSpansMerged();
|
|
588
|
+
other.ensureSpansMerged();
|
|
589
|
+
const thisSpans = this.internalGetSpans();
|
|
590
|
+
const otherSpans = other.internalGetSpans();
|
|
591
|
+
if (thisSpans === undefined && otherSpans !== undefined) {
|
|
592
|
+
if (spansHaveStyles(otherSpans))
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
else if (thisSpans !== undefined && otherSpans === undefined) {
|
|
596
|
+
if (spansHaveStyles(thisSpans))
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
else if (thisSpans !== undefined && otherSpans !== undefined) {
|
|
600
|
+
if (thisSpans.length !== otherSpans.length)
|
|
385
601
|
return false;
|
|
602
|
+
for (let i = 0; i < thisSpans.length; i++) {
|
|
603
|
+
const sp1 = thisSpans[i];
|
|
604
|
+
const sp2 = otherSpans[i];
|
|
605
|
+
if (sp1.length !== sp2.length ||
|
|
606
|
+
sp1.formatFlags !== sp2.formatFlags ||
|
|
607
|
+
sp1.fgColor !== sp2.fgColor ||
|
|
608
|
+
sp1.bgColor !== sp2.bgColor ||
|
|
609
|
+
sp1.link !== sp2.link) {
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
386
612
|
}
|
|
387
613
|
}
|
|
388
|
-
const thisCharData = this.
|
|
389
|
-
const otherCharData = other.
|
|
390
|
-
if (thisCharData && otherCharData) {
|
|
614
|
+
const thisCharData = this.internalGetCharData();
|
|
615
|
+
const otherCharData = other.internalGetCharData();
|
|
616
|
+
if (thisCharData === undefined && otherCharData !== undefined) {
|
|
617
|
+
if (!isDefaultCharData(otherCharData, this.length))
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
else if (thisCharData !== undefined && otherCharData === undefined) {
|
|
621
|
+
if (!isDefaultCharData(thisCharData, this.length))
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
else if (thisCharData !== undefined && otherCharData !== undefined) {
|
|
391
625
|
for (let i = 0; i < this.length; i++) {
|
|
392
626
|
if (thisCharData[i] !== otherCharData[i])
|
|
393
627
|
return false;
|
|
@@ -399,7 +633,12 @@ export class StyledLine {
|
|
|
399
633
|
return this.text ?? '';
|
|
400
634
|
}
|
|
401
635
|
getSpans() {
|
|
402
|
-
|
|
636
|
+
this.ensureSpansMerged();
|
|
637
|
+
if (this.spans !== undefined)
|
|
638
|
+
return this.spans;
|
|
639
|
+
if (this.length > 0)
|
|
640
|
+
return [{ length: this.length, formatFlags: 0 }];
|
|
641
|
+
return [];
|
|
403
642
|
}
|
|
404
643
|
getValues() {
|
|
405
644
|
return Array.from({ length: this.length }, (_, i) => this.getValue(i));
|
|
@@ -435,18 +674,36 @@ export class StyledLine {
|
|
|
435
674
|
}
|
|
436
675
|
}
|
|
437
676
|
}
|
|
677
|
+
internalGetCharData() {
|
|
678
|
+
return this.charData;
|
|
679
|
+
}
|
|
680
|
+
internalGetSpans() {
|
|
681
|
+
return this.spans;
|
|
682
|
+
}
|
|
683
|
+
ensureSpansMerged() {
|
|
684
|
+
if (this._spansDirty) {
|
|
685
|
+
this.mergeSpans();
|
|
686
|
+
this._spansDirty = false;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
438
689
|
ensureInitialized() {
|
|
690
|
+
if (this.text === undefined) {
|
|
691
|
+
this.text = this.length > 0 ? ' '.repeat(this.length) : '';
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
ensureCharData() {
|
|
695
|
+
this.ensureInitialized();
|
|
439
696
|
if (this.charData === undefined) {
|
|
440
|
-
this.text = '';
|
|
441
697
|
this.charData = Array.from({ length: this.length });
|
|
698
|
+
for (let i = 0; i < this.length; i++) {
|
|
699
|
+
this.charData[i] = i;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
ensureSpans() {
|
|
704
|
+
if (this.spans === undefined) {
|
|
442
705
|
this.spans =
|
|
443
706
|
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
707
|
}
|
|
451
708
|
}
|
|
452
709
|
applyValuesAndSpans(values, spans) {
|
|
@@ -454,36 +711,60 @@ export class StyledLine {
|
|
|
454
711
|
const visibleChars = values.length;
|
|
455
712
|
this.length = visibleChars;
|
|
456
713
|
this.text = values.join('');
|
|
457
|
-
|
|
458
|
-
let currentOffset = 0;
|
|
459
|
-
let spanIdx = 0;
|
|
460
|
-
let spanPos = 0;
|
|
714
|
+
let needsCharData = false;
|
|
461
715
|
for (let i = 0; i < visibleChars; i++) {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
716
|
+
if (values[i].length !== 1) {
|
|
717
|
+
needsCharData = true;
|
|
718
|
+
break;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (!needsCharData && spans.length > 0) {
|
|
722
|
+
for (const span of spans) {
|
|
466
723
|
if ((span.formatFlags & FULL_WIDTH_MASK) !== 0) {
|
|
467
|
-
|
|
724
|
+
needsCharData = true;
|
|
725
|
+
break;
|
|
468
726
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (needsCharData) {
|
|
730
|
+
this.charData = Array.from({ length: this.length });
|
|
731
|
+
let currentOffset = 0;
|
|
732
|
+
let spanIdx = 0;
|
|
733
|
+
let spanPos = 0;
|
|
734
|
+
for (let i = 0; i < visibleChars; i++) {
|
|
735
|
+
const val = values[i];
|
|
736
|
+
let isFullWidth = false;
|
|
737
|
+
if (spans.length > 0 && spanIdx < spans.length) {
|
|
738
|
+
const span = spans[spanIdx];
|
|
739
|
+
if ((span.formatFlags & FULL_WIDTH_MASK) !== 0) {
|
|
740
|
+
isFullWidth = true;
|
|
741
|
+
}
|
|
742
|
+
spanPos++;
|
|
743
|
+
if (spanPos >= span.length) {
|
|
744
|
+
spanIdx++;
|
|
745
|
+
spanPos = 0;
|
|
746
|
+
}
|
|
473
747
|
}
|
|
748
|
+
this.charData[i] = currentOffset | (isFullWidth ? FULL_WIDTH_FLAG : 0);
|
|
749
|
+
currentOffset += val.length;
|
|
474
750
|
}
|
|
475
|
-
this.charData[i] = currentOffset | (isFullWidth ? FULL_WIDTH_FLAG : 0);
|
|
476
|
-
currentOffset += val.length;
|
|
477
751
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
752
|
+
const hasStyles = spansHaveStyles(spans);
|
|
753
|
+
if (hasStyles) {
|
|
754
|
+
this.spans = spans.map(s => ({
|
|
755
|
+
...s,
|
|
756
|
+
formatFlags: s.formatFlags & ~FULL_WIDTH_MASK,
|
|
757
|
+
}));
|
|
758
|
+
this._spansDirty = true;
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
this.spans = undefined;
|
|
762
|
+
}
|
|
483
763
|
}
|
|
484
764
|
splitSpansAt(index) {
|
|
485
765
|
if (this.spans === undefined || index <= 0 || index >= this.length)
|
|
486
766
|
return;
|
|
767
|
+
this.ensureSpansMerged();
|
|
487
768
|
let current = 0;
|
|
488
769
|
for (let i = 0; i < this.spans.length; i++) {
|
|
489
770
|
const span = this.spans[i];
|