Merge branch 'master' into clusters

This commit is contained in:
Per Bothner
2023-08-14 12:22:55 -07:00
committed by GitHub
47 changed files with 721 additions and 364 deletions
+12 -1
View File
@@ -163,6 +163,16 @@
"jsdoc/check-param-names": 1,
"jsdoc/no-multi-asterisks": 1,
"keyword-spacing": "warn",
"max-len": [
"warn",
{
"code": 1000, // Don't enforce for code
"comments": 100,
"ignoreTrailingComments": true,
"ignoreUrls": true,
"ignorePattern": "^ *((?<vt_comment>(//|\\*) @vt)|(?<table_comment>\\* \\| )|(?<commented_code>// ))"
}
],
"new-parens": "warn",
"no-duplicate-imports": "warn",
"no-else-return": [
@@ -229,7 +239,8 @@
{
"files": ["**/*.test.ts"],
"rules": {
"object-curly-spacing": "off"
"object-curly-spacing": "off",
"max-len": "off"
}
}
]
+104
View File
@@ -0,0 +1,104 @@
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint",
"jsdoc"
],
"rules": {
"no-extra-semi": "error",
"@typescript-eslint/array-type": [
"warn",
{
"default": "array",
"readonly": "generic"
}
],
"@typescript-eslint/explicit-function-return-type": [
"warn",
{
"allowExpressions": true
}
],
"@typescript-eslint/indent": [
"warn",
2
],
"@typescript-eslint/member-delimiter-style": [
"warn",
{
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"singleline": {
"delimiter": "comma",
"requireLast": false
}
}
],
"@typescript-eslint/naming-convention": [
"warn",
{ "selector": "typeLike", "format": ["PascalCase"] },
{ "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] }
],
"@typescript-eslint/prefer-namespace-keyword": "warn",
"@typescript-eslint/type-annotation-spacing": "warn",
"@typescript-eslint/quotes": [
"warn",
"single",
{ "allowTemplateLiterals": true }
],
"@typescript-eslint/semi": [
"warn",
"always"
],
"comma-dangle": [
"warn",
{
"objects": "never",
"arrays": "never",
"functions": "never"
}
],
"curly": [
"warn",
"multi-line"
],
"eol-last": "warn",
"eqeqeq": [
"warn",
"always"
],
"jsdoc/check-alignment": 1,
"jsdoc/check-param-names": 1,
"keyword-spacing": "warn",
"max-len": [
"warn",
{
"code": 1000, // Don't enforce for code
"comments": 80,
"ignoreUrls": true,
"ignorePattern": "^ *(?<ps_description>\\* Ps=)"
}
],
"no-irregular-whitespace": "warn",
"no-trailing-spaces": "warn",
"object-curly-spacing": [
"warn",
"always"
],
"spaced-comment": [
"warn",
"always",
{
"markers": ["/"],
"exceptions": ["-"]
}
]
}
}
+1
View File
@@ -0,0 +1 @@
16
@@ -58,7 +58,8 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._cursorRenderers = {
'bar': this._renderBarCursor.bind(this),
'block': this._renderBlockCursor.bind(this),
'underline': this._renderUnderlineCursor.bind(this)
'underline': this._renderUnderlineCursor.bind(this),
'outline': this._renderOutlineCursor.bind(this)
};
this.register(optionsService.onOptionChange(() => this._handleOptionsChanged()));
this._handleOptionsChanged();
@@ -150,7 +151,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._ctx.save();
this._ctx.fillStyle = this._themeService.colors.cursor.css;
const cursorStyle = this._optionsService.rawOptions.cursorStyle;
this._renderBlurCursor(cursorX, viewportRelativeCursorY, this._cell);
const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
if (cursorInactiveStyle && cursorInactiveStyle !== 'none') {
this._cursorRenderers[cursorInactiveStyle](cursorX, viewportRelativeCursorY, this._cell);
}
this._ctx.restore();
this._state.x = cursorX;
this._state.y = viewportRelativeCursorY;
@@ -231,7 +235,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._ctx.restore();
}
private _renderBlurCursor(x: number, y: number, cell: ICellData): void {
private _renderOutlineCursor(x: number, y: number, cell: ICellData): void {
this._ctx.save();
this._ctx.strokeStyle = this._themeService.colors.cursor.css;
this._strokeRectAtCell(x, y, cell.getWidth(), 1);
@@ -23,7 +23,8 @@ export interface IHeaderFields {
height?: string;
// Optional, defaults to 1 respecting aspect ratio (width takes precedence).
preserveAspectRatio?: number;
// Optional, defaults to 0. If set to 1, the file will be displayed inline, else downloaded (download not supported).
// Optional, defaults to 0. If set to 1, the file will be displayed inline, else downloaded
// (download not supported).
inline?: number;
}
+13 -16
View File
@@ -68,8 +68,6 @@ export class SearchAddon extends Disposable implements ITerminalAddon {
private _highlightDecorations: IHighlight[] = [];
private _selectedDecoration: IHighlight | undefined;
private _highlightLimit: number;
private _onDataDisposable: IDisposable | undefined;
private _onResizeDisposable: IDisposable | undefined;
private _lastSearchOptions: ISearchOptions | undefined;
private _highlightTimeout: number | undefined;
/**
@@ -93,13 +91,9 @@ export class SearchAddon extends Disposable implements ITerminalAddon {
public activate(terminal: Terminal): void {
this._terminal = terminal;
this._onDataDisposable = this.register(this._terminal.onWriteParsed(() => this._updateMatches()));
this._onResizeDisposable = this.register(this._terminal.onResize(() => this._updateMatches()));
this.register(toDisposable(() => {
this.clearDecorations();
this._onDataDisposable?.dispose();
this._onResizeDisposable?.dispose();
}));
this.register(this._terminal.onWriteParsed(() => this._updateMatches()));
this.register(this._terminal.onResize(() => this._updateMatches()));
this.register(toDisposable(() => this.clearDecorations()));
}
private _updateMatches(): void {
@@ -440,7 +434,8 @@ export class SearchAddon extends Disposable implements ITerminalAddon {
}
/**
* A found substring is a whole word if it doesn't have an alphanumeric character directly adjacent to it.
* A found substring is a whole word if it doesn't have an alphanumeric character directly
* adjacent to it.
* @param searchIndex starting indext of the potential whole word substring
* @param line entire string in which the potential whole word was found
* @param term the substring that starts at searchIndex
@@ -451,14 +446,15 @@ export class SearchAddon extends Disposable implements ITerminalAddon {
}
/**
* Searches a line for a search term. Takes the provided terminal line and searches the text line, which may contain
* subsequent terminal lines if the text is wrapped. If the provided line number is part of a wrapped text line that
* started on an earlier line then it is skipped since it will be properly searched when the terminal line that the
* text starts on is searched.
* Searches a line for a search term. Takes the provided terminal line and searches the text line,
* which may contain subsequent terminal lines if the text is wrapped. If the provided line number
* is part of a wrapped text line that started on an earlier line then it is skipped since it will
* be properly searched when the terminal line that the text starts on is searched.
* @param term The search term.
* @param searchPosition The position to start the search.
* @param searchOptions Search options.
* @param isReverseSearch Whether the search should start from the right side of the terminal and search to the left.
* @param isReverseSearch Whether the search should start from the right side of the terminal and
* search to the left.
* @returns The search result if it was found.
*/
protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined {
@@ -526,7 +522,8 @@ export class SearchAddon extends Disposable implements ITerminalAddon {
return;
}
// Adjust the row number and search index if needed since a "line" of text can span multiple rows
// Adjust the row number and search index if needed since a "line" of text can span multiple
// rows
let startRowOffset = 0;
while (startRowOffset < offsets.length - 1 && resultIndex >= offsets[startRowOffset + 1]) {
startRowOffset++;
@@ -131,7 +131,8 @@ class StringSerializeHandler extends BaseSerializeHandler {
private _thisRowLastSecondChar: IBufferCell = this._buffer.getNullCell();
private _nextRowFirstChar: IBufferCell = this._buffer.getNullCell();
protected _rowEnd(row: number, isLastRow: boolean): void {
// if there is colorful empty cell at line end, whe must pad it back, or the the color block will missing
// if there is colorful empty cell at line end, whe must pad it back, or the the color block
// will missing
if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) {
// use clear right to set background.
this._currentRow += `\u001b[${this._nullCellCount}X`;
@@ -292,7 +293,8 @@ class StringSerializeHandler extends BaseSerializeHandler {
const sgrSeq = this._diffStyle(cell, this._cursorStyle);
// the empty cell style is only assumed to be changed when background changed, because foreground is always 0.
// the empty cell style is only assumed to be changed when background changed, because
// foreground is always 0.
const styleChanged = isEmptyCell ? !equalBg(this._cursorStyle, cell) : sgrSeq.length > 0;
/**
@@ -255,7 +255,7 @@ export class RectangleRenderer extends Disposable {
let offset: number;
let rectangleCount = 0;
if (cursor.style === 'bar' || cursor.style === 'blur') {
if (cursor.style === 'bar' || cursor.style === 'outline') {
// Left edge
offset = rectangleCount++ * INDICES_PER_RECTANGLE;
this._addRectangleFloat(
@@ -268,7 +268,7 @@ export class RectangleRenderer extends Disposable {
this._cursorFloat
);
}
if (cursor.style === 'underline' || cursor.style === 'blur') {
if (cursor.style === 'underline' || cursor.style === 'outline') {
// Bottom edge
offset = rectangleCount++ * INDICES_PER_RECTANGLE;
this._addRectangleFloat(
@@ -281,7 +281,7 @@ export class RectangleRenderer extends Disposable {
this._cursorFloat
);
}
if (cursor.style === 'blur') {
if (cursor.style === 'outline') {
// Top edge
offset = rectangleCount++ * INDICES_PER_RECTANGLE;
this._addRectangleFloat(
+3 -1
View File
@@ -16,11 +16,13 @@ export interface ICursorRenderModel {
x: number;
y: number;
width: number;
style: string;
style: CursorStyle;
cursorWidth: number;
dpr: number;
}
export type CursorStyle = 'outline' | 'block' | 'bar' | 'underline' | 'none';
export interface IWebGL2RenderingContext extends WebGLRenderingContext {
vertexAttribDivisor(index: number, divisor: number): void;
createVertexArray(): IWebGLVertexArrayObject;
@@ -461,15 +461,17 @@ export class WebglRenderer extends Disposable implements IRenderer {
y: this._terminal.buffer.active.cursorY,
width: cell.getWidth(),
style: this._coreBrowserService.isFocused ?
(terminal.options.cursorStyle || 'block') : 'blur',
(terminal.options.cursorStyle || 'block') : terminal.options.cursorInactiveStyle,
cursorWidth: terminal.options.cursorWidth,
dpr: this._devicePixelRatio
};
lastCursorX = cursorX + cell.getWidth() - 1;
}
if (x >= cursorX && x <= lastCursorX &&
this._coreBrowserService.isFocused &&
(terminal.options.cursorStyle || 'block') === 'block') {
((this._coreBrowserService.isFocused &&
(terminal.options.cursorStyle || 'block') === 'block') ||
(this._coreBrowserService.isFocused === false &&
terminal.options.cursorInactiveStyle === 'block'))) {
this._cellColorResolver.result.fg =
Attributes.CM_RGB | (this._themeService.colors.cursorAccent.rgba >> 8 & Attributes.RGB_MASK);
this._cellColorResolver.result.bg =
@@ -789,7 +789,7 @@ describe('WebGL Renderer Integration Tests', async () => {
await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]);
await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]);
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
// Setting and check for minimum contrast values, note that these are note
// Setting and check for minimum contrast values, note that these are not
// exact to the contrast ratio, if the increase luminance algorithm
// changes then these will probably fail
await page.evaluate(`window.term.options.minimumContrastRatio = 10;`);
@@ -858,7 +858,7 @@ describe('WebGL Renderer Integration Tests', async () => {
await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]);
await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]);
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
// Setting and check for minimum contrast values, note that these are note
// Setting and check for minimum contrast values, note that these are not
// exact to the contrast ratio, if the increase luminance algorithm
// changes then these will probably fail
await page.evaluate(`window.term.options.minimumContrastRatio = 10;`);
@@ -879,6 +879,75 @@ describe('WebGL Renderer Integration Tests', async () => {
await pollFor(page, () => getCellColor(7, 2), [13, 67, 67, 255]);
await pollFor(page, () => getCellColor(8, 2), [64, 64, 64, 255]);
});
itWebgl('should enforce half the contrast for dim cells', async () => {
const theme: ITheme = {
background: '#ffffff',
black: '#2e3436',
red: '#cc0000',
green: '#4e9a06',
yellow: '#c4a000',
blue: '#3465a4',
magenta: '#75507b',
cyan: '#06989a',
white: '#d3d7cf',
brightBlack: '#555753',
brightRed: '#ef2929',
brightGreen: '#8ae234',
brightYellow: '#fce94f',
brightBlue: '#729fcf',
brightMagenta: '#ad7fa8',
brightCyan: '#34e2e2',
brightWhite: '#eeeeec'
};
await page.evaluate(`
window.term.options.theme = ${JSON.stringify(theme)};
window.term.options.minimumContrastRatio = 1;
`);
// Block characters ignore block elements so a different char is used here
await writeSync(page,
'\\x1b[2m' +
`\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■\\r\\n` +
`\\x1b[90m■\\x1b[91m■\\x1b[92m■\\x1b[93m■\\x1b[94m■\\x1b[95m■\\x1b[96m■\\x1b[97m■`
);
// Validate before minimumContrastRatio is applied
await pollFor(page, () => getCellColor(1, 1), [Math.floor((255 + 0x2e) / 2), Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x36) / 2), 255]);
await pollFor(page, () => getCellColor(2, 1), [Math.floor((255 + 0xcc) / 2), Math.floor((255 + 0x00) / 2), Math.floor((255 + 0x00) / 2), 255]);
await pollFor(page, () => getCellColor(3, 1), [Math.floor((255 + 0x4e) / 2), Math.floor((255 + 0x9a) / 2), Math.floor((255 + 0x06) / 2), 255]);
await pollFor(page, () => getCellColor(4, 1), [Math.floor((255 + 0xc4) / 2), Math.floor((255 + 0xa0) / 2), Math.floor((255 + 0x00) / 2), 255]);
await pollFor(page, () => getCellColor(5, 1), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x65) / 2), Math.floor((255 + 0xa4) / 2), 255]);
await pollFor(page, () => getCellColor(6, 1), [Math.floor((255 + 0x75) / 2), Math.floor((255 + 0x50) / 2), Math.floor((255 + 0x7b) / 2), 255]);
await pollFor(page, () => getCellColor(7, 1), [Math.floor((255 + 0x06) / 2), Math.floor((255 + 0x98) / 2), Math.floor((255 + 0x9a) / 2), 255]);
await pollFor(page, () => getCellColor(8, 1), [Math.floor((255 + 0xd3) / 2), Math.floor((255 + 0xd7) / 2), Math.floor((255 + 0xcf) / 2), 255]);
await pollFor(page, () => getCellColor(1, 2), [Math.floor((255 + 0x55) / 2), Math.floor((255 + 0x57) / 2), Math.floor((255 + 0x53) / 2), 255]);
await pollFor(page, () => getCellColor(2, 2), [Math.floor((255 + 0xef) / 2), Math.floor((255 + 0x29) / 2), Math.floor((255 + 0x29) / 2), 255]);
await pollFor(page, () => getCellColor(3, 2), [Math.floor((255 + 0x8a) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0x34) / 2), 255]);
await pollFor(page, () => getCellColor(4, 2), [Math.floor((255 + 0xfc) / 2), Math.floor((255 + 0xe9) / 2), Math.floor((255 + 0x4f) / 2), 255]);
await pollFor(page, () => getCellColor(5, 2), [Math.floor((255 + 0x72) / 2), Math.floor((255 + 0x9f) / 2), Math.floor((255 + 0xcf) / 2), 255]);
await pollFor(page, () => getCellColor(6, 2), [Math.floor((255 + 0xad) / 2), Math.floor((255 + 0x7f) / 2), Math.floor((255 + 0xa8) / 2), 255]);
await pollFor(page, () => getCellColor(7, 2), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0xe2) / 2), 255]);
await pollFor(page, () => getCellColor(8, 2), [Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xec) / 2), 255]);
// Setting and check for minimum contrast values, note that these are not
// exact to the contrast ratio, if the increase luminance algorithm
// changes then these will probably fail
await page.evaluate(`window.term.options.minimumContrastRatio = 10;`);
await pollFor(page, () => getCellColor(1, 1), [150, 153, 154, 255]);
await pollFor(page, () => getCellColor(2, 1), [229, 127, 127, 255]);
await pollFor(page, () => getCellColor(3, 1), [63, 124, 4, 255]);
await pollFor(page, () => getCellColor(4, 1), [127, 104, 0, 255]);
await pollFor(page, () => getCellColor(5, 1), [153, 178, 209, 255]);
await pollFor(page, () => getCellColor(6, 1), [186, 167, 189, 255]);
await pollFor(page, () => getCellColor(7, 1), [4, 122, 124, 255]);
await pollFor(page, () => getCellColor(8, 1), [110, 112, 108, 255]);
await pollFor(page, () => getCellColor(1, 2), [170, 171, 169, 255]);
await pollFor(page, () => getCellColor(2, 2), [215, 36, 36, 255]);
await pollFor(page, () => getCellColor(3, 2), [72, 117, 25, 255]);
await pollFor(page, () => getCellColor(4, 2), [117, 109, 36, 255]);
await pollFor(page, () => getCellColor(5, 2), [72, 103, 135, 255]);
await pollFor(page, () => getCellColor(6, 2), [125, 91, 121, 255]);
await pollFor(page, () => getCellColor(7, 2), [25, 117, 117, 255]);
await pollFor(page, () => getCellColor(8, 2), [111, 111, 110, 255]);
});
});
describe('selectionBackground', async () => {
+9 -3
View File
@@ -33,7 +33,9 @@ jobs:
exit $EXIT_CODE
displayName: 'Unit tests'
- script: yarn lint
displayName: 'Lint'
displayName: 'Lint code'
- script: yarn lint-api
displayName: 'Lint API'
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: Cobertura
@@ -58,7 +60,9 @@ jobs:
- script: yarn test-unit --forbid-only
displayName: 'Unit tests'
- script: yarn lint
displayName: 'Lint'
displayName: 'Lint code'
- script: yarn lint-api
displayName: 'Lint API'
- job: Windows
pool:
@@ -78,7 +82,9 @@ jobs:
- script: yarn test-unit --forbid-only
displayName: 'Unit tests'
- script: yarn lint
displayName: 'Lint'
displayName: 'Lint code'
- script: yarn lint-api
displayName: 'Lint API'
- job: Linux_IntegrationTests
pool:
+1
View File
@@ -430,6 +430,7 @@ function initOptions(term: TerminalType): void {
];
const stringOptions = {
cursorStyle: ['block', 'underline', 'bar'],
cursorInactiveStyle: ['outline', 'block', 'bar', 'underline', 'none'],
fastScrollModifier: ['none', 'alt', 'ctrl', 'shift'],
fontFamily: null,
fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
+3 -1
View File
@@ -30,6 +30,7 @@
"start": "node demo/start",
"start-debug": "node --inspect-brk demo/start",
"lint": "eslint -c .eslintrc.json --max-warnings 0 --ext .ts src/ addons/",
"lint-api": "eslint --no-eslintrc -c .eslintrc.json.typings --max-warnings 0 --no-ignore --ext .d.ts typings/",
"test": "npm run test-unit",
"posttest": "npm run lint",
"test-api": "npm run test-api-chromium",
@@ -43,9 +44,10 @@
"prepare": "npm run setup",
"setup": "npm run build",
"presetup": "node ./bin/install-addons.js",
"postsetup": "cd addons/xterm-addon-image && npm run inwasm -- -S",
"postsetup": "npm run inwasm",
"prepublishOnly": "npm run package",
"watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput",
"inwasm": "cd addons/xterm-addon-image && npm run inwasm -- -S",
"benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json",
"benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/test/benchmark/*benchmark.js",
"benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/test/benchmark/*benchmark.js",
+2 -1
View File
@@ -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();
+2 -1
View File
@@ -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);
}
}
+3 -1
View File
@@ -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;
}
+3
View File
@@ -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']> };
+2 -2
View File
@@ -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';
+11 -9
View File
@@ -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,
+2 -2
View File
@@ -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';
+2 -1
View File
@@ -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
*/
+29 -26
View File
@@ -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,
+2 -1
View File
@@ -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;
+6 -1
View File
@@ -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);
}
+5 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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:
*
+14
View File
@@ -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]);
});
});
});
+2 -1
View File
@@ -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 {
+2
View File
@@ -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;
/**
+2 -2
View File
@@ -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;
}
+2 -2
View File
@@ -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>());
+2 -1
View File
@@ -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 === '_') { // ^_
+2 -2
View File
@@ -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
+4 -3
View File
@@ -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))
+6 -6
View File
@@ -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');
+2 -1
View File
@@ -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 });
}
+1 -13
View File
@@ -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 {
+1
View File
@@ -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',
+2 -1
View File
@@ -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;
+56 -38
View File
@@ -19,7 +19,8 @@ declare module 'xterm-headless' {
export interface ITerminalOptions {
/**
* Whether to allow the use of proposed API. When false, any usage of APIs
* marked as experimental/proposed will throw an error. The default is false.
* marked as experimental/proposed will throw an error. The default is
* false.
*/
allowProposedApi?: boolean;
@@ -63,13 +64,13 @@ declare module 'xterm-headless' {
cursorWidth?: number;
/**
* Whether to draw custom glyphs for block element and box drawing characters instead of using
* the font. This should typically result in better rendering with continuous lines, even when
* line height and letter spacing is used. Note that this doesn't work with the DOM renderer
* which renders all characters using the font. The default is true.
* Whether to draw custom glyphs for block element and box drawing
* characters instead of using the font. This should typically result in
* better rendering with continuous lines, even when line height and letter
* spacing is used. Note that this doesn't work with the DOM renderer which
* renders all characters using the font. The default is true.
*/
customGlyphs?: boolean;
/**
* Whether input should be disabled.
*/
@@ -217,9 +218,9 @@ declare module 'xterm-headless' {
windowsPty?: IWindowsPty;
/**
* A string containing all characters that are considered word separated by the
* double click to select work logic.
*/
* A string containing all characters that are considered word separated by
* the double click to select work logic.
*/
wordSeparator?: string;
/**
@@ -314,23 +315,23 @@ declare module 'xterm-headless' {
*/
export interface ILogger {
/**
* Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to
* debug.
* Log a debug message, this will only be called if
* {@link ITerminalOptions.logLevel} is set to debug.
*/
debug(message: string, ...args: any[]): void;
/**
* Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to
* info or below.
* Log a debug message, this will only be called if
* {@link ITerminalOptions.logLevel} is set to info or below.
*/
info(message: string, ...args: any[]): void;
/**
* Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to
* warn or below.
* Log a debug message, this will only be called if
* {@link ITerminalOptions.logLevel} is set to warn or below.
*/
warn(message: string, ...args: any[]): void;
/**
* Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to
* error or below.
* Log a debug message, this will only be called if
* {@link ITerminalOptions.logLevel} is set to error or below.
*/
error(message: string | Error, ...args: any[]): void;
}
@@ -355,29 +356,32 @@ declare module 'xterm-headless' {
* is trimmed and lines are added or removed. This is a single line that may
* be part of a larger wrapped line.
*/
export interface IMarker extends IDisposable {
export interface IMarker extends IDisposableWithEvent {
/**
* A unique identifier for this marker.
*/
readonly id: number;
/**
* Whether this marker is disposed.
*/
readonly isDisposed: boolean;
/**
* The actual line index in the buffer at this point in time. This is set to
* -1 if the marker has been disposed.
*/
readonly line: number;
}
/**
* Represents a disposable that tracks is disposed state.
*/
export interface IDisposableWithEvent extends IDisposable {
/**
* Event listener to get notified when the marker gets disposed. Automatic disposal
* might happen for a marker, that got invalidated by scrolling out or removal of
* a line from the buffer.
* Event listener to get notified when this gets disposed.
*/
onDispose: IEvent<void>;
/**
* Whether this is disposed.
*/
readonly isDisposed: boolean;
}
/**
@@ -397,7 +401,8 @@ declare module 'xterm-headless' {
}
/**
* Enable various window manipulation and report features (CSI Ps ; Ps ; Ps t).
* Enable various window manipulation and report features
* (`CSI Ps ; Ps ; Ps t`).
*
* Most settings have no default implementation, as they heavily rely on
* the embedding environment.
@@ -417,10 +422,10 @@ declare module 'xterm-headless' {
*
* Note on security:
* Most features are meant to deal with some information of the host machine
* where the terminal runs on. This is seen as a security risk possibly leaking
* sensitive data of the host to the program in the terminal. Therefore all options
* (even those without a default implementation) are guarded by the boolean flag
* and disabled by default.
* where the terminal runs on. This is seen as a security risk possibly
* leaking sensitive data of the host to the program in the terminal.
* Therefore all options (even those without a default implementation) are
* guarded by the boolean flag and disabled by default.
*/
export interface IWindowOptions {
/**
@@ -601,23 +606,36 @@ declare module 'xterm-headless' {
readonly modes: IModes;
/**
* Gets or sets the terminal options. This supports setting multiple options.
* Gets or sets the terminal options. This supports setting multiple
* options.
*
* @example Get a single option
* ```typescript
* ```ts
* console.log(terminal.options.fontSize);
* ```
*
* @example Set a single option
* ```typescript
* @example Set a single option:
* ```ts
* terminal.options.fontSize = 12;
* ```
* Note that for options that are object, a new object must be used in order
* to take effect as a reference comparison will be done:
* ```ts
* const newValue = terminal.options.theme;
* newValue.background = '#000000';
*
* // This won't work
* terminal.options.theme = newValue;
*
* // This will work
* terminal.options.theme = { ...newValue };
* ```
*
* @example Set multiple options
* ```typescript
* ```ts
* terminal.options = {
* fontSize: 12,
* fontFamily: 'Courier New',
* fontFamily: 'Courier New'
* };
* ```
*/
@@ -1283,6 +1301,6 @@ declare module 'xterm-headless' {
/**
* Auto-Wrap Mode (DECAWM): `CSI ? 7 h`
*/
readonly wraparoundMode: boolean
readonly wraparoundMode: boolean;
}
}
+126 -108
View File
@@ -26,7 +26,8 @@ declare module 'xterm' {
export interface ITerminalOptions {
/**
* Whether to allow the use of proposed API. When false, any usage of APIs
* marked as experimental/proposed will throw an error. The default is false.
* marked as experimental/proposed will throw an error. The default is
* false.
*/
allowProposedApi?: boolean;
@@ -60,7 +61,7 @@ declare module 'xterm' {
cursorBlink?: boolean;
/**
* The style of the cursor.
* The style of the cursor when the terminal is focused.
*/
cursorStyle?: 'block' | 'underline' | 'bar';
@@ -70,10 +71,16 @@ declare module 'xterm' {
cursorWidth?: number;
/**
* Whether to draw custom glyphs for block element and box drawing characters instead of using
* the font. This should typically result in better rendering with continuous lines, even when
* line height and letter spacing is used. Note that this doesn't work with the DOM renderer
* which renders all characters using the font. The default is true.
* The style of the cursor when the terminal is not focused.
*/
cursorInactiveStyle?: 'outline' | 'block' | 'bar' | 'underline' | 'none';
/**
* Whether to draw custom glyphs for block element and box drawing
* characters instead of using the font. This should typically result in
* better rendering with continuous lines, even when line height and letter
* spacing is used. Note that this doesn't work with the DOM renderer which
* renders all characters using the font. The default is true.
*/
customGlyphs?: boolean;
@@ -275,9 +282,9 @@ declare module 'xterm' {
windowsPty?: IWindowsPty;
/**
* A string containing all characters that are considered word separated by the
* double click to select work logic.
*/
* A string containing all characters that are considered word separated by
* the double click to select work logic.
*/
wordSeparator?: string;
/**
@@ -325,7 +332,10 @@ declare module 'xterm' {
selectionBackground?: string;
/** The selection foreground color */
selectionForeground?: string;
/** The selection background color when the terminal does not have focus (can be transparent) */
/**
* The selection background color when the terminal does not have focus (can
* be transparent)
*/
selectionInactiveBackground?: string;
/** ANSI black (eg. `\x1b[30m`) */
black?: string;
@@ -382,23 +392,23 @@ declare module 'xterm' {
*/
export interface ILogger {
/**
* Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to
* debug.
* Log a debug message, this will only be called if
* {@link ITerminalOptions.logLevel} is set to debug.
*/
debug(message: string, ...args: any[]): void;
/**
* Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to
* info or below.
* Log a debug message, this will only be called if
* {@link ITerminalOptions.logLevel} is set to info or below.
*/
info(message: string, ...args: any[]): void;
/**
* Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to
* warn or below.
* Log a debug message, this will only be called if
* {@link ITerminalOptions.logLevel} is set to warn or below.
*/
warn(message: string, ...args: any[]): void;
/**
* Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to
* error or below.
* Log a debug message, this will only be called if
* {@link ITerminalOptions.logLevel} is set to error or below.
*/
error(message: string | Error, ...args: any[]): void;
}
@@ -438,8 +448,6 @@ declare module 'xterm' {
/**
* Represents a disposable that tracks is disposed state.
* @param onDispose event listener and
* @param isDisposed property.
*/
export interface IDisposableWithEvent extends IDisposable {
/**
@@ -454,7 +462,8 @@ declare module 'xterm' {
}
/**
* Represents a decoration in the terminal that is associated with a particular marker and DOM element.
* Represents a decoration in the terminal that is associated with a
* particular marker and DOM element.
*/
export interface IDecoration extends IDisposableWithEvent {
/*
@@ -477,9 +486,9 @@ declare module 'xterm' {
element: HTMLElement | undefined;
/**
* The options for the overview ruler that can be updated.
* This will only take effect when {@link IDecorationOptions.overviewRulerOptions}
* were provided initially.
* The options for the overview ruler that can be updated. This will only
* take effect when {@link IDecorationOptions.overviewRulerOptions} were
* provided initially.
*/
options: Pick<IDecorationOptions, 'overviewRulerOptions'>;
}
@@ -526,24 +535,26 @@ declare module 'xterm' {
readonly height?: number;
/**
* The background color of the cell(s). When 2 decorations both set the foreground color the
* last registered decoration will be used. Only the `#RRGGBB` format is supported.
* The background color of the cell(s). When 2 decorations both set the
* foreground color the last registered decoration will be used. Only the
* `#RRGGBB` format is supported.
*/
readonly backgroundColor?: string;
/**
* The foreground color of the cell(s). When 2 decorations both set the foreground color the
* last registered decoration will be used. Only the `#RRGGBB` format is supported.
* The foreground color of the cell(s). When 2 decorations both set the
* foreground color the last registered decoration will be used. Only the
* `#RRGGBB` format is supported.
*/
readonly foregroundColor?: string;
/**
* What layer to render the decoration at when {@link backgroundColor} or
* {@link foregroundColor} are used. `'bottom'` will render under the selection, `'top`' will
* render above the selection\*.
* {@link foregroundColor} are used. `'bottom'` will render under the
* selection, `'top`' will render above the selection\*.
*
* *\* The selection will render on top regardless of layer on the canvas renderer due to how
* it renders selection separately.*
* *\* The selection will render on top regardless of layer on the canvas
* renderer due to how it renders selection separately.*
*/
readonly layer?: 'bottom' | 'top';
@@ -554,7 +565,7 @@ declare module 'xterm' {
* @param color The color of the decoration.
* @param position The position of the decoration.
*/
overviewRulerOptions?: IDecorationOverviewRulerOptions
overviewRulerOptions?: IDecorationOverviewRulerOptions;
}
/**
@@ -574,7 +585,8 @@ declare module 'xterm' {
}
/**
* Enable various window manipulation and report features (CSI Ps ; Ps ; Ps t).
* Enable various window manipulation and report features
* (`CSI Ps ; Ps ; Ps t`).
*
* Most settings have no default implementation, as they heavily rely on
* the embedding environment.
@@ -594,10 +606,10 @@ declare module 'xterm' {
*
* Note on security:
* Most features are meant to deal with some information of the host machine
* where the terminal runs on. This is seen as a security risk possibly leaking
* sensitive data of the host to the program in the terminal. Therefore all options
* (even those without a default implementation) are guarded by the boolean flag
* and disabled by default.
* where the terminal runs on. This is seen as a security risk possibly
* leaking sensitive data of the host to the program in the terminal.
* Therefore all options (even those without a default implementation) are
* guarded by the boolean flag and disabled by default.
*/
export interface IWindowOptions {
/**
@@ -865,9 +877,9 @@ declare module 'xterm' {
onData: IEvent<string>;
/**
* Adds an event listener for when a key is pressed. The event value contains the
* string that will be sent in the data event as well as the DOM event that
* triggered it.
* Adds an event listener for when a key is pressed. The event value
* contains the string that will be sent in the data event as well as the
* DOM event that triggered it.
* @returns an `IDisposable` to stop listening.
*/
onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>;
@@ -1035,10 +1047,11 @@ declare module 'xterm' {
/**
* (EXPERIMENTAL) Adds a decoration to the terminal using
* @param decorationOptions, which takes a marker and an optional anchor,
* width, height, and x offset from the anchor. Returns the decoration or
* undefined if the alt buffer is active or the marker has already been disposed of.
* @throws when options include a negative x offset.
* @param decorationOptions, which takes a marker and an optional anchor,
* width, height, and x offset from the anchor. Returns the decoration or
* undefined if the alt buffer is active or the marker has already been
* disposed of.
* @throws when options include a negative x offset.
*/
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
@@ -1144,7 +1157,8 @@ declare module 'xterm' {
writeln(data: string | Uint8Array, callback?: () => void): void;
/**
* Writes text to the terminal, performing the necessary transformations for pasted text.
* Writes text to the terminal, performing the necessary transformations for
* pasted text.
* @param data The text to write to the terminal.
*/
paste(data: string): void;
@@ -1158,10 +1172,10 @@ declare module 'xterm' {
refresh(start: number, end: number): void;
/**
* Clears the texture atlas of the canvas renderer if it's active. Doing this will force a
* redraw of all glyphs which can workaround issues causing the texture to become corrupt, for
* example Chromium/Nvidia has an issue where the texture gets messed up when resuming the OS
* from sleep.
* Clears the texture atlas of the canvas renderer if it's active. Doing
* this will force a redraw of all glyphs which can workaround issues
* causing the texture to become corrupt, for example Chromium/Nvidia has an
* issue where the texture gets messed up when resuming the OS from sleep.
*/
clearTextureAtlas(): void;
@@ -1232,32 +1246,34 @@ declare module 'xterm' {
* @param text The text of the link.
* @param range The buffer range of the link.
*/
activate(event: MouseEvent, text: string, range: IBufferRange): void;
activate(event: MouseEvent, text: string, range: IBufferRange): void;
/**
* Called when the mouse hovers the link. To use this to create a DOM-based hover tooltip,
* create the hover element within `Terminal.element` and add the `xterm-hover` class to it,
* that will cause mouse events to not fall through and activate other links.
* @param event The mouse event triggering the callback.
* @param text The text of the link.
* @param range The buffer range of the link.
*/
hover?(event: MouseEvent, text: string, range: IBufferRange): void;
/**
* Called when the mouse hovers the link. To use this to create a DOM-based
* hover tooltip, create the hover element within `Terminal.element` and
* add the `xterm-hover` class to it, that will cause mouse events to not
* fall through and activate other links.
* @param event The mouse event triggering the callback.
* @param text The text of the link.
* @param range The buffer range of the link.
*/
hover?(event: MouseEvent, text: string, range: IBufferRange): void;
/**
* Called when the mouse leaves the link.
* @param event The mouse event triggering the callback.
* @param text The text of the link.
* @param range The buffer range of the link.
*/
leave?(event: MouseEvent, text: string, range: IBufferRange): void;
/**
* Called when the mouse leaves the link.
* @param event The mouse event triggering the callback.
* @param text The text of the link.
* @param range The buffer range of the link.
*/
leave?(event: MouseEvent, text: string, range: IBufferRange): void;
/**
* Whether to receive non-HTTP URLs from LinkProvider. When false, any usage of non-HTTP URLs
* will be ignored. Enabling this option without proper protection in `activate` function
* may cause security issues such as XSS.
*/
allowNonHttpProtocols?: boolean;
/**
* Whether to receive non-HTTP URLs from LinkProvider. When false, any
* usage of non-HTTP URLs will be ignored. Enabling this option without
* proper protection in `activate` function may cause security issues such
* as XSS.
*/
allowNonHttpProtocols?: boolean;
}
/**
@@ -1289,9 +1305,9 @@ declare module 'xterm' {
text: string;
/**
* What link decorations to show when hovering the link, this property is tracked and changes
* made after the link is provided will trigger changes. If not set, all decroations will be
* enabled.
* What link decorations to show when hovering the link, this property is
* tracked and changes made after the link is provided will trigger changes.
* If not set, all decroations will be enabled.
*/
decorations?: ILinkDecorations;
@@ -1303,9 +1319,10 @@ declare module 'xterm' {
activate(event: MouseEvent, text: string): void;
/**
* Called when the mouse hovers the link. To use this to create a DOM-based hover tooltip,
* create the hover element within `Terminal.element` and add the `xterm-hover` class to it,
* that will cause mouse events to not fall through and activate other links.
* Called when the mouse hovers the link. To use this to create a DOM-based
* hover tooltip, create the hover element within `Terminal.element` and add
* the `xterm-hover` class to it, that will cause mouse events to not fall
* through and activate other links.
* @param event The mouse event triggering the callback.
* @param text The text of the link.
*/
@@ -1429,7 +1446,8 @@ declare module 'xterm' {
export interface IBufferElementProvider {
/**
* Provides a document fragment or HTMLElement containing the buffer elements.
* Provides a document fragment or HTMLElement containing the buffer
* elements.
*/
provideBufferElements(): DocumentFragment | HTMLElement;
}
@@ -1676,43 +1694,43 @@ declare module 'xterm' {
export interface IParser {
/**
* Adds a handler for CSI escape sequences.
* @param id Specifies the function identifier under which the callback
* gets registered, e.g. {final: 'm'} for SGR.
* @param id Specifies the function identifier under which the callback gets
* registered, e.g. {final: 'm'} for SGR.
* @param callback The function to handle the sequence. The callback is
* called with the numerical params. If the sequence has subparams the
* array will contain subarrays with their numercial values.
* Return `true` if the sequence was handled, `false` if the parser should try
* a previous handler. The most recently added handler is tried first.
* called with the numerical params. If the sequence has subparams the array
* will contain subarrays with their numercial values. Return `true` if the
* sequence was handled, `false` if the parser should try a previous
* handler. The most recently added handler is tried first.
* @returns An IDisposable you can call to remove this handler.
*/
registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean | Promise<boolean>): IDisposable;
/**
* Adds a handler for DCS escape sequences.
* @param id Specifies the function identifier under which the callback
* gets registered, e.g. {intermediates: '$' final: 'q'} for DECRQSS.
* @param id Specifies the function identifier under which the callback gets
* registered, e.g. {intermediates: '$' final: 'q'} for DECRQSS.
* @param callback The function to handle the sequence. Note that the
* function will only be called once if the sequence finished sucessfully.
* There is currently no way to intercept smaller data chunks, data chunks
* will be stored up until the sequence is finished. Since DCS sequences
* are not limited by the amount of data this might impose a problem for
* big payloads. Currently xterm.js limits DCS payload to 10 MB
* which should give enough room for most use cases.
* The function gets the payload and numerical parameters as arguments.
* Return `true` if the sequence was handled, `false` if the parser should try
* a previous handler. The most recently added handler is tried first.
* will be stored up until the sequence is finished. Since DCS sequences are
* not limited by the amount of data this might impose a problem for big
* payloads. Currently xterm.js limits DCS payload to 10 MB which should
* give enough room for most use cases. The function gets the payload and
* numerical parameters as arguments. Return `true` if the sequence was
* handled, `false` if the parser should try a previous handler. The most
* recently added handler is tried first.
* @returns An IDisposable you can call to remove this handler.
*/
registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean | Promise<boolean>): IDisposable;
/**
* Adds a handler for ESC escape sequences.
* @param id Specifies the function identifier under which the callback
* gets registered, e.g. {intermediates: '%' final: 'G'} for
* default charset selection.
* @param id Specifies the function identifier under which the callback gets
* registered, e.g. {intermediates: '%' final: 'G'} for default charset
* selection.
* @param callback The function to handle the sequence.
* Return `true` if the sequence was handled, `false` if the parser should try
* a previous handler. The most recently added handler is tried first.
* Return `true` if the sequence was handled, `false` if the parser should
* try a previous handler. The most recently added handler is tried first.
* @returns An IDisposable you can call to remove this handler.
*/
registerEscHandler(id: IFunctionIdentifier, handler: () => boolean | Promise<boolean>): IDisposable;
@@ -1723,13 +1741,13 @@ declare module 'xterm' {
* @param callback The function to handle the sequence. Note that the
* function will only be called once if the sequence finished sucessfully.
* There is currently no way to intercept smaller data chunks, data chunks
* will be stored up until the sequence is finished. Since OSC sequences
* are not limited by the amount of data this might impose a problem for
* big payloads. Currently xterm.js limits OSC payload to 10 MB
* which should give enough room for most use cases.
* The callback is called with OSC data string.
* Return `true` if the sequence was handled, `false` if the parser should try
* a previous handler. The most recently added handler is tried first.
* will be stored up until the sequence is finished. Since OSC sequences are
* not limited by the amount of data this might impose a problem for big
* payloads. Currently xterm.js limits OSC payload to 10 MB which should
* give enough room for most use cases. The callback is called with OSC data
* string. Return `true` if the sequence was handled, `false` if the parser
* should try a previous handler. The most recently added handler is tried
* first.
* @returns An IDisposable you can call to remove this handler.
*/
registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;
@@ -1816,6 +1834,6 @@ declare module 'xterm' {
/**
* Auto-Wrap Mode (DECAWM): `CSI ? 7 h`
*/
readonly wraparoundMode: boolean
readonly wraparoundMode: boolean;
}
}