@xterm/addon-serialize 0.15.0-beta.17 → 0.15.0-beta.170
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/src/SerializeAddon.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { IBuffer, IBufferCell, IBufferRange, ITerminalAddon, Terminal } fro
|
|
|
9
9
|
import type { IHTMLSerializeOptions, SerializeAddon as ISerializeApi, ISerializeOptions, ISerializeRange } from '@xterm/addon-serialize';
|
|
10
10
|
import { IAttributeData, IColor } from 'common/Types';
|
|
11
11
|
import { DEFAULT_ANSI_COLORS } from 'browser/Types';
|
|
12
|
+
import { UnderlineStyle } from 'common/buffer/Constants';
|
|
12
13
|
|
|
13
14
|
function constrain(value: number, low: number, high: number): number {
|
|
14
15
|
return Math.max(low, Math.min(value, high));
|
|
@@ -82,10 +83,32 @@ function equalBg(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boole
|
|
|
82
83
|
&& cell1.getBgColor() === cell2.getBgColor();
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
function equalUnderline(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
|
|
87
|
+
// If neither cell has underline, consider them equal regardless of internal underline color
|
|
88
|
+
// values
|
|
89
|
+
if (!cell1.isUnderline() && !cell2.isUnderline()) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (cell1.getUnderlineStyle() !== cell2.getUnderlineStyle()) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
const cell1Default = cell1.isUnderlineColorDefault();
|
|
96
|
+
const cell2Default = cell2.isUnderlineColorDefault();
|
|
97
|
+
if (cell1Default && cell2Default) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
if (cell1Default !== cell2Default) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return cell1.getUnderlineColor() === cell2.getUnderlineColor()
|
|
104
|
+
&& cell1.getUnderlineColorMode() === cell2.getUnderlineColorMode();
|
|
105
|
+
}
|
|
106
|
+
|
|
85
107
|
function equalFlags(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
|
|
86
108
|
return cell1.isInverse() === cell2.isInverse()
|
|
87
109
|
&& cell1.isBold() === cell2.isBold()
|
|
88
110
|
&& cell1.isUnderline() === cell2.isUnderline()
|
|
111
|
+
&& equalUnderline(cell1, cell2)
|
|
89
112
|
&& cell1.isOverline() === cell2.isOverline()
|
|
90
113
|
&& cell1.isBlink() === cell2.isBlink()
|
|
91
114
|
&& cell1.isInvisible() === cell2.isInvisible()
|
|
@@ -94,6 +117,16 @@ function equalFlags(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): bo
|
|
|
94
117
|
&& cell1.isStrikethrough() === cell2.isStrikethrough();
|
|
95
118
|
}
|
|
96
119
|
|
|
120
|
+
function attributesEquals(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
|
|
121
|
+
const cell1AsBufferCell = cell1 as IBufferCell;
|
|
122
|
+
if (typeof cell1AsBufferCell.attributesEquals === 'function') {
|
|
123
|
+
return cell1AsBufferCell.attributesEquals(cell2);
|
|
124
|
+
}
|
|
125
|
+
return equalFg(cell1, cell2)
|
|
126
|
+
&& equalBg(cell1, cell2)
|
|
127
|
+
&& equalFlags(cell1, cell2);
|
|
128
|
+
}
|
|
129
|
+
|
|
97
130
|
class StringSerializeHandler extends BaseSerializeHandler {
|
|
98
131
|
private _rowIndex: number = 0;
|
|
99
132
|
private _allRows: string[] = new Array<string>();
|
|
@@ -243,6 +276,9 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|
|
243
276
|
|
|
244
277
|
private _diffStyle(cell: IBufferCell | IAttributeData, oldCell: IBufferCell): number[] {
|
|
245
278
|
const sgrSeq: number[] = [];
|
|
279
|
+
if (attributesEquals(cell, oldCell)) {
|
|
280
|
+
return sgrSeq;
|
|
281
|
+
}
|
|
246
282
|
const fgChanged = !equalFg(cell, oldCell);
|
|
247
283
|
const bgChanged = !equalBg(cell, oldCell);
|
|
248
284
|
const flagsChanged = !equalFlags(cell, oldCell);
|
|
@@ -274,7 +310,28 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|
|
274
310
|
if (flagsChanged) {
|
|
275
311
|
if (cell.isInverse() !== oldCell.isInverse()) { sgrSeq.push(cell.isInverse() ? 7 : 27); }
|
|
276
312
|
if (cell.isBold() !== oldCell.isBold()) { sgrSeq.push(cell.isBold() ? 1 : 22); }
|
|
277
|
-
if (cell
|
|
313
|
+
if (!equalUnderline(cell, oldCell)) {
|
|
314
|
+
const style = cell.getUnderlineStyle();
|
|
315
|
+
if (style === UnderlineStyle.NONE) {
|
|
316
|
+
sgrSeq.push(24);
|
|
317
|
+
} else if (style === UnderlineStyle.SINGLE && cell.isUnderlineColorDefault()) {
|
|
318
|
+
sgrSeq.push(4);
|
|
319
|
+
} else {
|
|
320
|
+
// Use SGR 4:X format for underline styles
|
|
321
|
+
sgrSeq.push('4:' + style as unknown as number);
|
|
322
|
+
// Handle underline color
|
|
323
|
+
if (!cell.isUnderlineColorDefault()) {
|
|
324
|
+
const color = cell.getUnderlineColor();
|
|
325
|
+
if (cell.isUnderlineColorRGB()) {
|
|
326
|
+
sgrSeq.push('58:2::' + ((color >>> 16) & 0xFF) + ':' + ((color >>> 8) & 0xFF) + ':' + (color & 0xFF) as unknown as number);
|
|
327
|
+
} else {
|
|
328
|
+
sgrSeq.push('58:5:' + color as unknown as number);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
} else if (cell.isUnderline() !== oldCell.isUnderline()) {
|
|
333
|
+
sgrSeq.push(cell.isUnderline() ? 4 : 24);
|
|
334
|
+
}
|
|
278
335
|
if (cell.isOverline() !== oldCell.isOverline()) { sgrSeq.push(cell.isOverline() ? 53 : 55); }
|
|
279
336
|
if (cell.isBlink() !== oldCell.isBlink()) { sgrSeq.push(cell.isBlink() ? 5 : 25); }
|
|
280
337
|
if (cell.isInvisible() !== oldCell.isInvisible()) { sgrSeq.push(cell.isInvisible() ? 8 : 28); }
|
|
@@ -422,7 +479,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|
|
422
479
|
}
|
|
423
480
|
}
|
|
424
481
|
|
|
425
|
-
export class SerializeAddon implements ITerminalAddon
|
|
482
|
+
export class SerializeAddon implements ITerminalAddon, ISerializeApi {
|
|
426
483
|
private _terminal: Terminal | undefined;
|
|
427
484
|
|
|
428
485
|
public activate(terminal: Terminal): void {
|
|
@@ -478,6 +535,25 @@ export class SerializeAddon implements ITerminalAddon , ISerializeApi {
|
|
|
478
535
|
return '';
|
|
479
536
|
}
|
|
480
537
|
|
|
538
|
+
/**
|
|
539
|
+
* Serializes the scroll region (DECSTBM) if it's not set to the full terminal size.
|
|
540
|
+
* Uses internal API access since scroll region is not exposed in the public API.
|
|
541
|
+
*/
|
|
542
|
+
private _serializeScrollRegion(terminal: Terminal): string {
|
|
543
|
+
// HACK: Internal API access since scroll region is not exposed in the public API
|
|
544
|
+
const buffer = (terminal as any)._core.buffer;
|
|
545
|
+
const scrollTop: number = buffer.scrollTop;
|
|
546
|
+
const scrollBottom: number = buffer.scrollBottom;
|
|
547
|
+
|
|
548
|
+
// Only serialize if scroll region is not the default (full terminal size)
|
|
549
|
+
if (scrollTop !== 0 || scrollBottom !== terminal.rows - 1) {
|
|
550
|
+
// DECSTBM uses 1-based indices: CSI Ps ; Ps r
|
|
551
|
+
return `\x1b[${scrollTop + 1};${scrollBottom + 1}r`;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return '';
|
|
555
|
+
}
|
|
556
|
+
|
|
481
557
|
private _serializeModes(terminal: Terminal): string {
|
|
482
558
|
let content = '';
|
|
483
559
|
const modes = terminal.modes;
|
|
@@ -505,6 +581,12 @@ export class SerializeAddon implements ITerminalAddon , ISerializeApi {
|
|
|
505
581
|
}
|
|
506
582
|
}
|
|
507
583
|
|
|
584
|
+
// Cursor visibility (DECTCEM)
|
|
585
|
+
// Default: visible
|
|
586
|
+
if (!modes.showCursor) {
|
|
587
|
+
content += '\x1b[?25l';
|
|
588
|
+
}
|
|
589
|
+
|
|
508
590
|
return content;
|
|
509
591
|
}
|
|
510
592
|
|
|
@@ -527,9 +609,10 @@ export class SerializeAddon implements ITerminalAddon , ISerializeApi {
|
|
|
527
609
|
}
|
|
528
610
|
}
|
|
529
611
|
|
|
530
|
-
// Modes
|
|
612
|
+
// Modes and scroll region
|
|
531
613
|
if (!options?.excludeModes) {
|
|
532
614
|
content += this._serializeModes(this._terminal);
|
|
615
|
+
content += this._serializeScrollRegion(this._terminal);
|
|
533
616
|
}
|
|
534
617
|
|
|
535
618
|
return content;
|
|
@@ -540,7 +623,7 @@ export class SerializeAddon implements ITerminalAddon , ISerializeApi {
|
|
|
540
623
|
throw new Error('Cannot use addon until it has been loaded');
|
|
541
624
|
}
|
|
542
625
|
|
|
543
|
-
return this._serializeBufferAsHTML(this._terminal, options
|
|
626
|
+
return this._serializeBufferAsHTML(this._terminal, options ?? {});
|
|
544
627
|
}
|
|
545
628
|
|
|
546
629
|
public dispose(): void { }
|
|
@@ -569,20 +652,6 @@ export class HTMLSerializeHandler extends BaseSerializeHandler {
|
|
|
569
652
|
}
|
|
570
653
|
}
|
|
571
654
|
|
|
572
|
-
private _padStart(target: string, targetLength: number, padString: string): string {
|
|
573
|
-
targetLength = targetLength >> 0;
|
|
574
|
-
padString = padString ?? ' ';
|
|
575
|
-
if (target.length > targetLength) {
|
|
576
|
-
return target;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
targetLength -= target.length;
|
|
580
|
-
if (targetLength > padString.length) {
|
|
581
|
-
padString += padString.repeat(targetLength / padString.length);
|
|
582
|
-
}
|
|
583
|
-
return padString.slice(0, targetLength) + target;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
655
|
protected _beforeSerialize(rows: number, start: number, end: number): void {
|
|
587
656
|
this._htmlContent += '<html><body><!--StartFragment--><pre>';
|
|
588
657
|
|
|
@@ -619,7 +688,7 @@ export class HTMLSerializeHandler extends BaseSerializeHandler {
|
|
|
619
688
|
(color >> 8) & 255,
|
|
620
689
|
(color ) & 255
|
|
621
690
|
];
|
|
622
|
-
return '#' + rgb.map(x =>
|
|
691
|
+
return '#' + rgb.map(x => x.toString(16).padStart(2, '0')).join('');
|
|
623
692
|
}
|
|
624
693
|
if (isFg ? cell.isFgPalette() : cell.isBgPalette()) {
|
|
625
694
|
return this._ansiColors[color].css;
|
|
@@ -627,9 +696,47 @@ export class HTMLSerializeHandler extends BaseSerializeHandler {
|
|
|
627
696
|
return undefined;
|
|
628
697
|
}
|
|
629
698
|
|
|
699
|
+
private _getUnderlineColor(cell: IBufferCell): string | undefined {
|
|
700
|
+
if (cell.isUnderlineColorDefault()) {
|
|
701
|
+
return undefined;
|
|
702
|
+
}
|
|
703
|
+
const color = cell.getUnderlineColor();
|
|
704
|
+
if (cell.isUnderlineColorRGB()) {
|
|
705
|
+
const rgb = [
|
|
706
|
+
(color >> 16) & 255,
|
|
707
|
+
(color >> 8) & 255,
|
|
708
|
+
(color ) & 255
|
|
709
|
+
];
|
|
710
|
+
return '#' + rgb.map(x => x.toString(16).padStart(2, '0')).join('');
|
|
711
|
+
}
|
|
712
|
+
// Palette color
|
|
713
|
+
return this._ansiColors[color].css;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
private _getUnderlineStyle(cell: IBufferCell): string {
|
|
717
|
+
switch (cell.getUnderlineStyle()) {
|
|
718
|
+
case UnderlineStyle.SINGLE:
|
|
719
|
+
return 'underline';
|
|
720
|
+
case UnderlineStyle.DOUBLE:
|
|
721
|
+
return 'underline double';
|
|
722
|
+
case UnderlineStyle.CURLY:
|
|
723
|
+
return 'underline wavy';
|
|
724
|
+
case UnderlineStyle.DOTTED:
|
|
725
|
+
return 'underline dotted';
|
|
726
|
+
case UnderlineStyle.DASHED:
|
|
727
|
+
return 'underline dashed';
|
|
728
|
+
default:
|
|
729
|
+
return 'underline';
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
630
733
|
private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): string[] | undefined {
|
|
631
734
|
const content: string[] = [];
|
|
632
735
|
|
|
736
|
+
if (attributesEquals(cell, oldCell)) {
|
|
737
|
+
return undefined;
|
|
738
|
+
}
|
|
739
|
+
|
|
633
740
|
const fgChanged = !equalFg(cell, oldCell);
|
|
634
741
|
const bgChanged = !equalBg(cell, oldCell);
|
|
635
742
|
const flagsChanged = !equalFlags(cell, oldCell);
|
|
@@ -647,14 +754,36 @@ export class HTMLSerializeHandler extends BaseSerializeHandler {
|
|
|
647
754
|
|
|
648
755
|
if (cell.isInverse()) { content.push('color: #000000; background-color: #BFBFBF;'); }
|
|
649
756
|
if (cell.isBold()) { content.push('font-weight: bold;'); }
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
if (cell.
|
|
757
|
+
|
|
758
|
+
// Handle text-decoration (underline, overline, strikethrough, blink)
|
|
759
|
+
const decorations: string[] = [];
|
|
760
|
+
if (cell.isUnderline()) {
|
|
761
|
+
decorations.push(this._getUnderlineStyle(cell));
|
|
762
|
+
}
|
|
763
|
+
if (cell.isOverline()) {
|
|
764
|
+
decorations.push('overline');
|
|
765
|
+
}
|
|
766
|
+
if (cell.isStrikethrough()) {
|
|
767
|
+
decorations.push('line-through');
|
|
768
|
+
}
|
|
769
|
+
if (cell.isBlink()) {
|
|
770
|
+
decorations.push('blink');
|
|
771
|
+
}
|
|
772
|
+
if (decorations.length > 0) {
|
|
773
|
+
content.push('text-decoration: ' + decorations.join(' ') + ';');
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Handle underline color
|
|
777
|
+
if (cell.isUnderline()) {
|
|
778
|
+
const underlineColor = this._getUnderlineColor(cell);
|
|
779
|
+
if (underlineColor) {
|
|
780
|
+
content.push('text-decoration-color: ' + underlineColor + ';');
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
654
784
|
if (cell.isInvisible()) { content.push('visibility: hidden;'); }
|
|
655
785
|
if (cell.isItalic()) { content.push('font-style: italic;'); }
|
|
656
786
|
if (cell.isDim()) { content.push('opacity: 0.5;'); }
|
|
657
|
-
if (cell.isStrikethrough()) { content.push('text-decoration: line-through;'); }
|
|
658
787
|
|
|
659
788
|
return content;
|
|
660
789
|
}
|