Merge branch 'master' into clusters
This commit is contained in:
@@ -109,7 +109,8 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
||||
}
|
||||
|
||||
private _handleHover(position: IBufferCellPosition): void {
|
||||
// TODO: This currently does not cache link provider results across wrapped lines, activeLine should be something like `activeRange: {startY, endY}`
|
||||
// TODO: This currently does not cache link provider results across wrapped lines, activeLine
|
||||
// should be something like `activeRange: {startY, endY}`
|
||||
// Check if we need to clear the link
|
||||
if (this._activeLine !== position.y || this._wasResized) {
|
||||
this._clearCurrentLink();
|
||||
|
||||
@@ -104,7 +104,8 @@ export class OscLinkProvider implements ILinkProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Handle fetching and returning other link ranges to underline other links with the same id
|
||||
// TODO: Handle fetching and returning other link ranges to underline other links with the same
|
||||
// id
|
||||
callback(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,6 +529,8 @@ export class MockThemeService implements IThemeService{
|
||||
css.toColor('#ad7fa8'),
|
||||
css.toColor('#34e2e2'),
|
||||
css.toColor('#eeeeec')
|
||||
]
|
||||
],
|
||||
selectionBackgroundOpaque: css.toColor('#ff0000'),
|
||||
selectionInactiveBackgroundOpaque: css.toColor('#00ff00')
|
||||
} as any;
|
||||
}
|
||||
|
||||
Vendored
+3
@@ -136,7 +136,10 @@ export interface IColorSet {
|
||||
selectionInactiveBackgroundTransparent: IColor;
|
||||
selectionInactiveBackgroundOpaque: IColor;
|
||||
ansi: IColor[];
|
||||
/** Maps original colors to colors that respect minimum contrast ratio. */
|
||||
contrastCache: IColorContrastCache;
|
||||
/** Maps original colors to colors that respect _half_ of the minimum contrast ratio. */
|
||||
halfContrastCache: IColorContrastCache;
|
||||
}
|
||||
|
||||
export type ReadonlyColorSet = Readonly<Omit<IColorSet, 'ansi'>> & { ansi: Readonly<Pick<IColorSet, 'ansi'>['ansi']> };
|
||||
|
||||
@@ -65,8 +65,8 @@ export class Viewport extends Disposable implements IViewport {
|
||||
super();
|
||||
|
||||
// Measure the width of the scrollbar. If it is 0 we can assume it's an OSX overlay scrollbar.
|
||||
// Unfortunately the overlay scrollbar would be hidden underneath the screen element in that case,
|
||||
// therefore we account for a standard amount to make it visible
|
||||
// Unfortunately the overlay scrollbar would be hidden underneath the screen element in that
|
||||
// case, therefore we account for a standard amount to make it visible
|
||||
this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
|
||||
this.register(addDisposableDomListener(this._viewportElement, 'scroll', this._handleScroll.bind(this)));
|
||||
|
||||
|
||||
@@ -103,6 +103,10 @@ export class BufferDecorationRenderer extends Disposable {
|
||||
decoration.element = element;
|
||||
this._decorationElements.set(decoration, element);
|
||||
this._container.appendChild(element);
|
||||
decoration.onDispose(() => {
|
||||
this._decorationElements.delete(decoration);
|
||||
element!.remove();
|
||||
});
|
||||
}
|
||||
element.style.top = `${line * this._renderService.dimensions.css.cell.height}px`;
|
||||
element.style.display = this._altBufferIsActive ? 'none' : 'block';
|
||||
|
||||
@@ -197,23 +197,20 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
`}`;
|
||||
// Cursor
|
||||
styles +=
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} ,` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} ,` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} ` +
|
||||
`{` +
|
||||
` outline: 1px solid ${colors.cursor.css};` +
|
||||
` outline-offset: -1px;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}:not(.${RowCss.CURSOR_STYLE_BLOCK_CLASS}) {` +
|
||||
` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +
|
||||
` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +
|
||||
` background-color: ${colors.cursor.css};` +
|
||||
` color: ${colors.cursorAccent.css};` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_OUTLINE_CLASS} {` +
|
||||
` outline: 1px solid ${colors.cursor.css};` +
|
||||
` outline-offset: -1px;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} {` +
|
||||
` box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${colors.cursor.css} inset;` +
|
||||
`}` +
|
||||
@@ -392,7 +389,8 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
public clear(): void {
|
||||
for (const e of this._rowElements) {
|
||||
/**
|
||||
* NOTE: This used to be `e.innerText = '';` but that doesn't work when using `jsdom` and `@testing-library/react`
|
||||
* NOTE: This used to be `e.innerText = '';` but that doesn't work when using `jsdom` and
|
||||
* `@testing-library/react`
|
||||
*
|
||||
* references:
|
||||
* - https://github.com/testing-library/react-testing-library/issues/1146
|
||||
@@ -408,6 +406,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
const cursorX = Math.min(buffer.x, this._bufferService.cols - 1);
|
||||
const cursorBlink = this._optionsService.rawOptions.cursorBlink;
|
||||
const cursorStyle = this._optionsService.rawOptions.cursorStyle;
|
||||
const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
|
||||
|
||||
for (let y = start; y <= end; y++) {
|
||||
const row = y + buffer.ydisp;
|
||||
@@ -422,6 +421,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
row,
|
||||
row === cursorAbsoluteY,
|
||||
cursorStyle,
|
||||
cursorInactiveStyle,
|
||||
cursorX,
|
||||
cursorBlink,
|
||||
this.dimensions.css.cell.width,
|
||||
@@ -474,6 +474,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
const cursorX = Math.min(buffer.x, cols - 1);
|
||||
const cursorBlink = this._optionsService.rawOptions.cursorBlink;
|
||||
const cursorStyle = this._optionsService.rawOptions.cursorStyle;
|
||||
const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
|
||||
|
||||
// refresh rows within link range
|
||||
for (let i = y; i <= y2; ++i) {
|
||||
@@ -489,6 +490,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
row,
|
||||
row === cursorAbsoluteY,
|
||||
cursorStyle,
|
||||
cursorInactiveStyle,
|
||||
cursorX,
|
||||
cursorBlink,
|
||||
this.dimensions.css.cell.width,
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('DomRendererRowFactory', () => {
|
||||
|
||||
describe('createRow', () => {
|
||||
it('should not create anything for an empty row', () => {
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
''
|
||||
);
|
||||
@@ -50,7 +50,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)]));
|
||||
// There should be no element for the following "empty" cell
|
||||
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '', 0, 0]));
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>語</span>'
|
||||
);
|
||||
@@ -58,7 +58,7 @@ describe('DomRendererRowFactory', () => {
|
||||
|
||||
it('should add class for cursor and cursor style', () => {
|
||||
for (const style of ['block', 'bar', 'underline']) {
|
||||
const spans = rowFactory.createRow(lineData, 0, true, style, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, true, style, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
`<span class="xterm-cursor xterm-cursor-${style}"> </span>`
|
||||
);
|
||||
@@ -66,18 +66,42 @@ describe('DomRendererRowFactory', () => {
|
||||
});
|
||||
|
||||
it('should add class for cursor blink', () => {
|
||||
const spans = rowFactory.createRow(lineData, 0, true, 'block', 0, true, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, true, 'block', undefined, 0, true, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
`<span class="xterm-cursor xterm-cursor-blink xterm-cursor-block"> </span>`
|
||||
);
|
||||
});
|
||||
|
||||
it('should add class for inactive cursor', () => {
|
||||
const coreBrowserService = new MockCoreBrowserService();
|
||||
coreBrowserService.isFocused = false;
|
||||
const rowFactory = new DomRendererRowFactory(
|
||||
dom.window.document,
|
||||
new MockCharacterJoinerService(),
|
||||
new MockOptionsService({ drawBoldTextInBrightColors: true }),
|
||||
coreBrowserService,
|
||||
new MockCoreService(),
|
||||
new MockDecorationService(),
|
||||
new MockThemeService()
|
||||
);
|
||||
for (const inactiveStyle of ['outline', 'block', 'bar', 'underline', 'none']){
|
||||
const spans = rowFactory.createRow(lineData, 0, true, 'block', inactiveStyle, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
if (inactiveStyle === 'none') {
|
||||
assert.equal(extractHtml(spans),
|
||||
`<span class="xterm-cursor"> </span>`);
|
||||
} else {
|
||||
assert.equal(extractHtml(spans),
|
||||
`<span class="xterm-cursor xterm-cursor-${inactiveStyle}"> </span>`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('attributes', () => {
|
||||
it('should add class for bold', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.BOLD;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bold">a</span>'
|
||||
);
|
||||
@@ -87,7 +111,7 @@ describe('DomRendererRowFactory', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.ITALIC;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-italic">a</span>'
|
||||
);
|
||||
@@ -97,7 +121,7 @@ describe('DomRendererRowFactory', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.DIM;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-dim">a</span>'
|
||||
);
|
||||
@@ -110,7 +134,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;
|
||||
cell.extended.underlineStyle = UnderlineStyle.SINGLE;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-underline-1">a</span>'
|
||||
);
|
||||
@@ -121,7 +145,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;
|
||||
cell.extended.underlineStyle = UnderlineStyle.DOUBLE;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-underline-2">a</span>'
|
||||
);
|
||||
@@ -132,7 +156,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;
|
||||
cell.extended.underlineStyle = UnderlineStyle.CURLY;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-underline-3">a</span>'
|
||||
);
|
||||
@@ -143,7 +167,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;
|
||||
cell.extended.underlineStyle = UnderlineStyle.DOTTED;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-underline-4">a</span>'
|
||||
);
|
||||
@@ -154,7 +178,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;
|
||||
cell.extended.underlineStyle = UnderlineStyle.DASHED;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-underline-5">a</span>'
|
||||
);
|
||||
@@ -165,7 +189,7 @@ describe('DomRendererRowFactory', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.OVERLINE;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-overline">a</span>'
|
||||
);
|
||||
@@ -175,7 +199,7 @@ describe('DomRendererRowFactory', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.STRIKETHROUGH;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-strikethrough">a</span>'
|
||||
);
|
||||
@@ -188,7 +212,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.fg &= ~Attributes.PCOLOR_MASK;
|
||||
cell.fg |= i;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
`<span class="xterm-fg-${i}">a</span>`
|
||||
);
|
||||
@@ -202,7 +226,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.bg &= ~Attributes.PCOLOR_MASK;
|
||||
cell.bg |= i;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
`<span class="xterm-bg-${i}">a</span>`
|
||||
);
|
||||
@@ -214,7 +238,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.fg |= Attributes.CM_P16 | 2 | FgFlags.INVERSE;
|
||||
cell.bg |= Attributes.CM_P16 | 1;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bg-2 xterm-fg-1">a</span>'
|
||||
);
|
||||
@@ -225,7 +249,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.fg |= FgFlags.INVERSE;
|
||||
cell.bg |= Attributes.CM_P16 | 1;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bg-257 xterm-fg-1">a</span>'
|
||||
);
|
||||
@@ -235,7 +259,7 @@ describe('DomRendererRowFactory', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.fg |= Attributes.CM_P16 | 1 | FgFlags.INVERSE;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bg-1 xterm-fg-257">a</span>'
|
||||
);
|
||||
@@ -248,7 +272,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.fg &= ~Attributes.PCOLOR_MASK;
|
||||
cell.fg |= i;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
`<span class="xterm-bold xterm-fg-${i + 8}">a</span>`
|
||||
);
|
||||
@@ -260,7 +284,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3;
|
||||
cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span style="background-color:#040506;color:#010203;">a</span>'
|
||||
);
|
||||
@@ -271,7 +295,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3 | FgFlags.INVERSE;
|
||||
cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span style="background-color:#010203;color:#040506;">a</span>'
|
||||
);
|
||||
@@ -283,17 +307,17 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
|
||||
rowFactory.handleSelectionChanged([1, 0], [2, 0], false);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>a</span><span class="xterm-decoration-top">b</span>'
|
||||
'<span>a</span><span style="background-color:#ff0000;" class="xterm-decoration-top">b</span>'
|
||||
);
|
||||
});
|
||||
it('should force whitespace cells to be rendered above the background', () => {
|
||||
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
rowFactory.handleSelectionChanged([0, 0], [2, 0], false);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-decoration-top"> </span><span class="xterm-decoration-top">a</span>'
|
||||
'<span style="background-color:#ff0000;" class="xterm-decoration-top"> a</span>'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -308,7 +332,7 @@ describe('DomRendererRowFactory', () => {
|
||||
});
|
||||
|
||||
it('should not create anything for an empty row', () => {
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
''
|
||||
);
|
||||
@@ -318,7 +342,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
|
||||
lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'c', 1, 'c'.charCodeAt(0)]));
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>abc</span>'
|
||||
);
|
||||
@@ -329,7 +353,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '€', 1, '€'.charCodeAt(0)]));
|
||||
lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'c', 1, 'c'.charCodeAt(0)]));
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>a</span><span style="letter-spacing: 3px;">€</span><span>c</span>'
|
||||
);
|
||||
@@ -344,7 +368,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(1, aColor1);
|
||||
lineData.setCell(2, bColor2);
|
||||
lineData.setCell(3, bColor2);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-fg-1">aa</span><span class="xterm-fg-2">bb</span>'
|
||||
);
|
||||
@@ -356,7 +380,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'X', 1, 'X'.charCodeAt(0)]));
|
||||
lineData.setCell(3, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
|
||||
lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
|
||||
const spans = rowFactory.createRow(lineData, 0, true, undefined, 2, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, true, undefined, undefined, 2, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>aa</span><span class="xterm-cursor xterm-cursor-block">X</span><span>bb</span>'
|
||||
);
|
||||
@@ -369,7 +393,7 @@ describe('DomRendererRowFactory', () => {
|
||||
nullCell.bg = Attributes.CM_P16 | 2;
|
||||
lineData.setCell(3, nullCell);
|
||||
lineData.setCell(4, nullCell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span> </span><span class="xterm-bg-1"> </span><span class="xterm-bg-2"> </span>'
|
||||
);
|
||||
@@ -379,23 +403,23 @@ describe('DomRendererRowFactory', () => {
|
||||
const nullCell = lineData.loadCell(0, new CellData());
|
||||
nullCell.bg = Attributes.CM_P16 | 1;
|
||||
lineData.setCell(0, nullCell);
|
||||
let spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
let spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bg-1"> </span>'
|
||||
);
|
||||
lineData.setCell(1, nullCell);
|
||||
spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bg-1"> </span>'
|
||||
);
|
||||
lineData.setCell(2, nullCell);
|
||||
lineData.setCell(3, nullCell);
|
||||
spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bg-1"> </span>'
|
||||
);
|
||||
lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bg-1"> </span><span>a</span>'
|
||||
);
|
||||
@@ -410,7 +434,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'c', 1, 'c'.charCodeAt(0)]));
|
||||
lineData.setCell(3, CellData.fromCharData([DEFAULT_ATTR, '語', 2, 'c'.charCodeAt(0)]));
|
||||
lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, '𝄞', 1, 'c'.charCodeAt(0)]));
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>a</span><span style="letter-spacing: 3px;">€</span><span>c語</span><span style="letter-spacing: -2px;">𝄞</span>'
|
||||
);
|
||||
@@ -424,7 +448,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)]));
|
||||
lineData.setCell(5, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
|
||||
lineData.setCell(6, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, 2, 4);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, 2, 4);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>aa</span><span style="text-decoration: underline;">xxx</span><span>bb</span>'
|
||||
);
|
||||
@@ -435,7 +459,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)]));
|
||||
lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)]));
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, 2, 4);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, 2, 4);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>aa</span><span style="text-decoration: underline;">x x</span>'
|
||||
);
|
||||
@@ -445,7 +469,7 @@ describe('DomRendererRowFactory', () => {
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
lineData.setCell(i, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
}
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -100, 100);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -100, 100);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span style="text-decoration: underline;">aaaaaaaaaa</span>'
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { JoinedCellData } from 'browser/services/CharacterJoinerService';
|
||||
import { excludeFromContrastRatioDemands } from 'browser/renderer/shared/RendererUtils';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { WidthCache } from 'browser/renderer/dom/WidthCache';
|
||||
import { IColorContrastCache } from 'browser/Types';
|
||||
|
||||
|
||||
export const enum RowCss {
|
||||
@@ -26,6 +27,7 @@ export const enum RowCss {
|
||||
CURSOR_CLASS = 'xterm-cursor',
|
||||
CURSOR_BLINK_CLASS = 'xterm-cursor-blink',
|
||||
CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block',
|
||||
CURSOR_STYLE_OUTLINE_CLASS = 'xterm-cursor-outline',
|
||||
CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar',
|
||||
CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline'
|
||||
}
|
||||
@@ -61,6 +63,7 @@ export class DomRendererRowFactory {
|
||||
row: number,
|
||||
isCursorRow: boolean,
|
||||
cursorStyle: string | undefined,
|
||||
cursorInactiveStyle: string | undefined,
|
||||
cursorX: number,
|
||||
cursorBlink: boolean,
|
||||
cellWidth: number,
|
||||
@@ -86,6 +89,7 @@ export class DomRendererRowFactory {
|
||||
let oldExt = 0;
|
||||
let oldLinkHover: number | boolean = false;
|
||||
let oldSpacing = 0;
|
||||
let oldIsInSelection: boolean = false;
|
||||
let spacing = 0;
|
||||
const classes: string[] = [];
|
||||
|
||||
@@ -151,16 +155,24 @@ export class DomRendererRowFactory {
|
||||
/**
|
||||
* chars can only be merged on existing span if:
|
||||
* - existing span only contains mergeable chars (cellAmount != 0)
|
||||
* - fg/bg/ul did not change
|
||||
* - char not part of a selection
|
||||
* - bg did not change (or both are in selection)
|
||||
* - fg did not change (or both are in selection and selection fg is set)
|
||||
* - ext did not change
|
||||
* - underline from hover state did not change
|
||||
* - cell content renders to same letter-spacing
|
||||
* - cell is not cursor
|
||||
*/
|
||||
if (
|
||||
cellAmount
|
||||
&& cell.bg === oldBg && cell.fg === oldFg && cell.extended.ext === oldExt
|
||||
&& !isInSelection
|
||||
&& (
|
||||
(isInSelection && oldIsInSelection)
|
||||
|| (!isInSelection && cell.bg === oldBg)
|
||||
)
|
||||
&& (
|
||||
(isInSelection && oldIsInSelection && colors.selectionForeground)
|
||||
|| (!(isInSelection && oldIsInSelection && colors.selectionForeground) && cell.fg === oldFg)
|
||||
)
|
||||
&& cell.extended.ext === oldExt
|
||||
&& isLinkHover === oldLinkHover
|
||||
&& spacing === oldSpacing
|
||||
&& !isCursorCell
|
||||
@@ -191,6 +203,7 @@ export class DomRendererRowFactory {
|
||||
oldExt = cell.extended.ext;
|
||||
oldLinkHover = isLinkHover;
|
||||
oldSpacing = spacing;
|
||||
oldIsInSelection = isInSelection;
|
||||
|
||||
if (isJoined) {
|
||||
// The DOM renderer colors the background of the cursor but for ligatures all cells are
|
||||
@@ -203,16 +216,37 @@ export class DomRendererRowFactory {
|
||||
|
||||
if (!this._coreService.isCursorHidden && isCursorCell) {
|
||||
classes.push(RowCss.CURSOR_CLASS);
|
||||
if (cursorBlink) {
|
||||
classes.push(RowCss.CURSOR_BLINK_CLASS);
|
||||
if (this._coreBrowserService.isFocused) {
|
||||
if (cursorBlink) {
|
||||
classes.push(RowCss.CURSOR_BLINK_CLASS);
|
||||
}
|
||||
classes.push(
|
||||
cursorStyle === 'bar'
|
||||
? RowCss.CURSOR_STYLE_BAR_CLASS
|
||||
: cursorStyle === 'underline'
|
||||
? RowCss.CURSOR_STYLE_UNDERLINE_CLASS
|
||||
: RowCss.CURSOR_STYLE_BLOCK_CLASS
|
||||
);
|
||||
} else {
|
||||
if (cursorInactiveStyle) {
|
||||
switch (cursorInactiveStyle) {
|
||||
case 'outline':
|
||||
classes.push(RowCss.CURSOR_STYLE_OUTLINE_CLASS);
|
||||
break;
|
||||
case 'block':
|
||||
classes.push(RowCss.CURSOR_STYLE_BLOCK_CLASS);
|
||||
break;
|
||||
case 'bar':
|
||||
classes.push(RowCss.CURSOR_STYLE_BAR_CLASS);
|
||||
break;
|
||||
case 'underline':
|
||||
classes.push(RowCss.CURSOR_STYLE_UNDERLINE_CLASS);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
classes.push(
|
||||
cursorStyle === 'bar'
|
||||
? RowCss.CURSOR_STYLE_BAR_CLASS
|
||||
: cursorStyle === 'underline'
|
||||
? RowCss.CURSOR_STYLE_UNDERLINE_CLASS
|
||||
: RowCss.CURSOR_STYLE_BLOCK_CLASS
|
||||
);
|
||||
}
|
||||
|
||||
if (cell.isBold()) {
|
||||
@@ -262,7 +296,8 @@ export class DomRendererRowFactory {
|
||||
classes.push(RowCss.STRIKETHROUGH_CLASS);
|
||||
}
|
||||
|
||||
// apply link hover underline late, effectively overrides any previous text-decoration settings
|
||||
// apply link hover underline late, effectively overrides any previous text-decoration
|
||||
// settings
|
||||
if (isLinkHover) {
|
||||
charElement.style.textDecoration = 'underline';
|
||||
}
|
||||
@@ -303,22 +338,26 @@ export class DomRendererRowFactory {
|
||||
isTop = d.options.layer === 'top';
|
||||
});
|
||||
|
||||
// Apply selection foreground if applicable
|
||||
if (!isTop) {
|
||||
if (colors.selectionForeground && isInSelection) {
|
||||
// Apply selection
|
||||
if (!isTop && isInSelection) {
|
||||
// If in the selection, force the element to be above the selection to improve contrast and
|
||||
// support opaque selections. The applies background is not actually needed here as
|
||||
// selection is drawn in a seperate container, the main purpose of this to ensuring minimum
|
||||
// contrast ratio
|
||||
bgOverride = this._coreBrowserService.isFocused ? colors.selectionBackgroundOpaque : colors.selectionInactiveBackgroundOpaque;
|
||||
bg = bgOverride.rgba >> 8 & 0xFFFFFF;
|
||||
bgColorMode = Attributes.CM_RGB;
|
||||
// Since an opaque selection is being rendered, the selection pretends to be a decoration to
|
||||
// ensure text is drawn above the selection.
|
||||
isTop = true;
|
||||
// Apply selection foreground if applicable
|
||||
if (colors.selectionForeground) {
|
||||
fgColorMode = Attributes.CM_RGB;
|
||||
fg = colors.selectionForeground.rgba >> 8 & 0xFFFFFF;
|
||||
fgOverride = colors.selectionForeground;
|
||||
}
|
||||
}
|
||||
|
||||
// If in the selection, force the element to be above the selection to improve contrast and
|
||||
// support opaque selections
|
||||
if (isInSelection) {
|
||||
bgOverride = this._coreBrowserService.isFocused ? colors.selectionBackgroundOpaque : colors.selectionInactiveBackgroundOpaque;
|
||||
isTop = true;
|
||||
}
|
||||
|
||||
// If it's a top decoration, render above the selection
|
||||
if (isTop) {
|
||||
classes.push('xterm-decoration-top');
|
||||
@@ -392,7 +431,7 @@ export class DomRendererRowFactory {
|
||||
}
|
||||
|
||||
// exclude conditions for cell merging - never merge these
|
||||
if (!isCursorCell && !isInSelection && !isJoined && !isDecorated) {
|
||||
if (!isCursorCell && !isJoined && !isDecorated) {
|
||||
cellAmount++;
|
||||
} else {
|
||||
charElement.textContent = text;
|
||||
@@ -420,15 +459,19 @@ export class DomRendererRowFactory {
|
||||
}
|
||||
|
||||
// Try get from cache first, only use the cache when there are no decoration overrides
|
||||
const cache = this._getContrastCache(cell);
|
||||
let adjustedColor: IColor | undefined | null = undefined;
|
||||
if (!bgOverride && !fgOverride) {
|
||||
adjustedColor = this._themeService.colors.contrastCache.getColor(bg.rgba, fg.rgba);
|
||||
adjustedColor = cache.getColor(bg.rgba, fg.rgba);
|
||||
}
|
||||
|
||||
// Calculate and store in cache
|
||||
if (adjustedColor === undefined) {
|
||||
adjustedColor = color.ensureContrastRatio(bgOverride || bg, fgOverride || fg, this._optionsService.rawOptions.minimumContrastRatio);
|
||||
this._themeService.colors.contrastCache.setColor((bgOverride || bg).rgba, (fgOverride || fg).rgba, adjustedColor ?? null);
|
||||
// Dim cells only require half the contrast, otherwise they wouldn't be distinguishable from
|
||||
// non-dim cells
|
||||
const ratio = this._optionsService.rawOptions.minimumContrastRatio / (cell.isDim() ? 2 : 1);
|
||||
adjustedColor = color.ensureContrastRatio(bgOverride || bg, fgOverride || fg, ratio);
|
||||
cache.setColor((bgOverride || bg).rgba, (fgOverride || fg).rgba, adjustedColor ?? null);
|
||||
}
|
||||
|
||||
if (adjustedColor) {
|
||||
@@ -439,6 +482,13 @@ export class DomRendererRowFactory {
|
||||
return false;
|
||||
}
|
||||
|
||||
private _getContrastCache(cell: ICellData): IColorContrastCache {
|
||||
if (cell.isDim()) {
|
||||
return this._themeService.colors.halfContrastCache;
|
||||
}
|
||||
return this._themeService.colors.contrastCache;
|
||||
}
|
||||
|
||||
private _addStyle(element: HTMLElement, style: string): void {
|
||||
element.setAttribute('style', `${element.getAttribute('style') || ''}${style};`);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ export function generateConfig(deviceCellWidth: number, deviceCellHeight: number
|
||||
// For the static char atlas, we only use the first 16 colors, but we need all 256 for the
|
||||
// dynamic character atlas.
|
||||
ansi: colors.ansi.slice(),
|
||||
contrastCache: colors.contrastCache
|
||||
contrastCache: colors.contrastCache,
|
||||
halfContrastCache: colors.halfContrastCache
|
||||
};
|
||||
return {
|
||||
customGlyphs: options.customGlyphs,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { isFirefox, isLegacyEdge } from 'common/Platform';
|
||||
export const INVERTED_DEFAULT_COLOR = 257;
|
||||
|
||||
export const DIM_OPACITY = 0.5;
|
||||
// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge would
|
||||
// result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly
|
||||
// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge
|
||||
// would result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly
|
||||
// unaligned Powerline fonts (PR 3356#issuecomment-850928179).
|
||||
export const TEXT_BASELINE: CanvasTextBaseline = isFirefox || isLegacyEdge ? 'bottom' : 'ideographic';
|
||||
|
||||
@@ -349,7 +349,8 @@ const enum VectorType {
|
||||
* not been patched with powerline characters and also to get pixel perfect rendering as rendering
|
||||
* issues can occur around AA/SPAA.
|
||||
*
|
||||
* The line variants draw beyond the cell and get clipped to ensure the end of the line is not visible.
|
||||
* The line variants draw beyond the cell and get clipped to ensure the end of the line is not
|
||||
* visible.
|
||||
*
|
||||
* Original symbols defined in https://github.com/powerline/fontpatcher
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,7 @@ import { FourKeyMap } from 'common/MultiKeyMap';
|
||||
import { IdleTaskQueue } from 'common/TaskQueue';
|
||||
import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { IColorContrastCache } from 'browser/Types';
|
||||
|
||||
/**
|
||||
* A shared object which is used to draw nothing for a particular cell.
|
||||
@@ -298,7 +299,7 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (inverse) {
|
||||
result = this._config.colors.foreground;
|
||||
result = color.opaque(this._config.colors.foreground);
|
||||
} else {
|
||||
result = this._config.colors.background;
|
||||
}
|
||||
@@ -309,8 +310,7 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
}
|
||||
|
||||
private _getForegroundColor(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, dim: boolean, bold: boolean, excludeFromContrastRatioDemands: boolean): IColor {
|
||||
// TODO: Pass dim along to get min contrast?
|
||||
const minimumContrastColor = this._getMinimumContrastColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, false, bold, excludeFromContrastRatioDemands);
|
||||
const minimumContrastColor = this._getMinimumContrastColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, false, bold, dim, excludeFromContrastRatioDemands);
|
||||
if (minimumContrastColor) {
|
||||
return minimumContrastColor;
|
||||
}
|
||||
@@ -385,23 +385,26 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
}
|
||||
}
|
||||
|
||||
private _getMinimumContrastColor(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean, excludeFromContrastRatioDemands: boolean): IColor | undefined {
|
||||
private _getMinimumContrastColor(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean, dim: boolean, excludeFromContrastRatioDemands: boolean): IColor | undefined {
|
||||
if (this._config.minimumContrastRatio === 1 || excludeFromContrastRatioDemands) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Try get from cache first
|
||||
const adjustedColor = this._config.colors.contrastCache.getColor(bg, fg);
|
||||
const cache = this._getContrastCache(dim);
|
||||
const adjustedColor = cache.getColor(bg, fg);
|
||||
if (adjustedColor !== undefined) {
|
||||
return adjustedColor || undefined;
|
||||
}
|
||||
|
||||
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse);
|
||||
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, inverse, bold);
|
||||
const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._config.minimumContrastRatio);
|
||||
// Dim cells only require half the contrast, otherwise they wouldn't be distinguishable from
|
||||
// non-dim cells
|
||||
const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._config.minimumContrastRatio / (dim ? 2 : 1));
|
||||
|
||||
if (!result) {
|
||||
this._config.colors.contrastCache.setColor(bg, fg, null);
|
||||
cache.setColor(bg, fg, null);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -410,11 +413,18 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
(result >> 16) & 0xFF,
|
||||
(result >> 8) & 0xFF
|
||||
);
|
||||
this._config.colors.contrastCache.setColor(bg, fg, color);
|
||||
cache.setColor(bg, fg, color);
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
private _getContrastCache(dim: boolean): IColorContrastCache {
|
||||
if (dim) {
|
||||
return this._config.colors.halfContrastCache;
|
||||
}
|
||||
return this._config.colors.contrastCache;
|
||||
}
|
||||
|
||||
private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean = false): IRasterizedGlyph {
|
||||
const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars;
|
||||
|
||||
@@ -466,8 +476,8 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
|
||||
// draw the background
|
||||
const backgroundColor = this._getBackgroundColor(bgColorMode, bgColor, inverse, dim);
|
||||
// Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of
|
||||
// transparency in backgroundColor
|
||||
// Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha,
|
||||
// regardless of transparency in backgroundColor
|
||||
this._tmpCtx.globalCompositeOperation = 'copy';
|
||||
this._tmpCtx.fillStyle = backgroundColor.css;
|
||||
this._tmpCtx.fillRect(0, 0, this._tmpCanvas.width, this._tmpCanvas.height);
|
||||
@@ -532,10 +542,9 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
// Underline style/stroke
|
||||
this._tmpCtx.beginPath();
|
||||
const xLeft = padding;
|
||||
const yTop = Math.ceil(padding + this._config.deviceCharHeight) - yOffset;
|
||||
const yMid = padding + this._config.deviceCharHeight + lineWidth - yOffset;
|
||||
const yBot = Math.ceil(padding + this._config.deviceCharHeight + lineWidth * 2) - yOffset;
|
||||
const ySpace = lineWidth * 2;
|
||||
const yTop = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0);
|
||||
const yMid = yTop + lineWidth;
|
||||
const yBot = yTop + lineWidth * 2;
|
||||
|
||||
for (let i = 0; i < chWidth; i++) {
|
||||
this._tmpCtx.save();
|
||||
@@ -544,17 +553,10 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
const xChMid = xChLeft + this._config.deviceCellWidth / 2;
|
||||
switch (this._workAttributeData.extended.underlineStyle) {
|
||||
case UnderlineStyle.DOUBLE:
|
||||
if (restrictToCellHeight) {
|
||||
this._tmpCtx.moveTo(xChLeft, yTop - ySpace);
|
||||
this._tmpCtx.lineTo(xChRight, yTop - ySpace);
|
||||
this._tmpCtx.moveTo(xChLeft, yTop);
|
||||
this._tmpCtx.lineTo(xChRight, yTop);
|
||||
} else {
|
||||
this._tmpCtx.moveTo(xChLeft, yTop);
|
||||
this._tmpCtx.lineTo(xChRight, yTop);
|
||||
this._tmpCtx.moveTo(xChLeft, yBot);
|
||||
this._tmpCtx.lineTo(xChRight, yBot);
|
||||
}
|
||||
this._tmpCtx.moveTo(xChLeft, yTop);
|
||||
this._tmpCtx.lineTo(xChRight, yTop);
|
||||
this._tmpCtx.moveTo(xChLeft, yBot);
|
||||
this._tmpCtx.lineTo(xChRight, yBot);
|
||||
break;
|
||||
case UnderlineStyle.CURLY:
|
||||
// Choose the bezier top and bottom based on the device pixel ratio, the curly line is
|
||||
@@ -567,7 +569,8 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
const clipRegion = new Path2D();
|
||||
clipRegion.rect(xChLeft, yTop, this._config.deviceCellWidth, yBot - yTop);
|
||||
this._tmpCtx.clip(clipRegion);
|
||||
// Start 1/2 cell before and end 1/2 cells after to ensure a smooth curve with other cells
|
||||
// Start 1/2 cell before and end 1/2 cells after to ensure a smooth curve with other
|
||||
// cells
|
||||
this._tmpCtx.moveTo(xChLeft - this._config.deviceCellWidth / 2, yMid);
|
||||
this._tmpCtx.bezierCurveTo(
|
||||
xChLeft - this._config.deviceCellWidth / 2, yCurlyTop,
|
||||
|
||||
@@ -60,7 +60,8 @@ interface IMeasureResult {
|
||||
height: number;
|
||||
}
|
||||
|
||||
// TODO: For supporting browsers we should also provide a CanvasCharDimensionsProvider that uses ctx.measureText
|
||||
// TODO: For supporting browsers we should also provide a CanvasCharDimensionsProvider that uses
|
||||
// ctx.measureText
|
||||
class DomMeasureStrategy implements IMeasureStrategy {
|
||||
private _result: IMeasureResult = { width: 0, height: 0 };
|
||||
private _measureElement: HTMLElement;
|
||||
|
||||
@@ -35,7 +35,12 @@ describe('ThemeService', () => {
|
||||
describe('constructor', () => {
|
||||
it('should fill all colors with values', () => {
|
||||
for (const key of Object.keys(themeService.colors)) {
|
||||
if (key !== 'ansi' && key !== 'contrastCache' && key !== 'selectionForeground') {
|
||||
if (![
|
||||
'ansi',
|
||||
'contrastCache',
|
||||
'halfContrastCache',
|
||||
'selectionForeground'
|
||||
].includes(key)) {
|
||||
// A #rrggbb or rgba(...)
|
||||
assert.ok((themeService.colors as any)[key].css.length >= 7);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ export class ThemeService extends Disposable implements IThemeService {
|
||||
public serviceBrand: undefined;
|
||||
|
||||
private _colors: IColorSet;
|
||||
private _contrastCache: IColorContrastCache;
|
||||
private _contrastCache: IColorContrastCache = new ColorContrastCache();
|
||||
private _halfContrastCache: IColorContrastCache = new ColorContrastCache();
|
||||
private _restoreColors!: IRestoreColorSet;
|
||||
|
||||
public get colors(): ReadonlyColorSet { return this._colors; }
|
||||
@@ -94,7 +95,6 @@ export class ThemeService extends Disposable implements IThemeService {
|
||||
) {
|
||||
super();
|
||||
|
||||
this._contrastCache = new ColorContrastCache();
|
||||
this._colors = {
|
||||
foreground: DEFAULT_FOREGROUND,
|
||||
background: DEFAULT_BACKGROUND,
|
||||
@@ -106,7 +106,8 @@ export class ThemeService extends Disposable implements IThemeService {
|
||||
selectionInactiveBackgroundTransparent: DEFAULT_SELECTION,
|
||||
selectionInactiveBackgroundOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION),
|
||||
ansi: DEFAULT_ANSI_COLORS.slice(),
|
||||
contrastCache: this._contrastCache
|
||||
contrastCache: this._contrastCache,
|
||||
halfContrastCache: this._halfContrastCache
|
||||
};
|
||||
this._updateRestoreColors();
|
||||
this._setTheme(this._optionsService.rawOptions.theme);
|
||||
@@ -172,6 +173,7 @@ export class ThemeService extends Disposable implements IThemeService {
|
||||
}
|
||||
// Clear our the cache
|
||||
this._contrastCache.clear();
|
||||
this._halfContrastCache.clear();
|
||||
this._updateRestoreColors();
|
||||
this._onChangeColors.fire(this.colors);
|
||||
}
|
||||
|
||||
+2
-1
@@ -106,7 +106,8 @@ export namespace color {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper functions where the source type is "css" (string: '#rgb', '#rgba', '#rrggbb', '#rrggbbaa').
|
||||
* Helper functions where the source type is "css" (string: '#rgb', '#rgba', '#rrggbb',
|
||||
* '#rrggbbaa').
|
||||
*/
|
||||
export namespace css {
|
||||
let $ctx: CanvasRenderingContext2D | undefined;
|
||||
|
||||
+40
-32
@@ -421,11 +421,11 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* - undefined (void):
|
||||
* all handlers were sync, no stack save, continue normally with next chunk
|
||||
* - Promise\<boolean\>:
|
||||
* execution stopped at async handler, stack saved, continue with
|
||||
* same chunk and the promise resolve value as `promiseResult` until the method returns `undefined`
|
||||
* execution stopped at async handler, stack saved, continue with same chunk and the promise
|
||||
* resolve value as `promiseResult` until the method returns `undefined`
|
||||
*
|
||||
* Note: This method should only be called by `Terminal.write` to ensure correct execution order and
|
||||
* proper continuation of async parser handlers.
|
||||
* Note: This method should only be called by `Terminal.write` to ensure correct execution order
|
||||
* and proper continuation of async parser handlers.
|
||||
*/
|
||||
public parse(data: string | Uint8Array, promiseResult?: boolean): void | Promise<boolean> {
|
||||
let result: void | Promise<boolean>;
|
||||
@@ -804,12 +804,13 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
// find last taken cell - last cell can have 3 different states:
|
||||
// - hasContent(true) + hasWidth(1): narrow char - we are done
|
||||
// - hasWidth(0): second part of wide char - we are done
|
||||
// - hasContent(false) + hasWidth(1): empty cell due to early wrapping wide char, go one cell further back
|
||||
// - hasContent(false) + hasWidth(1): empty cell due to early wrapping wide char, go one
|
||||
// cell further back
|
||||
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!;
|
||||
if (line.hasWidth(this._activeBuffer.x) && !line.hasContent(this._activeBuffer.x)) {
|
||||
this._activeBuffer.x--;
|
||||
// We do this only once, since width=1 + hasContent=false currently happens only once before
|
||||
// early wrapping of a wide char.
|
||||
// We do this only once, since width=1 + hasContent=false currently happens only once
|
||||
// before early wrapping of a wide char.
|
||||
// This needs to be fixed once we support graphemes taking more than 2 cells.
|
||||
}
|
||||
}
|
||||
@@ -1164,8 +1165,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to reset cells in a terminal row.
|
||||
* The cell gets replaced with the eraseChar of the terminal and the isWrapped property is set to false.
|
||||
* Helper method to reset cells in a terminal row. The cell gets replaced with the eraseChar of
|
||||
* the terminal and the isWrapped property is set to false.
|
||||
* @param y row index
|
||||
*/
|
||||
private _resetBufferLine(y: number, respectProtect: boolean = false): void {
|
||||
@@ -1362,8 +1363,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Insert Ps (Blank) Character(s) (default = 1) (ICH).
|
||||
*
|
||||
* @vt: #Y CSI ICH "Insert Characters" "CSI Ps @" "Insert `Ps` (blank) characters (default = 1)."
|
||||
* The ICH sequence inserts `Ps` blank characters. The cursor remains at the beginning of the blank characters.
|
||||
* Text between the cursor and right margin moves to the right. Characters moved past the right margin are lost.
|
||||
* The ICH sequence inserts `Ps` blank characters. The cursor remains at the beginning of the
|
||||
* blank characters. Text between the cursor and right margin moves to the right. Characters moved
|
||||
* past the right margin are lost.
|
||||
*
|
||||
*
|
||||
* FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual)
|
||||
@@ -1388,8 +1390,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Delete Ps Character(s) (default = 1) (DCH).
|
||||
*
|
||||
* @vt: #Y CSI DCH "Delete Character" "CSI Ps P" "Delete `Ps` characters (default=1)."
|
||||
* As characters are deleted, the remaining characters between the cursor and right margin move to the left.
|
||||
* Character attributes move with the characters. The terminal adds blank characters at the right margin.
|
||||
* As characters are deleted, the remaining characters between the cursor and right margin move to
|
||||
* the left. Character attributes move with the characters. The terminal adds blank characters at
|
||||
* the right margin.
|
||||
*
|
||||
*
|
||||
* FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual)
|
||||
@@ -1514,9 +1517,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Insert Ps Column(s) (default = 1) (DECIC), VT420 and up.
|
||||
*
|
||||
* @vt: #Y CSI DECIC "Insert Columns" "CSI Ps ' }" "Insert `Ps` columns at cursor position."
|
||||
* DECIC inserts `Ps` times blank columns at the cursor position for all lines with the scroll margins,
|
||||
* moving content to the right. Content at the right margin is lost.
|
||||
* DECIC has no effect outside the scrolling margins.
|
||||
* DECIC inserts `Ps` times blank columns at the cursor position for all lines with the scroll
|
||||
* margins, moving content to the right. Content at the right margin is lost. DECIC has no effect
|
||||
* outside the scrolling margins.
|
||||
*/
|
||||
public insertColumns(params: IParams): boolean {
|
||||
if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {
|
||||
@@ -1597,12 +1600,12 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* - wrap around is respected
|
||||
* - any valid sequence resets the carried forward char
|
||||
*
|
||||
* Note: To get reset on a valid sequence working correctly without much runtime penalty,
|
||||
* the preceding codepoint is stored on the parser in `this.print` and reset during `parser.parse`.
|
||||
* Note: To get reset on a valid sequence working correctly without much runtime penalty, the
|
||||
* preceding codepoint is stored on the parser in `this.print` and reset during `parser.parse`.
|
||||
*
|
||||
* @vt: #Y CSI REP "Repeat Preceding Character" "CSI Ps b" "Repeat preceding character `Ps` times (default=1)."
|
||||
* REP repeats the previous character `Ps` times advancing the cursor, also wrapping if DECAWM is set.
|
||||
* REP has no effect if the sequence does not follow a printable ASCII character
|
||||
* REP repeats the previous character `Ps` times advancing the cursor, also wrapping if DECAWM is
|
||||
* set. REP has no effect if the sequence does not follow a printable ASCII character
|
||||
* (NOOP for any other sequence in between or NON ASCII characters).
|
||||
*/
|
||||
public repeatPrecedingCharacter(params: IParams): boolean {
|
||||
@@ -2463,7 +2466,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* | 5 | Dashed underline. | #Y |
|
||||
* | other | Single underline. Same as `SGR 4 m`. | #Y |
|
||||
*
|
||||
* Extended colors are supported for foreground (Ps=38), background (Ps=48) and underline (Ps=58) as follows:
|
||||
* Extended colors are supported for foreground (Ps=38), background (Ps=48) and underline (Ps=58)
|
||||
* as follows:
|
||||
*
|
||||
* | Ps + 1 | Meaning | Support |
|
||||
* | ------ | ------------------------------------------------------------- | ------- |
|
||||
@@ -2673,8 +2677,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* http://vt100.net/docs/vt220-rm/table4-10.html
|
||||
*
|
||||
* @vt: #Y CSI DECSTR "Soft Terminal Reset" "CSI ! p" "Reset several terminal attributes to initial state."
|
||||
* There are two terminal reset sequences - RIS and DECSTR. While RIS performs almost a full terminal bootstrap,
|
||||
* DECSTR only resets certain attributes. For most needs DECSTR should be sufficient.
|
||||
* There are two terminal reset sequences - RIS and DECSTR. While RIS performs almost a full
|
||||
* terminal bootstrap, DECSTR only resets certain attributes. For most needs DECSTR should be
|
||||
* sufficient.
|
||||
*
|
||||
* The following terminal attributes are reset to default values:
|
||||
* - IRM is reset (dafault = false)
|
||||
@@ -2899,7 +2904,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Icon name is not supported. For Window Title see below.
|
||||
*
|
||||
* @vt: #Y OSC 2 "Set Windows Title" "OSC 2 ; Pt BEL" "Set window title."
|
||||
* xterm.js does not manipulate the title directly, instead exposes changes via the event `Terminal.onTitleChange`.
|
||||
* xterm.js does not manipulate the title directly, instead exposes changes via the event
|
||||
* `Terminal.onTitleChange`.
|
||||
*/
|
||||
public setTitle(data: string): boolean {
|
||||
this._windowTitle = data;
|
||||
@@ -2920,9 +2926,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* OSC 4; <num> ; <text> ST (set ANSI color <num> to <text>)
|
||||
*
|
||||
* @vt: #Y OSC 4 "Set ANSI color" "OSC 4 ; c ; spec BEL" "Change color number `c` to the color specified by `spec`."
|
||||
* `c` is the color index between 0 and 255. The color format of `spec` is derived from `XParseColor` (see OSC 10 for supported formats).
|
||||
* There may be multipe `c ; spec` pairs present in the same instruction.
|
||||
* If `spec` contains `?` the terminal returns a sequence with the currently set color.
|
||||
* `c` is the color index between 0 and 255. The color format of `spec` is derived from
|
||||
* `XParseColor` (see OSC 10 for supported formats). There may be multipe `c ; spec` pairs present
|
||||
* in the same instruction. If `spec` contains `?` the terminal returns a sequence with the
|
||||
* currently set color.
|
||||
*/
|
||||
public setOrReportIndexedColor(data: string): boolean {
|
||||
const event: IColorEvent = [];
|
||||
@@ -2962,9 +2969,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
*
|
||||
* @vt: #Y OSC 8 "Create hyperlink" "OSC 8 ; params ; uri BEL" "Create a hyperlink to `uri` using `params`."
|
||||
* `uri` is a hyperlink starting with `http://`, `https://`, `ftp://`, `file://` or `mailto://`. `params` is an
|
||||
* optional list of key=value assignments, separated by the : character. Example: `id=xyz123:foo=bar:baz=quux`.
|
||||
* Currently only the id key is defined. Cells that share the same ID and URI share hover feedback.
|
||||
* Use `OSC 8 ; ; BEL` to finish the current hyperlink.
|
||||
* optional list of key=value assignments, separated by the : character.
|
||||
* Example: `id=xyz123:foo=bar:baz=quux`.
|
||||
* Currently only the id key is defined. Cells that share the same ID and URI share hover
|
||||
* feedback. Use `OSC 8 ; ; BEL` to finish the current hyperlink.
|
||||
*/
|
||||
public setHyperlink(data: string): boolean {
|
||||
const args = data.split(';');
|
||||
@@ -3351,8 +3359,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html)
|
||||
*
|
||||
* @vt: #P[Limited support, see below.] DCS DECRQSS "Request Selection or Setting" "DCS $ q Pt ST" "Request several terminal settings."
|
||||
* Response is in the form `ESC P 1 $ r Pt ST` for valid requests, where `Pt` contains the corresponding CSI string,
|
||||
* `ESC P 0 ST` for invalid requests.
|
||||
* Response is in the form `ESC P 1 $ r Pt ST` for valid requests, where `Pt` contains the
|
||||
* corresponding CSI string, `ESC P 0 ST` for invalid requests.
|
||||
*
|
||||
* Supported requests and responses:
|
||||
*
|
||||
|
||||
@@ -104,4 +104,18 @@ describe('SortedList', () => {
|
||||
{ key: 10 }
|
||||
]);
|
||||
});
|
||||
describe('values', () => {
|
||||
it('should iterate correctly when list items change during iteration', () => {
|
||||
list.insert(1);
|
||||
list.insert(2);
|
||||
list.insert(3);
|
||||
list.insert(4);
|
||||
const visited: number[] = [];
|
||||
for (const item of list.values()) {
|
||||
visited.push(item);
|
||||
list.delete(item);
|
||||
}
|
||||
deepStrictEqual(visited, [1, 2, 3, 4]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -89,7 +89,8 @@ export class SortedList<T> {
|
||||
}
|
||||
|
||||
public values(): IterableIterator<T> {
|
||||
return this._array.values();
|
||||
// Duplicate the array to avoid issues when _array changes while iterating
|
||||
return [...this._array].values();
|
||||
}
|
||||
|
||||
private _search(key: number): number {
|
||||
|
||||
Vendored
+2
@@ -38,6 +38,8 @@ export interface ITerminalOptions extends IPublicTerminalOptions {
|
||||
|
||||
export type CursorStyle = 'block' | 'underline' | 'bar';
|
||||
|
||||
export type CursorInactiveStyle = 'outline' | 'block' | 'bar' | 'underline' | 'none';
|
||||
|
||||
export type XtermListener = (...args: any[]) => void;
|
||||
|
||||
/**
|
||||
|
||||
@@ -270,8 +270,8 @@ export class Buffer implements IBuffer {
|
||||
private _batchedMemoryCleanup(): boolean {
|
||||
let normalRun = true;
|
||||
if (this._memoryCleanupPosition >= this.lines.length) {
|
||||
// cleanup made it once through all lines, thus rescan in loop below to also catch shifted lines,
|
||||
// which should finish rather quick if there are no more cleanups pending
|
||||
// cleanup made it once through all lines, thus rescan in loop below to also catch shifted
|
||||
// lines, which should finish rather quick if there are no more cleanups pending
|
||||
this._memoryCleanupPosition = 0;
|
||||
normalRun = false;
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ export class Marker implements IMarker {
|
||||
private static _nextId = 1;
|
||||
|
||||
public isDisposed: boolean = false;
|
||||
private _disposables: IDisposable[] = [];
|
||||
private readonly _disposables: IDisposable[] = [];
|
||||
|
||||
private _id: number = Marker._nextId++;
|
||||
private readonly _id: number = Marker._nextId++;
|
||||
public get id(): number { return this._id; }
|
||||
|
||||
private readonly _onDispose = this.register(new EventEmitter<void>());
|
||||
|
||||
@@ -380,7 +380,8 @@ export function evaluateKeyboardEvent(
|
||||
result.type = KeyboardResultType.SELECT_ALL;
|
||||
}
|
||||
} else if (ev.key && !ev.ctrlKey && !ev.altKey && !ev.metaKey && ev.keyCode >= 48 && ev.key.length === 1) {
|
||||
// Include only keys that that result in a _single_ character; don't include num lock, volume up, etc.
|
||||
// Include only keys that that result in a _single_ character; don't include num lock,
|
||||
// volume up, etc.
|
||||
result.key = ev.key;
|
||||
} else if (ev.key && ev.ctrlKey) {
|
||||
if (ev.key === '_') { // ^_
|
||||
|
||||
@@ -28,8 +28,8 @@ export function utf32ToString(data: Uint32Array, start: number = 0, end: number
|
||||
for (let i = start; i < end; ++i) {
|
||||
let codepoint = data[i];
|
||||
if (codepoint > 0xFFFF) {
|
||||
// JS strings are encoded as UTF16, thus a non BMP codepoint gets converted into a surrogate pair
|
||||
// conversion rules:
|
||||
// JS strings are encoded as UTF16, thus a non BMP codepoint gets converted into a surrogate
|
||||
// pair conversion rules:
|
||||
// - subtract 0x10000 from code point, leaving a 20 bit number
|
||||
// - add high 10 bits to 0xD800 --> first surrogate
|
||||
// - add low 10 bits to 0xDC00 --> second surrogate
|
||||
|
||||
@@ -181,9 +181,10 @@ export class WriteBuffer extends Disposable {
|
||||
|
||||
/**
|
||||
* If a promise takes long to resolve, we should schedule continuation behind setTimeout.
|
||||
* This might already be too late, if our .then enters really late (executor + prev thens took very long).
|
||||
* This cannot be solved here for the handler itself (it is the handlers responsibility to slice hard work),
|
||||
* but we can at least schedule a screen update as we gain control.
|
||||
* This might already be too late, if our .then enters really late (executor + prev thens
|
||||
* took very long). This cannot be solved here for the handler itself (it is the handlers
|
||||
* responsibility to slice hard work), but we can at least schedule a screen update as we
|
||||
* gain control.
|
||||
*/
|
||||
const continuation: (r: boolean) => void = (r: boolean) => Date.now() - startTime >= WRITE_TIMEOUT_MS
|
||||
? setTimeout(() => this._innerWrite(0, r))
|
||||
|
||||
@@ -532,9 +532,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
} else {
|
||||
if (promiseResult === undefined || this._parseStack.state === ParserStackType.FAIL) {
|
||||
/**
|
||||
* Reject further parsing on improper continuation after pausing.
|
||||
* This is a really bad condition with screwed up execution order and prolly messed up
|
||||
* terminal state, therefore we exit hard with an exception and reject any further parsing.
|
||||
* Reject further parsing on improper continuation after pausing. This is a really bad
|
||||
* condition with screwed up execution order and prolly messed up terminal state,
|
||||
* therefore we exit hard with an exception and reject any further parsing.
|
||||
*
|
||||
* Note: With `Terminal.write` usage this exception should never occur, as the top level
|
||||
* calls are guaranteed to handle async conditions properly. If you ever encounter this
|
||||
@@ -542,9 +542,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
* `InputHandler.parse` or `EscapeSequenceParser.parse` synchronously without waiting for
|
||||
* continuation of a running async handler.
|
||||
*
|
||||
* It is possible to get rid of this error by calling `reset`. But dont rely on that,
|
||||
* as the pending async handler still might mess up the terminal later. Instead fix the faulty
|
||||
* async handling, so this error will not be thrown anymore.
|
||||
* It is possible to get rid of this error by calling `reset`. But dont rely on that, as
|
||||
* the pending async handler still might mess up the terminal later. Instead fix the
|
||||
* faulty async handling, so this error will not be thrown anymore.
|
||||
*/
|
||||
this._parseStack.state = ParserStackType.FAIL;
|
||||
throw new Error('improper continuation due to previous async handler, giving up parsing');
|
||||
|
||||
@@ -43,7 +43,8 @@ export class BufferService extends Disposable implements IBufferService {
|
||||
this.cols = cols;
|
||||
this.rows = rows;
|
||||
this.buffers.resize(cols, rows);
|
||||
// TODO: This doesn't fire when scrollback changes - add a resize event to BufferSet and forward event
|
||||
// TODO: This doesn't fire when scrollback changes - add a resize event to BufferSet and forward
|
||||
// event
|
||||
this._onResize.fire({ cols, rows });
|
||||
}
|
||||
|
||||
|
||||
@@ -35,12 +35,7 @@ export class DecorationService extends Disposable implements IDecorationService
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.register(toDisposable(() => {
|
||||
for (const d of this._decorations.values()) {
|
||||
this._onDecorationRemoved.fire(d);
|
||||
}
|
||||
this.reset();
|
||||
}));
|
||||
this.register(toDisposable(() => this.reset()));
|
||||
}
|
||||
|
||||
public registerDecoration(options: IDecorationOptions): IDecoration | undefined {
|
||||
@@ -92,13 +87,6 @@ export class DecorationService extends Disposable implements IDecorationService
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
for (const d of this._decorations.values()) {
|
||||
this._onDecorationRemoved.fire(d);
|
||||
}
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
||||
class Decoration extends Disposable implements IInternalDecoration {
|
||||
|
||||
@@ -15,6 +15,7 @@ export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
|
||||
cursorBlink: false,
|
||||
cursorStyle: 'block',
|
||||
cursorWidth: 1,
|
||||
cursorInactiveStyle: 'outline',
|
||||
customGlyphs: true,
|
||||
drawBoldTextInBrightColors: true,
|
||||
fastScrollModifier: 'alt',
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { IEvent, IEventEmitter } from 'common/EventEmitter';
|
||||
import { IBuffer, IBufferSet } from 'common/buffer/Types';
|
||||
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColor, CursorStyle, IOscLinkData } from 'common/Types';
|
||||
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColor, CursorStyle, CursorInactiveStyle, IOscLinkData } from 'common/Types';
|
||||
import { createDecorator } from 'common/services/ServiceRegistry';
|
||||
import { IDecorationOptions, IDecoration, ILinkHandler, IWindowsPty, ILogger } from 'xterm';
|
||||
|
||||
@@ -212,6 +212,7 @@ export interface ITerminalOptions {
|
||||
cursorBlink?: boolean;
|
||||
cursorStyle?: CursorStyle;
|
||||
cursorWidth?: number;
|
||||
cursorInactiveStyle?: CursorInactiveStyle;
|
||||
customGlyphs?: boolean;
|
||||
disableStdin?: boolean;
|
||||
drawBoldTextInBrightColors?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user