Add attributesEqual and underline diff APIs

Fixes #3440
This commit is contained in:
Daniel Imms
2026-02-06 16:21:25 -08:00
parent 452ee50c81
commit cc8e680790
10 changed files with 253 additions and 23 deletions
+65
View File
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { Attributes, BgFlags, FgFlags, UnderlineStyle } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { assert } from 'chai';
function createStyledCell(char: string, underlineStyle: UnderlineStyle, underlineColor: number): CellData {
const cell = new CellData();
const fg = Attributes.CM_P256 | 12 | FgFlags.BOLD | FgFlags.UNDERLINE;
cell.setFromCharData([fg, char, 1, char.charCodeAt(0)]);
cell.bg = Attributes.CM_P16 | 2 | BgFlags.ITALIC;
cell.extended.underlineStyle = underlineStyle;
cell.extended.underlineColor = Attributes.CM_P256 | underlineColor;
cell.updateExtended();
return cell;
}
describe('CellData', () => {
describe('attributesEquals', () => {
it('returns true for same attributes with different chars', () => {
const cellA = createStyledCell('A', UnderlineStyle.DOUBLE, 45);
const cellB = createStyledCell('B', UnderlineStyle.DOUBLE, 45);
assert.equal(cellA.attributesEquals(cellB), true);
});
it('detects underline style changes', () => {
const cellA = createStyledCell('A', UnderlineStyle.DOUBLE, 45);
const cellB = createStyledCell('B', UnderlineStyle.SINGLE, 45);
assert.equal(cellA.attributesEquals(cellB), false);
});
it('detects underline color changes', () => {
const cellA = createStyledCell('A', UnderlineStyle.SINGLE, 45);
const cellB = createStyledCell('B', UnderlineStyle.SINGLE, 46);
assert.equal(cellA.attributesEquals(cellB), false);
});
it('ignores underline variant offsets', () => {
const cellA = createStyledCell('A', UnderlineStyle.SINGLE, 45);
const cellB = createStyledCell('B', UnderlineStyle.SINGLE, 45);
cellA.extended.underlineVariantOffset = 1;
cellB.extended.underlineVariantOffset = 3;
cellA.updateExtended();
cellB.updateExtended();
assert.equal(cellA.attributesEquals(cellB), true);
});
it('ignores url ids', () => {
const cellA = createStyledCell('A', UnderlineStyle.SINGLE, 45);
const cellB = createStyledCell('B', UnderlineStyle.SINGLE, 45);
cellA.extended.urlId = 1;
cellB.extended.urlId = 2;
cellA.updateExtended();
cellB.updateExtended();
assert.equal(cellA.attributesEquals(cellB), true);
});
});
});
+59 -1
View File
@@ -3,10 +3,11 @@
* @license MIT
*/
import { CharData, ICellData, IExtendedAttrs } from 'common/Types';
import { CharData, IAttributeData, ICellData, IExtendedAttrs } from 'common/Types';
import { stringFromCodePoint } from 'common/input/TextDecoder';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, Content } from 'common/buffer/Constants';
import { AttributeData, ExtendedAttrs } from 'common/buffer/AttributeData';
import type { IBufferCell as IBufferCellApi } from '@xterm/xterm';
/**
* CellData - represents a single Cell in the terminal buffer.
@@ -91,4 +92,61 @@ export class CellData extends AttributeData implements ICellData {
public getAsCharData(): CharData {
return [this.fg, this.getChars(), this.getWidth(), this.getCode()];
}
public attributesEquals(other: IBufferCellApi): boolean {
if (this.getFgColorMode() !== other.getFgColorMode() || this.getFgColor() !== other.getFgColor()) {
return false;
}
if (this.getBgColorMode() !== other.getBgColorMode() || this.getBgColor() !== other.getBgColor()) {
return false;
}
if (this.isInverse() !== other.isInverse()) {
return false;
}
if (this.isBold() !== other.isBold()) {
return false;
}
if (this.isUnderline() !== other.isUnderline()) {
return false;
}
if (this.isUnderline()) {
const otherData = other as unknown as IAttributeData;
if (this.getUnderlineStyle() !== otherData.getUnderlineStyle()) {
return false;
}
const thisDefault = this.isUnderlineColorDefault();
const otherDefault = otherData.isUnderlineColorDefault();
if (!(thisDefault && otherDefault)) {
if (thisDefault !== otherDefault) {
return false;
}
if (this.getUnderlineColor() !== otherData.getUnderlineColor()) {
return false;
}
if (this.getUnderlineColorMode() !== otherData.getUnderlineColorMode()) {
return false;
}
}
}
if (this.isOverline() !== other.isOverline()) {
return false;
}
if (this.isBlink() !== other.isBlink()) {
return false;
}
if (this.isInvisible() !== other.isInvisible()) {
return false;
}
if (this.isItalic() !== other.isItalic()) {
return false;
}
if (this.isDim() !== other.isDim()) {
return false;
}
if (this.isStrikethrough() !== other.isStrikethrough()) {
return false;
}
return true;
}
}