Skip to content

Commit 36118d9

Browse files
committed
Merge upstream/master - resolve IIPHandler.ts const enum conflict
2 parents cf8a4e4 + 2f66b5f commit 36118d9

51 files changed

Lines changed: 602 additions & 184 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

addons/addon-fit/src/FitAddon.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,10 @@ export class FitAddon implements ITerminalAddon, IFitApi {
6969
return undefined;
7070
}
7171

72-
const scrollbarWidth = (this._terminal.options.scrollback === 0
72+
const showScrollbar = this._terminal.options.scrollbar?.showScrollbar ?? true;
73+
const scrollbarWidth = (this._terminal.options.scrollback === 0 || !showScrollbar
7374
? 0
74-
: (this._terminal.options.overviewRuler?.width || ViewportConstants.DEFAULT_SCROLL_BAR_WIDTH));
75+
: (this._terminal.options.scrollbar?.width ?? ViewportConstants.DEFAULT_SCROLL_BAR_WIDTH));
7576

7677
const parentElementStyle = _getComputedStyle(this._terminal.element.parentElement);
7778
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));

addons/addon-image/src/IIPHandler.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@
55
import { IImageAddonOptions, IOscHandler, IResetHandler, ITerminalExt } from './Types';
66
import { ImageRenderer } from './ImageRenderer';
77
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
8-
import Base64Decoder, { type DecodeStatus } from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm';
8+
import Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm';
99
import { HeaderParser, IHeaderFields, HeaderState } from './IIPHeaderParser';
1010
import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';
1111

12-
// limit hold memory in base64 decoder (encoded bytes)
13-
const KEEP_DATA = 4194304;
14-
const INITIAL_DATA = 1048576;
15-
16-
// Local mirror of const enum (esbuild can't inline const enums from external packages)
17-
const DECODER_OK: DecodeStatus.OK = 0;
12+
// Local const enum mirror - esbuild can't inline const enums from external packages
13+
const enum DecoderConst {
14+
// Limit held memory in base64 decoder (encoded bytes).
15+
KEEP_DATA = 4194304,
16+
// Initial buffer allocation for the decoder.
17+
INITIAL_DATA = 1048576,
18+
// Local mirror of const enum (esbuild can't inline const enums from external packages)
19+
OK = 0
20+
}
1821

1922
// default IIP header values
2023
const DEFAULT_HEADER: IHeaderFields = {
@@ -41,8 +44,8 @@ export class IIPHandler implements IOscHandler, IResetHandler {
4144
private readonly _coreTerminal: ITerminalExt
4245
) {
4346
const maxEncodedBytes = Math.ceil(this._opts.iipSizeLimit * 4 / 3);
44-
const initialBytes = Math.min(INITIAL_DATA, maxEncodedBytes);
45-
this._dec = new Base64Decoder(KEEP_DATA, maxEncodedBytes, initialBytes);
47+
const initialBytes = Math.min(DecoderConst.INITIAL_DATA, maxEncodedBytes);
48+
this._dec = new Base64Decoder(DecoderConst.KEEP_DATA, maxEncodedBytes, initialBytes);
4649
}
4750

4851
public reset(): void {}
@@ -58,7 +61,7 @@ export class IIPHandler implements IOscHandler, IResetHandler {
5861
if (this._aborted) return;
5962

6063
if (this._hp.state === HeaderState.END) {
61-
if (this._dec.put(data.subarray(start, end)) !== DECODER_OK) {
64+
if ((this._dec.put(data.subarray(start, end)) as number) !== DecoderConst.OK) {
6265
this._dec.release();
6366
this._aborted = true;
6467
}
@@ -75,7 +78,7 @@ export class IIPHandler implements IOscHandler, IResetHandler {
7578
return;
7679
}
7780
this._dec.init();
78-
if (this._dec.put(data.subarray(dataPos, end)) !== DECODER_OK) {
81+
if ((this._dec.put(data.subarray(dataPos, end)) as number) !== DecoderConst.OK) {
7982
this._dec.release();
8083
this._aborted = true;
8184
}

addons/addon-serialize/src/SerializeAddon.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ describe('SerializeAddon', () => {
116116
describe('underline styles', () => {
117117
it('should serialize single underline with style', async () => {
118118
await writeP(terminal, sgr('4:1') + 'test' + sgr('24'));
119-
assert.equal(serializeAddon.serialize(), '\u001b[4:1mtest\u001b[0m');
119+
assert.equal(serializeAddon.serialize(), '\u001b[4mtest\u001b[0m');
120120
});
121121

122122
it('should serialize double underline', async () => {

addons/addon-serialize/src/SerializeAddon.ts

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,19 @@ function equalUnderline(cell1: IBufferCell | IAttributeData, cell2: IBufferCell)
8989
if (!cell1.isUnderline() && !cell2.isUnderline()) {
9090
return true;
9191
}
92-
const cell1Data = cell1 as unknown as IAttributeData;
93-
const cell2Data = cell2 as unknown as IAttributeData;
94-
return cell1Data.getUnderlineStyle() === cell2Data.getUnderlineStyle()
95-
&& cell1Data.getUnderlineColor() === cell2Data.getUnderlineColor()
96-
&& cell1Data.getUnderlineColorMode() === cell2Data.getUnderlineColorMode();
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();
97105
}
98106

99107
function equalFlags(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
@@ -109,6 +117,16 @@ function equalFlags(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): bo
109117
&& cell1.isStrikethrough() === cell2.isStrikethrough();
110118
}
111119

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+
112130
class StringSerializeHandler extends BaseSerializeHandler {
113131
private _rowIndex: number = 0;
114132
private _allRows: string[] = new Array<string>();
@@ -258,6 +276,9 @@ class StringSerializeHandler extends BaseSerializeHandler {
258276

259277
private _diffStyle(cell: IBufferCell | IAttributeData, oldCell: IBufferCell): number[] {
260278
const sgrSeq: number[] = [];
279+
if (attributesEquals(cell, oldCell)) {
280+
return sgrSeq;
281+
}
261282
const fgChanged = !equalFg(cell, oldCell);
262283
const bgChanged = !equalBg(cell, oldCell);
263284
const flagsChanged = !equalFlags(cell, oldCell);
@@ -290,17 +311,18 @@ class StringSerializeHandler extends BaseSerializeHandler {
290311
if (cell.isInverse() !== oldCell.isInverse()) { sgrSeq.push(cell.isInverse() ? 7 : 27); }
291312
if (cell.isBold() !== oldCell.isBold()) { sgrSeq.push(cell.isBold() ? 1 : 22); }
292313
if (!equalUnderline(cell, oldCell)) {
293-
const cellData = cell as unknown as IAttributeData;
294-
const style = cellData.getUnderlineStyle();
314+
const style = cell.getUnderlineStyle();
295315
if (style === UnderlineStyle.NONE) {
296316
sgrSeq.push(24);
317+
} else if (style === UnderlineStyle.SINGLE && cell.isUnderlineColorDefault()) {
318+
sgrSeq.push(4);
297319
} else {
298320
// Use SGR 4:X format for underline styles
299321
sgrSeq.push('4:' + style as unknown as number);
300322
// Handle underline color
301-
if (!cellData.isUnderlineColorDefault()) {
302-
const color = cellData.getUnderlineColor();
303-
if (cellData.isUnderlineColorRGB()) {
323+
if (!cell.isUnderlineColorDefault()) {
324+
const color = cell.getUnderlineColor();
325+
if (cell.isUnderlineColorRGB()) {
304326
sgrSeq.push('58:2::' + ((color >>> 16) & 0xFF) + ':' + ((color >>> 8) & 0xFF) + ':' + (color & 0xFF) as unknown as number);
305327
} else {
306328
sgrSeq.push('58:5:' + color as unknown as number);
@@ -675,12 +697,11 @@ export class HTMLSerializeHandler extends BaseSerializeHandler {
675697
}
676698

677699
private _getUnderlineColor(cell: IBufferCell): string | undefined {
678-
const cellData = cell as unknown as IAttributeData;
679-
if (cellData.isUnderlineColorDefault()) {
700+
if (cell.isUnderlineColorDefault()) {
680701
return undefined;
681702
}
682-
const color = cellData.getUnderlineColor();
683-
if (cellData.isUnderlineColorRGB()) {
703+
const color = cell.getUnderlineColor();
704+
if (cell.isUnderlineColorRGB()) {
684705
const rgb = [
685706
(color >> 16) & 255,
686707
(color >> 8) & 255,
@@ -693,8 +714,7 @@ export class HTMLSerializeHandler extends BaseSerializeHandler {
693714
}
694715

695716
private _getUnderlineStyle(cell: IBufferCell): string {
696-
const cellData = cell as unknown as IAttributeData;
697-
switch (cellData.getUnderlineStyle()) {
717+
switch (cell.getUnderlineStyle()) {
698718
case UnderlineStyle.SINGLE:
699719
return 'underline';
700720
case UnderlineStyle.DOUBLE:
@@ -713,6 +733,10 @@ export class HTMLSerializeHandler extends BaseSerializeHandler {
713733
private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): string[] | undefined {
714734
const content: string[] = [];
715735

736+
if (attributesEquals(cell, oldCell)) {
737+
return undefined;
738+
}
739+
716740
const fgChanged = !equalFg(cell, oldCell);
717741
const bgChanged = !equalBg(cell, oldCell);
718742
const flagsChanged = !equalFlags(cell, oldCell);

addons/addon-serialize/test/SerializeAddon.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,46 @@ test.describe('SerializeAddon', () => {
203203
strictEqual(await ctx.page.evaluate(`window.serialize.serialize();`), lines.join('\r\n'));
204204
});
205205

206+
test('buffer cell attributesEquals compares underline style and color', async () => {
207+
await ctx.proxy.write(`${sgr(UNDERLINE_DOUBLE, UNDERLINE_COLOR_RED)}A${sgr(UNDERLINE_DOUBLE, UNDERLINE_COLOR_RED)}B${sgr(NORMAL)}`);
208+
const sameAttributes = await ctx.page.evaluate(`(() => {
209+
const line = window.term.buffer.active.getLine(0);
210+
const cellA = line?.getCell(0);
211+
const cellB = line?.getCell(1);
212+
if (!cellA || !cellB) {
213+
return undefined;
214+
}
215+
return cellA.attributesEquals(cellB);
216+
})()`);
217+
strictEqual(sameAttributes, true);
218+
219+
await ctx.page.evaluate(`window.term.reset()`);
220+
await ctx.proxy.write(`${sgr(UNDERLINE_DOUBLE, UNDERLINE_COLOR_RED)}A${sgr(UNDERLINE_DOUBLE, UNDERLINE_COLOR_GREEN)}B${sgr(NORMAL)}`);
221+
const differentColor = await ctx.page.evaluate(`(() => {
222+
const line = window.term.buffer.active.getLine(0);
223+
const cellA = line?.getCell(0);
224+
const cellB = line?.getCell(1);
225+
if (!cellA || !cellB) {
226+
return undefined;
227+
}
228+
return cellA.attributesEquals(cellB);
229+
})()`);
230+
strictEqual(differentColor, false);
231+
232+
await ctx.page.evaluate(`window.term.reset()`);
233+
await ctx.proxy.write(`${sgr(UNDERLINE_DOUBLE, UNDERLINE_COLOR_RED)}A${sgr(UNDERLINED, UNDERLINE_COLOR_RED)}B${sgr(NORMAL)}`);
234+
const differentStyle = await ctx.page.evaluate(`(() => {
235+
const line = window.term.buffer.active.getLine(0);
236+
const cellA = line?.getCell(0);
237+
const cellB = line?.getCell(1);
238+
if (!cellA || !cellB) {
239+
return undefined;
240+
}
241+
return cellA.attributesEquals(cellB);
242+
})()`);
243+
strictEqual(differentStyle, false);
244+
});
245+
206246
test('serialize all rows of content with color256', async function(): Promise<any> {
207247
const rows = 32;
208248
const cols = 10;
@@ -602,6 +642,9 @@ const BOLD = '1';
602642
const DIM = '2';
603643
const ITALIC = '3';
604644
const UNDERLINED = '4';
645+
const UNDERLINE_DOUBLE = '4:2';
646+
const UNDERLINE_COLOR_RED = '58;5;196';
647+
const UNDERLINE_COLOR_GREEN = '58;5;46';
605648
const BLINK = '5';
606649
const INVERSE = '7';
607650
const INVISIBLE = '8';

addons/addon-webgl/src/CellColorResolver.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ICellData } from 'common/Types';
77
import { Terminal } from '@xterm/xterm';
88
import { rgba } from 'common/Color';
99
import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';
10+
import { blockPatternCodepoints } from './customGlyphs/CustomGlyphDefinitions';
1011

1112
// Work variables to avoid garbage collection
1213
let $fg = 0;
@@ -42,7 +43,7 @@ export class CellColorResolver {
4243
* Resolves colors for the cell, putting the result into the shared {@link result}. This resolves
4344
* overrides, inverse and selection for the cell which can then be used to feed into the renderer.
4445
*/
45-
public resolve(cell: ICellData, x: number, y: number, deviceCellWidth: number): void {
46+
public resolve(cell: ICellData, x: number, y: number, deviceCellWidth: number, deviceCellHeight: number): void {
4647
this.result.bg = cell.bg;
4748
this.result.fg = cell.fg;
4849
this.result.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0;
@@ -63,7 +64,9 @@ export class CellColorResolver {
6364
const lineWidth = Math.max(1, Math.floor(this._optionService.rawOptions.fontSize * this._coreBrowserService.dpr / 15));
6465
$variantOffset = x * deviceCellWidth % (Math.round(lineWidth) * 2);
6566
}
66-
67+
if ($variantOffset === 0 && blockPatternCodepoints.has(code)) {
68+
$variantOffset = ((x * deviceCellWidth) % 2) * 2 + ((y * deviceCellHeight) % 2);
69+
}
6770
// Apply decorations on the bottom layer
6871
this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => {
6972
if (d.backgroundColorRGB) {

addons/addon-webgl/src/CharAtlasCache.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ITerminalOptions, Terminal } from '@xterm/xterm';
88
import { ITerminal, ReadonlyColorSet } from 'browser/Types';
99
import { ICharAtlasConfig, ITextureAtlas } from './Types';
1010
import { generateConfig, configEquals } from './CharAtlasUtils';
11+
import type { ILogService } from 'common/services/Services';
1112

1213
interface ITextureAtlasCacheEntry {
1314
atlas: ITextureAtlas;
@@ -67,8 +68,9 @@ export function acquireTextureAtlas(
6768
}
6869

6970
const core: ITerminal = (terminal as any)._core;
71+
const logService = (core as any)._logService as ILogService;
7072
const newEntry: ITextureAtlasCacheEntry = {
71-
atlas: new TextureAtlas(document, newConfig, core.unicodeService),
73+
atlas: new TextureAtlas(document, newConfig, core.unicodeService, logService),
7274
config: newConfig,
7375
ownedBy: [terminal]
7476
};

addons/addon-webgl/src/TextureAtlas.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { IdleTaskQueue } from 'common/TaskQueue';
1414
import { IColor } from 'common/Types';
1515
import { AttributeData } from 'common/buffer/AttributeData';
1616
import { Attributes, DEFAULT_COLOR, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants';
17-
import { IUnicodeService } from 'common/services/Services';
17+
import { ILogService, IUnicodeService } from 'common/services/Services';
1818
import { Emitter } from 'common/Event';
1919

2020
/**
@@ -88,7 +88,8 @@ export class TextureAtlas implements ITextureAtlas {
8888
constructor(
8989
private readonly _document: Document,
9090
private readonly _config: ICharAtlasConfig,
91-
private readonly _unicodeService: IUnicodeService
91+
private readonly _unicodeService: IUnicodeService,
92+
private readonly _logService: ILogService
9293
) {
9394
this._createNewPage();
9495
this._tmpCanvas = createCanvas(
@@ -119,7 +120,7 @@ export class TextureAtlas implements ITextureAtlas {
119120

120121
private _doWarmUp(): void {
121122
// Pre-fill with ASCII 33-126, this is not urgent and done in idle callbacks
122-
const queue = new IdleTaskQueue();
123+
const queue = new IdleTaskQueue(this._logService);
123124
for (let i = 33; i < 126; i++) {
124125
queue.enqueue(() => {
125126
if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT)) {
@@ -525,7 +526,8 @@ export class TextureAtlas implements ITextureAtlas {
525526
// Draw custom characters if applicable
526527
let customGlyph = false;
527528
if (this._config.customGlyphs !== false) {
528-
customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, backgroundColor.css);
529+
const variantOffset = this._workAttributeData.getUnderlineVariantOffset();
530+
customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, backgroundColor.css, variantOffset);
529531
}
530532

531533
// Whether to clear pixels based on a threshold difference between the glyph color and the

addons/addon-webgl/src/WebglRenderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
500500
}
501501

502502
// Load colors/resolve overrides into work colors
503-
this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width);
503+
this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width, this.dimensions.device.cell.height);
504504

505505
// Override colors for cursor cell
506506
if (isCursorVisible && row === cursorY) {

addons/addon-webgl/src/customGlyphs/CustomGlyphDefinitions.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,8 +644,8 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
644644
] },
645645

646646
// Diagonal fill characters (1FB98-1FB99)
647-
'\u{1FB98}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,0 L1,1 M0,.25 L.75,1 M0,.5 L.5,1 M0,.75 L.25,1 M.25,0 L1,.75 M.5,0 L1,.5 M.75,0 L1,.25', strokeWidth: 1 }, // UPPER LEFT TO LOWER RIGHT FILL
648-
'\u{1FB99}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M0,.25 L.25,0 M0,.5 L.5,0 M0,.75 L.75,0 M0,1 L1,0 M.25,1 L1,.25 M.5,1 L1,.5 M.75,1 L1,.75', strokeWidth: 1 }, // UPPER RIGHT TO LOWER LEFT FILL
647+
'\u{1FB98}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M-0.25,-0.25 L1.25,1.25 M-0.25,0 L1,1.25 M-0.25,0.25 L0.75,1.25 M-0.25,0.5 L0.5,1.25 M0,-0.25 L1.25,1 M0.25,-0.25 L1.25,0.75 M0.5,-0.25 L1.25,0.5 M-0.25,0.75 L0.25,1.25 M0.75,-0.25 L1.25,0.25', strokeWidth: 1 }, // UPPER LEFT TO LOWER RIGHT FILL
648+
'\u{1FB99}': { type: CustomGlyphDefinitionType.PATH_FUNCTION, data: 'M-0.25,0.5 L0.5,-0.25 M-0.25,0.75 L0.75,-0.25 M-0.25,1 L1,-0.25 M-0.25,1.25 L1.25,-0.25 M0,1.25 L1.25,0 M0.25,1.25 L1.25,0.25 M0.5,1.25 L1.25,0.5 M-0.25,0.25 L0.25,-0.25 M0.75,1.25 L1.25,0.75', strokeWidth: 1 }, // UPPER RIGHT TO LOWER LEFT FILL
649649

650650
// Smooth mosaic terminal graphic characters (1FB9A-1FB9B)
651651
'\u{1FB9A}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L.5,.5 L0,1 L1,1 L.5,.5 L1,0', type: CustomGlyphVectorType.FILL } }, // UPPER AND LOWER TRIANGULAR HALF BLOCK
@@ -820,6 +820,27 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
820820
// #endregion
821821
};
822822

823+
export const blockPatternCodepoints = new Set<number>([
824+
// Shade characters (2591-2593)
825+
0x2591,
826+
0x2592,
827+
0x2593,
828+
// Rectangular shade characters (1FB8C-1FB94)
829+
0x1FB8C,
830+
0x1FB8D,
831+
0x1FB8E,
832+
0x1FB8F,
833+
0x1FB90,
834+
0x1FB91,
835+
0x1FB92,
836+
0x1FB94,
837+
// Triangular shade characters (1FB9C-1FB9F)
838+
0x1FB9C,
839+
0x1FB9D,
840+
0x1FB9E,
841+
0x1FB9F
842+
]);
843+
823844
/**
824845
* Generates a drawing function for sextant characters. Sextants are a 2x3 grid where each cell
825846
* can be on or off.

0 commit comments

Comments
 (0)