@valyrianjs/terminal 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ansi.d.ts.map +1 -1
- package/dist/ansi.js +166 -4
- package/dist/ansi.js.map +1 -1
- package/dist/render-internal.d.ts +10 -0
- package/dist/render-internal.d.ts.map +1 -0
- package/dist/render-internal.js +1295 -0
- package/dist/render-internal.js.map +1 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +13 -1244
- package/dist/render.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +76 -4
- package/dist/session.js.map +1 -1
- package/dist/text.d.ts.map +1 -1
- package/dist/text.js +12 -1
- package/dist/text.js.map +1 -1
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +18 -2
- package/dist/theme.js.map +1 -1
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ansi.ts +196 -4
- package/src/render-internal.ts +1441 -0
- package/src/render.ts +14 -1368
- package/src/session.ts +97 -12
- package/src/text.ts +13 -1
- package/src/theme.ts +22 -2
- package/src/types.ts +0 -2
package/src/ansi.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { terminalCellWidth, terminalGraphemes } from "./text.js";
|
|
1
|
+
import { dropTerminalCells, terminalCellWidth, terminalGraphemes } from "./text.js";
|
|
2
2
|
import { resolveTerminalStyle, resolveTerminalStyleToken } from "./theme.js";
|
|
3
3
|
import type { CursorPosition, TerminalFrame, TerminalStyleSpan, TerminalTheme } from "./types.js";
|
|
4
4
|
|
|
@@ -234,6 +234,134 @@ export function toAnsiFrame(lines: string[], cursor: CursorPosition | null, span
|
|
|
234
234
|
return `\u001b[?25l\u001b[H${ansiLines.join("\n")}${cursorCode}`;
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
function commonPrefixCellWidth(previousLine: string, nextLine: string) {
|
|
238
|
+
const previousGraphemes = terminalGraphemes(previousLine);
|
|
239
|
+
const nextGraphemes = terminalGraphemes(nextLine);
|
|
240
|
+
const length = Math.min(previousGraphemes.length, nextGraphemes.length);
|
|
241
|
+
let width = 0;
|
|
242
|
+
|
|
243
|
+
for (let index = 0; index < length; index += 1) {
|
|
244
|
+
if (previousGraphemes[index] !== nextGraphemes[index]) {
|
|
245
|
+
return width;
|
|
246
|
+
}
|
|
247
|
+
if (previousGraphemes[index] !== "|") {
|
|
248
|
+
width += terminalCellWidth(previousGraphemes[index]);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return width;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function renderAnsiLineSuffix(line: string, spans: TerminalStyleSpan[], y: number, startColumn: number, theme?: TerminalTheme) {
|
|
256
|
+
let output = "";
|
|
257
|
+
let visibleColumn = Math.max(1, startColumn);
|
|
258
|
+
const rowSpans = lineSpans(spans, y);
|
|
259
|
+
let activeSpanIndex = rowSpans.findIndex((span) => span.x1 >= visibleColumn);
|
|
260
|
+
if (activeSpanIndex < 0) {
|
|
261
|
+
activeSpanIndex = rowSpans.length;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
for (const span of rowSpans) {
|
|
265
|
+
if (span.x1 <= visibleColumn && span.x2 > visibleColumn) {
|
|
266
|
+
output += spanAnsiOpen(span, theme);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for (const grapheme of terminalGraphemes(dropTerminalCells(line, visibleColumn - 1))) {
|
|
271
|
+
while (rowSpans[activeSpanIndex]?.x1 === visibleColumn) {
|
|
272
|
+
output += spanAnsiOpen(rowSpans[activeSpanIndex], theme);
|
|
273
|
+
activeSpanIndex += 1;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (grapheme === "|") {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
output += grapheme;
|
|
281
|
+
visibleColumn += terminalCellWidth(grapheme);
|
|
282
|
+
|
|
283
|
+
let closedSpan = false;
|
|
284
|
+
for (const span of rowSpans) {
|
|
285
|
+
if (span.x2 === visibleColumn) {
|
|
286
|
+
if (span.kind !== "focus") {
|
|
287
|
+
output += spanAnsiClose(span, theme);
|
|
288
|
+
closedSpan = true;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
for (const span of rowSpans) {
|
|
293
|
+
if (span.x2 === visibleColumn && span.kind === "focus") {
|
|
294
|
+
output += spanAnsiClose(span, theme);
|
|
295
|
+
closedSpan = true;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (closedSpan && visibleColumn <= terminalCellWidth(line)) {
|
|
299
|
+
for (const span of rowSpans) {
|
|
300
|
+
if (span.x1 < visibleColumn && span.x2 > visibleColumn) {
|
|
301
|
+
output += spanAnsiOpen(span, theme);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
for (const span of rowSpans) {
|
|
308
|
+
if (span.x1 < visibleColumn && span.x2 > visibleColumn) {
|
|
309
|
+
if (span.kind !== "focus") {
|
|
310
|
+
output += spanAnsiClose(span, theme);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
for (const span of rowSpans) {
|
|
315
|
+
if (span.x1 < visibleColumn && span.x2 > visibleColumn && span.kind === "focus") {
|
|
316
|
+
output += spanAnsiClose(span, theme);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return output;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
function sameSpanStyle(previous: TerminalStyleSpan["style"], next: TerminalStyleSpan["style"]) {
|
|
325
|
+
if (previous === next) {
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
if (!previous || !next) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
return previous.color === next.color
|
|
332
|
+
&& previous.background === next.background
|
|
333
|
+
&& previous.plainPrefix === next.plainPrefix
|
|
334
|
+
&& previous.plainSuffix === next.plainSuffix
|
|
335
|
+
&& JSON.stringify(previous.border) === JSON.stringify(next.border)
|
|
336
|
+
&& JSON.stringify(previous.padding) === JSON.stringify(next.padding);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function sameLineSpans(previousSpans: TerminalStyleSpan[], nextSpans: TerminalStyleSpan[], y: number) {
|
|
340
|
+
if (previousSpans.length === 0 && nextSpans.length === 0) {
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const previousRowSpans = lineSpans(previousSpans, y);
|
|
345
|
+
const nextRowSpans = lineSpans(nextSpans, y);
|
|
346
|
+
if (previousRowSpans.length !== nextRowSpans.length) {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
for (let index = 0; index < previousRowSpans.length; index += 1) {
|
|
351
|
+
const previous = previousRowSpans[index];
|
|
352
|
+
const next = nextRowSpans[index];
|
|
353
|
+
if (previous.kind !== next.kind
|
|
354
|
+
|| previous.x1 !== next.x1
|
|
355
|
+
|| previous.x2 !== next.x2
|
|
356
|
+
|| previous.y !== next.y
|
|
357
|
+
|| !sameSpanStyle(previous.style, next.style)) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
|
|
237
365
|
export function createAnsiFrameDiff(
|
|
238
366
|
previousAnsiLines: string[],
|
|
239
367
|
nextAnsiLines: string[],
|
|
@@ -267,13 +395,77 @@ export function createAnsiFrameDiff(
|
|
|
267
395
|
};
|
|
268
396
|
}
|
|
269
397
|
|
|
398
|
+
function createAnsiFramePatch(
|
|
399
|
+
previousLines: string[],
|
|
400
|
+
previousAnsiLines: string[],
|
|
401
|
+
nextLines: string[],
|
|
402
|
+
previousSpans: TerminalStyleSpan[],
|
|
403
|
+
nextSpans: TerminalStyleSpan[],
|
|
404
|
+
cursor: CursorPosition | null,
|
|
405
|
+
options: AnsiFrameOptions = {}
|
|
406
|
+
): AnsiFrameDiffResult & { nextAnsiLines: string[] } {
|
|
407
|
+
const nextAnsiLines: string[] = [];
|
|
408
|
+
const changedLineIndexes: number[] = [];
|
|
409
|
+
const writes: string[] = ["\u001b[?25l"];
|
|
410
|
+
const maxLines = Math.max(previousAnsiLines.length, nextLines.length);
|
|
411
|
+
|
|
412
|
+
for (let i = 0; i < maxLines; i += 1) {
|
|
413
|
+
const previousLine = previousLines[i] || "";
|
|
414
|
+
const nextLine = nextLines[i] || "";
|
|
415
|
+
const previousAnsiLine = previousAnsiLines[i] || "";
|
|
416
|
+
const spansMatch = sameLineSpans(previousSpans, nextSpans, i + 1);
|
|
417
|
+
const nextAnsiLine = i < nextLines.length && previousLine === nextLine && spansMatch
|
|
418
|
+
? previousAnsiLine
|
|
419
|
+
: i < nextLines.length
|
|
420
|
+
? renderAnsiLine(nextLine, nextSpans, i + 1, options.theme)
|
|
421
|
+
: "";
|
|
422
|
+
nextAnsiLines[i] = nextAnsiLine;
|
|
423
|
+
if (nextAnsiLine === previousAnsiLine) {
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
changedLineIndexes.push(i);
|
|
428
|
+
const canPatchSuffix = cursor?.y === i + 1
|
|
429
|
+
&& lineSpans(previousSpans, i + 1).length === 0
|
|
430
|
+
&& lineSpans(nextSpans, i + 1).length === 0;
|
|
431
|
+
const prefixWidth = canPatchSuffix && i < previousLines.length && i < nextLines.length && previousLine !== nextLine
|
|
432
|
+
? commonPrefixCellWidth(previousLine, nextLine)
|
|
433
|
+
: 0;
|
|
434
|
+
|
|
435
|
+
if (prefixWidth > 0) {
|
|
436
|
+
const startColumn = prefixWidth + 1;
|
|
437
|
+
const suffix = renderAnsiLineSuffix(nextLine, nextSpans, i + 1, startColumn, options.theme);
|
|
438
|
+
writes.push(`\u001b[${i + 1};${startColumn}H${suffix}\u001b[0m\u001b[K`);
|
|
439
|
+
} else {
|
|
440
|
+
writes.push(`\u001b[${i + 1};1H${nextAnsiLine}\u001b[0m\u001b[K`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (cursor) {
|
|
445
|
+
writes.push(`\u001b[${cursor.y};${cursor.x}H`);
|
|
446
|
+
}
|
|
447
|
+
if (options.showCursor !== false || (options.showCursorWhenFrameHasCursor === true && cursor)) {
|
|
448
|
+
writes.push(ANSI_SHOW_CURSOR);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
changedLineIndexes,
|
|
453
|
+
outputChunk: writes.join(""),
|
|
454
|
+
restoresCursor: Boolean(cursor),
|
|
455
|
+
nextAnsiLines: nextAnsiLines.slice(0, nextLines.length)
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
270
459
|
export function createAnsiDiffWriter(options: AnsiFrameOptions = {}) {
|
|
460
|
+
let previousLines: string[] = [];
|
|
271
461
|
let previousAnsiLines: string[] = [];
|
|
462
|
+
let previousSpans: TerminalStyleSpan[] = [];
|
|
272
463
|
|
|
273
464
|
return function toAnsiDiff(lines: string[], cursor: CursorPosition | null, spans: TerminalStyleSpan[] = []) {
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
previousAnsiLines =
|
|
465
|
+
const diff = createAnsiFramePatch(previousLines, previousAnsiLines, lines, previousSpans, spans, cursor, options);
|
|
466
|
+
previousLines = lines.slice();
|
|
467
|
+
previousAnsiLines = diff.nextAnsiLines.slice();
|
|
468
|
+
previousSpans = spans.map((span) => ({ ...span }));
|
|
277
469
|
return diff.outputChunk;
|
|
278
470
|
};
|
|
279
471
|
}
|