Merge remote-tracking branch 'upstream/master' into pr/tisilent/4703

This commit is contained in:
Daniel Imms
2023-11-02 10:39:15 -07:00
295 changed files with 2464 additions and 707 deletions
+30 -17
View File
@@ -7,9 +7,10 @@ import { DomRendererRowFactory, RowCss } from 'browser/renderer/dom/DomRendererR
import { WidthCache } from 'browser/renderer/dom/WidthCache';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { ILinkifier2, ILinkifierEvent, ReadonlyColorSet } from 'browser/Types';
import { ILinkifier2, ILinkifierEvent, ITerminal, ReadonlyColorSet } from 'browser/Types';
import { color } from 'common/Color';
import { EventEmitter } from 'common/EventEmitter';
import { Disposable, toDisposable } from 'common/Lifecycle';
@@ -25,7 +26,6 @@ const SELECTION_CLASS = 'xterm-selection';
let nextTerminalId = 1;
/**
* A fallback renderer for when canvas is slow. This is not meant to be
* particularly fast or feature complete, more just stable and usable for when
@@ -41,15 +41,19 @@ export class DomRenderer extends Disposable implements IRenderer {
private _rowElements: HTMLElement[] = [];
private _selectionContainer: HTMLElement;
private _widthCache: WidthCache;
private _selectionRenderModel: ISelectionRenderModel = createSelectionRenderModel();
public dimensions: IRenderDimensions;
public readonly onRequestRedraw = this.register(new EventEmitter<IRequestRedrawEvent>()).event;
constructor(
private readonly _terminal: ITerminal,
private readonly _document: Document,
private readonly _element: HTMLElement,
private readonly _screenElement: HTMLElement,
private readonly _viewportElement: HTMLElement,
private readonly _helperContainer: HTMLElement,
private readonly _linkifier2: ILinkifier2,
@IInstantiationService instantiationService: IInstantiationService,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@@ -59,12 +63,12 @@ export class DomRenderer extends Disposable implements IRenderer {
@IThemeService private readonly _themeService: IThemeService
) {
super();
this._rowContainer = document.createElement('div');
this._rowContainer = this._document.createElement('div');
this._rowContainer.classList.add(ROW_CONTAINER_CLASS);
this._rowContainer.style.lineHeight = 'normal';
this._rowContainer.setAttribute('aria-hidden', 'true');
this._refreshRowElements(this._bufferService.cols, this._bufferService.rows);
this._selectionContainer = document.createElement('div');
this._selectionContainer = this._document.createElement('div');
this._selectionContainer.classList.add(SELECTION_CLASS);
this._selectionContainer.setAttribute('aria-hidden', 'true');
@@ -96,7 +100,7 @@ export class DomRenderer extends Disposable implements IRenderer {
this._dimensionsStyleElement.remove();
}));
this._widthCache = new WidthCache(document);
this._widthCache = new WidthCache(this._document, this._helperContainer);
this._widthCache.setFont(
this._optionsService.rawOptions.fontFamily,
this._optionsService.rawOptions.fontSize,
@@ -130,7 +134,7 @@ export class DomRenderer extends Disposable implements IRenderer {
}
if (!this._dimensionsStyleElement) {
this._dimensionsStyleElement = document.createElement('style');
this._dimensionsStyleElement = this._document.createElement('style');
this._screenElement.appendChild(this._dimensionsStyleElement);
}
@@ -150,7 +154,7 @@ export class DomRenderer extends Disposable implements IRenderer {
private _injectCss(colors: ReadonlyColorSet): void {
if (!this._themeStyleElement) {
this._themeStyleElement = document.createElement('style');
this._themeStyleElement = this._document.createElement('style');
this._screenElement.appendChild(this._themeStyleElement);
}
@@ -276,7 +280,7 @@ export class DomRenderer extends Disposable implements IRenderer {
private _refreshRowElements(cols: number, rows: number): void {
// Add missing elements
for (let i = this._rowElements.length; i <= rows; i++) {
const row = document.createElement('div');
const row = this._document.createElement('div');
this._rowContainer.appendChild(row);
this._rowElements.push(row);
}
@@ -289,6 +293,7 @@ export class DomRenderer extends Disposable implements IRenderer {
public handleResize(cols: number, rows: number): void {
this._refreshRowElements(cols, rows);
this._updateDimensions();
this.handleSelectionChanged(this._selectionRenderModel.selectionStart, this._selectionRenderModel.selectionEnd, this._selectionRenderModel.columnSelectMode);
}
public handleCharSizeChanged(): void {
@@ -318,11 +323,13 @@ export class DomRenderer extends Disposable implements IRenderer {
return;
}
this._selectionRenderModel.update(this._terminal, start, end, columnSelectMode);
// Translate from buffer position to viewport position
const viewportStartRow = start[1] - this._bufferService.buffer.ydisp;
const viewportEndRow = end[1] - this._bufferService.buffer.ydisp;
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1);
const viewportStartRow = this._selectionRenderModel.viewportStartRow;
const viewportEndRow = this._selectionRenderModel.viewportEndRow;
const viewportCappedStartRow = this._selectionRenderModel.viewportCappedStartRow;
const viewportCappedEndRow = this._selectionRenderModel.viewportCappedEndRow;
// No need to draw the selection
if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
@@ -330,7 +337,7 @@ export class DomRenderer extends Disposable implements IRenderer {
}
// Create the selections
const documentFragment = document.createDocumentFragment();
const documentFragment = this._document.createDocumentFragment();
if (columnSelectMode) {
const isXFlipped = start[0] > end[0];
@@ -362,11 +369,17 @@ export class DomRenderer extends Disposable implements IRenderer {
* @param colEnd The end columns.
*/
private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {
const element = document.createElement('div');
const element = this._document.createElement('div');
const left = colStart * this.dimensions.css.cell.width;
let width = this.dimensions.css.cell.width * (colEnd - colStart);
if (left + width > this.dimensions.css.canvas.width) {
width = this.dimensions.css.canvas.width - left;
}
element.style.height = `${rowCount * this.dimensions.css.cell.height}px`;
element.style.top = `${row * this.dimensions.css.cell.height}px`;
element.style.left = `${colStart * this.dimensions.css.cell.width}px`;
element.style.width = `${this.dimensions.css.cell.width * (colEnd - colStart)}px`;
element.style.left = `${left}px`;
element.style.width = `${width}px`;
return element;
}
@@ -14,8 +14,8 @@ import { MockCoreService, MockDecorationService, MockOptionsService } from 'comm
import { MockCharacterJoinerService, MockCoreBrowserService, MockThemeService } from 'browser/TestUtils.test';
import { TestWidthCache } from 'browser/renderer/dom/WidthCache.test';
const EMPTY_WIDTH = new TestWidthCache(new jsdom.JSDOM('').window.document);
const dom = new jsdom.JSDOM('');
const EMPTY_WIDTH = new TestWidthCache(dom.window.document, dom.window.document.createElement('div'));
describe('DomRendererRowFactory', () => {
@@ -96,6 +96,24 @@ describe('DomRendererRowFactory', () => {
}
});
it('should not display cursor for before initializing', () => {
const coreService = new MockCoreService();
coreService.isCursorInitialized = false;
const rowFactory = new DomRendererRowFactory(
dom.window.document,
new MockCharacterJoinerService(),
new MockOptionsService(),
new MockCoreBrowserService(),
coreService,
new MockDecorationService(),
new MockThemeService()
);
const spans = rowFactory.createRow(lineData, 0, true, 'block', undefined, 0, false, 5, EMPTY_WIDTH, -1, -1);
assert.equal(extractHtml(spans),
`<span> </span>`
);
});
describe('attributes', () => {
it('should add class for bold', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
@@ -218,7 +218,7 @@ export class DomRendererRowFactory {
}
}
if (!this._coreService.isCursorHidden && isCursorCell) {
if (!this._coreService.isCursorHidden && isCursorCell && this._coreService.isCursorInitialized) {
classes.push(RowCss.CURSOR_CLASS);
if (this._coreBrowserService.isFocused) {
if (cursorBlink) {
+2 -1
View File
@@ -36,7 +36,8 @@ function castf32(v: number): number {
describe('WidthCache', () => {
let wc: TestWidthCache;
beforeEach(() => {
wc = new TestWidthCache(new jsdom.JSDOM('').window.document);
const dom = new jsdom.JSDOM('');
wc = new TestWidthCache(dom.window.document, dom.window.document.createElement('div'));
wc.setFont('monospace', 15, 'normal', 'bold');
});
describe('cache invalidation', () => {
+8 -5
View File
@@ -45,25 +45,28 @@ export class WidthCache implements IDisposable {
private _container: HTMLDivElement;
private _measureElements: HTMLSpanElement[] = [];
constructor(_document: Document) {
constructor(_document: Document, _helperContainer: HTMLElement) {
this._container = _document.createElement('div');
this._container.style.position = 'absolute';
this._container.style.top = '-50000px';
this._container.style.width = '50000px';
this._container.classList.add('xterm-width-cache-measure-container');
this._container.setAttribute('aria-hidden', 'true');
// SP should stack in spans
this._container.style.whiteSpace = 'pre';
// avoid undercuts in non-monospace fonts from kerning
this._container.style.fontKerning = 'none';
const regular = _document.createElement('span');
regular.classList.add('xterm-char-measure-element');
const bold = _document.createElement('span');
bold.classList.add('xterm-char-measure-element');
bold.style.fontWeight = 'bold';
const italic = _document.createElement('span');
italic.classList.add('xterm-char-measure-element');
italic.style.fontStyle = 'italic';
const boldItalic = _document.createElement('span');
boldItalic.classList.add('xterm-char-measure-element');
boldItalic.style.fontWeight = 'bold';
boldItalic.style.fontStyle = 'italic';
@@ -74,7 +77,7 @@ export class WidthCache implements IDisposable {
this._container.appendChild(italic);
this._container.appendChild(boldItalic);
_document.body.appendChild(this._container);
_helperContainer.appendChild(this._container);
this.clear();
}
@@ -4,7 +4,7 @@ import { ReadonlyColorSet } from 'browser/Types';
import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';
import { IDecorationService, IOptionsService } from 'common/services/Services';
import { ICellData } from 'common/Types';
import { Terminal } from 'xterm';
import { Terminal } from '@xterm/xterm';
// Work variables to avoid garbage collection
let $fg = 0;
@@ -4,7 +4,7 @@
*/
import { TextureAtlas } from 'browser/renderer/shared/TextureAtlas';
import { ITerminalOptions, Terminal } from 'xterm';
import { ITerminalOptions, Terminal } from '@xterm/xterm';
import { ITerminal, ReadonlyColorSet } from 'browser/Types';
import { ICharAtlasConfig, ITextureAtlas } from 'browser/renderer/shared/Types';
import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils';
@@ -5,7 +5,7 @@
import { ICharAtlasConfig } from './Types';
import { Attributes } from 'common/buffer/Constants';
import { ITerminalOptions } from 'xterm';
import { ITerminalOptions } from '@xterm/xterm';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { NULL_COLOR } from 'common/Color';
+1 -1
View File
@@ -474,7 +474,7 @@ function drawPatternChar(
if (!pattern) {
const width = charDefinition[0].length;
const height = charDefinition.length;
const tmpCanvas = document.createElement('canvas');
const tmpCanvas = ctx.canvas.ownerDocument.createElement('canvas');
tmpCanvas.width = width;
tmpCanvas.height = height;
const tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d'));
@@ -3,8 +3,9 @@
* @license MIT
*/
import { ITerminal } from 'browser/Types';
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { Terminal } from 'xterm';
import { Terminal } from '@xterm/xterm';
class SelectionRenderModel implements ISelectionRenderModel {
public hasSelection!: boolean;
@@ -35,7 +36,7 @@ class SelectionRenderModel implements ISelectionRenderModel {
this.selectionEnd = undefined;
}
public update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
public update(terminal: ITerminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
this.selectionStart = start;
this.selectionEnd = end;
// Selection does not exist
@@ -45,8 +46,9 @@ class SelectionRenderModel implements ISelectionRenderModel {
}
// Translate from buffer position to viewport position
const viewportStartRow = start[1] - terminal.buffer.active.viewportY;
const viewportEndRow = end[1] - terminal.buffer.active.viewportY;
const viewportY = terminal.buffers.active.ydisp;
const viewportStartRow = start[1] - viewportY;
const viewportEndRow = end[1] - viewportY;
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1);
+3 -3
View File
@@ -3,8 +3,8 @@
* @license MIT
*/
import { FontWeight, Terminal } from 'xterm';
import { IColorSet } from 'browser/Types';
import { FontWeight, Terminal } from '@xterm/xterm';
import { IColorSet, ITerminal } from 'browser/Types';
import { IDisposable } from 'common/Types';
import { IEvent } from 'common/EventEmitter';
@@ -168,6 +168,6 @@ export interface ISelectionRenderModel {
readonly selectionStart: [number, number] | undefined;
readonly selectionEnd: [number, number] | undefined;
clear(): void;
update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode?: boolean): void;
update(terminal: ITerminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode?: boolean): void;
isCellSelected(terminal: Terminal, x: number, y: number): boolean;
}