aboutsummaryrefslogtreecommitdiffstats
path: root/node_modules/xterm/src/browser/renderer/dom/DomRenderer.ts
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/xterm/src/browser/renderer/dom/DomRenderer.ts')
-rw-r--r--node_modules/xterm/src/browser/renderer/dom/DomRenderer.ts400
1 files changed, 400 insertions, 0 deletions
diff --git a/node_modules/xterm/src/browser/renderer/dom/DomRenderer.ts b/node_modules/xterm/src/browser/renderer/dom/DomRenderer.ts
new file mode 100644
index 0000000..ee28339
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/dom/DomRenderer.ts
@@ -0,0 +1,400 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types';
+import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory';
+import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
+import { Disposable } from 'common/Lifecycle';
+import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types';
+import { ICharSizeService } from 'browser/services/Services';
+import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services';
+import { EventEmitter, IEvent } from 'common/EventEmitter';
+import { color } from 'browser/Color';
+import { removeElementFromParent } from 'browser/Dom';
+
+const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
+const ROW_CONTAINER_CLASS = 'xterm-rows';
+const FG_CLASS_PREFIX = 'xterm-fg-';
+const BG_CLASS_PREFIX = 'xterm-bg-';
+const FOCUS_CLASS = 'xterm-focus';
+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
+ * canvas is not an option.
+ */
+export class DomRenderer extends Disposable implements IRenderer {
+ private _rowFactory: DomRendererRowFactory;
+ private _terminalClass: number = nextTerminalId++;
+
+ private _themeStyleElement!: HTMLStyleElement;
+ private _dimensionsStyleElement!: HTMLStyleElement;
+ private _rowContainer: HTMLElement;
+ private _rowElements: HTMLElement[] = [];
+ private _selectionContainer: HTMLElement;
+
+ public dimensions: IRenderDimensions;
+
+ public get onRequestRedraw(): IEvent<IRequestRedrawEvent> { return new EventEmitter<IRequestRedrawEvent>().event; }
+
+ constructor(
+ private _colors: IColorSet,
+ private readonly _element: HTMLElement,
+ private readonly _screenElement: HTMLElement,
+ private readonly _viewportElement: HTMLElement,
+ private readonly _linkifier: ILinkifier,
+ private readonly _linkifier2: ILinkifier2,
+ @IInstantiationService instantiationService: IInstantiationService,
+ @ICharSizeService private readonly _charSizeService: ICharSizeService,
+ @IOptionsService private readonly _optionsService: IOptionsService,
+ @IBufferService private readonly _bufferService: IBufferService
+ ) {
+ super();
+ this._rowContainer = 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.classList.add(SELECTION_CLASS);
+ this._selectionContainer.setAttribute('aria-hidden', 'true');
+
+ this.dimensions = {
+ scaledCharWidth: 0,
+ scaledCharHeight: 0,
+ scaledCellWidth: 0,
+ scaledCellHeight: 0,
+ scaledCharLeft: 0,
+ scaledCharTop: 0,
+ scaledCanvasWidth: 0,
+ scaledCanvasHeight: 0,
+ canvasWidth: 0,
+ canvasHeight: 0,
+ actualCellWidth: 0,
+ actualCellHeight: 0
+ };
+ this._updateDimensions();
+ this._injectCss();
+
+ this._rowFactory = instantiationService.createInstance(DomRendererRowFactory, document, this._colors);
+
+ this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
+ this._screenElement.appendChild(this._rowContainer);
+ this._screenElement.appendChild(this._selectionContainer);
+
+ this._linkifier.onShowLinkUnderline(e => this._onLinkHover(e));
+ this._linkifier.onHideLinkUnderline(e => this._onLinkLeave(e));
+
+ this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e));
+ this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e));
+ }
+
+ public dispose(): void {
+ this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);
+
+ // Outside influences such as React unmounts may manipulate the DOM before our disposal.
+ // https://github.com/xtermjs/xterm.js/issues/2960
+ removeElementFromParent(this._rowContainer, this._selectionContainer, this._themeStyleElement, this._dimensionsStyleElement);
+
+ super.dispose();
+ }
+
+ private _updateDimensions(): void {
+ this.dimensions.scaledCharWidth = this._charSizeService.width * window.devicePixelRatio;
+ this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * window.devicePixelRatio);
+ this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.rawOptions.letterSpacing);
+ this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.rawOptions.lineHeight);
+ this.dimensions.scaledCharLeft = 0;
+ this.dimensions.scaledCharTop = 0;
+ this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._bufferService.cols;
+ this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._bufferService.rows;
+ this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio);
+ this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio);
+ this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols;
+ this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows;
+
+ for (const element of this._rowElements) {
+ element.style.width = `${this.dimensions.canvasWidth}px`;
+ element.style.height = `${this.dimensions.actualCellHeight}px`;
+ element.style.lineHeight = `${this.dimensions.actualCellHeight}px`;
+ // Make sure rows don't overflow onto following row
+ element.style.overflow = 'hidden';
+ }
+
+ if (!this._dimensionsStyleElement) {
+ this._dimensionsStyleElement = document.createElement('style');
+ this._screenElement.appendChild(this._dimensionsStyleElement);
+ }
+
+ const styles =
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` +
+ ` display: inline-block;` +
+ ` height: 100%;` +
+ ` vertical-align: top;` +
+ ` width: ${this.dimensions.actualCellWidth}px` +
+ `}`;
+
+ this._dimensionsStyleElement.textContent = styles;
+
+ this._selectionContainer.style.height = this._viewportElement.style.height;
+ this._screenElement.style.width = `${this.dimensions.canvasWidth}px`;
+ this._screenElement.style.height = `${this.dimensions.canvasHeight}px`;
+ }
+
+ public setColors(colors: IColorSet): void {
+ this._colors = colors;
+ this._injectCss();
+ }
+
+ private _injectCss(): void {
+ if (!this._themeStyleElement) {
+ this._themeStyleElement = document.createElement('style');
+ this._screenElement.appendChild(this._themeStyleElement);
+ }
+
+ // Base CSS
+ let styles =
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +
+ ` color: ${this._colors.foreground.css};` +
+ ` font-family: ${this._optionsService.rawOptions.fontFamily};` +
+ ` font-size: ${this._optionsService.rawOptions.fontSize}px;` +
+ `}`;
+ // Text styles
+ styles +=
+ `${this._terminalSelector} span:not(.${BOLD_CLASS}) {` +
+ ` font-weight: ${this._optionsService.rawOptions.fontWeight};` +
+ `}` +
+ `${this._terminalSelector} span.${BOLD_CLASS} {` +
+ ` font-weight: ${this._optionsService.rawOptions.fontWeightBold};` +
+ `}` +
+ `${this._terminalSelector} span.${ITALIC_CLASS} {` +
+ ` font-style: italic;` +
+ `}`;
+ // Blink animation
+ styles +=
+ `@keyframes blink_box_shadow` + `_` + this._terminalClass + ` {` +
+ ` 50% {` +
+ ` box-shadow: none;` +
+ ` }` +
+ `}`;
+ styles +=
+ `@keyframes blink_block` + `_` + this._terminalClass + ` {` +
+ ` 0% {` +
+ ` background-color: ${this._colors.cursor.css};` +
+ ` color: ${this._colors.cursorAccent.css};` +
+ ` }` +
+ ` 50% {` +
+ ` background-color: ${this._colors.cursorAccent.css};` +
+ ` color: ${this._colors.cursor.css};` +
+ ` }` +
+ `}`;
+ // Cursor
+ styles +=
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
+ ` outline: 1px solid ${this._colors.cursor.css};` +
+ ` outline-offset: -1px;` +
+ `}` +
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}:not(.${CURSOR_STYLE_BLOCK_CLASS}) {` +
+ ` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` +
+ `}` +
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
+ ` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` +
+ `}` +
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
+ ` background-color: ${this._colors.cursor.css};` +
+ ` color: ${this._colors.cursorAccent.css};` +
+ `}` +
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` +
+ ` box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${this._colors.cursor.css} inset;` +
+ `}` +
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` +
+ ` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` +
+ `}`;
+ // Selection
+ styles +=
+ `${this._terminalSelector} .${SELECTION_CLASS} {` +
+ ` position: absolute;` +
+ ` top: 0;` +
+ ` left: 0;` +
+ ` z-index: 1;` +
+ ` pointer-events: none;` +
+ `}` +
+ `${this._terminalSelector} .${SELECTION_CLASS} div {` +
+ ` position: absolute;` +
+ ` background-color: ${this._colors.selectionTransparent.css};` +
+ `}`;
+ // Colors
+ this._colors.ansi.forEach((c, i) => {
+ styles +=
+ `${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` +
+ `${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;
+ });
+ styles +=
+ `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(this._colors.background).css}; }` +
+ `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this._colors.foreground.css}; }`;
+
+ this._themeStyleElement.textContent = styles;
+ }
+
+ public onDevicePixelRatioChange(): void {
+ this._updateDimensions();
+ }
+
+ private _refreshRowElements(cols: number, rows: number): void {
+ // Add missing elements
+ for (let i = this._rowElements.length; i <= rows; i++) {
+ const row = document.createElement('div');
+ this._rowContainer.appendChild(row);
+ this._rowElements.push(row);
+ }
+ // Remove excess elements
+ while (this._rowElements.length > rows) {
+ this._rowContainer.removeChild(this._rowElements.pop()!);
+ }
+ }
+
+ public onResize(cols: number, rows: number): void {
+ this._refreshRowElements(cols, rows);
+ this._updateDimensions();
+ }
+
+ public onCharSizeChanged(): void {
+ this._updateDimensions();
+ }
+
+ public onBlur(): void {
+ this._rowContainer.classList.remove(FOCUS_CLASS);
+ }
+
+ public onFocus(): void {
+ this._rowContainer.classList.add(FOCUS_CLASS);
+ }
+
+ public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
+ // Remove all selections
+ while (this._selectionContainer.children.length) {
+ this._selectionContainer.removeChild(this._selectionContainer.children[0]);
+ }
+
+ // Selection does not exist
+ if (!start || !end) {
+ return;
+ }
+
+ // 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);
+
+ // No need to draw the selection
+ if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
+ return;
+ }
+
+ // Create the selections
+ const documentFragment = document.createDocumentFragment();
+
+ if (columnSelectMode) {
+ documentFragment.appendChild(
+ this._createSelectionElement(viewportCappedStartRow, start[0], end[0], viewportCappedEndRow - viewportCappedStartRow + 1)
+ );
+ } else {
+ // Draw first row
+ const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
+ const endCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
+ documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));
+ // Draw middle rows
+ const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;
+ documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._bufferService.cols, middleRowsCount));
+ // Draw final row
+ if (viewportCappedStartRow !== viewportCappedEndRow) {
+ // Only draw viewportEndRow if it's not the same as viewporttartRow
+ const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
+ documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol));
+ }
+ }
+ this._selectionContainer.appendChild(documentFragment);
+ }
+
+ /**
+ * Creates a selection element at the specified position.
+ * @param row The row of the selection.
+ * @param colStart The start column.
+ * @param colEnd The end columns.
+ */
+ private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {
+ const element = document.createElement('div');
+ element.style.height = `${rowCount * this.dimensions.actualCellHeight}px`;
+ element.style.top = `${row * this.dimensions.actualCellHeight}px`;
+ element.style.left = `${colStart * this.dimensions.actualCellWidth}px`;
+ element.style.width = `${this.dimensions.actualCellWidth * (colEnd - colStart)}px`;
+ return element;
+ }
+
+ public onCursorMove(): void {
+ // No-op, the cursor is drawn when rows are drawn
+ }
+
+ public onOptionsChanged(): void {
+ // Force a refresh
+ this._updateDimensions();
+ this._injectCss();
+ }
+
+ public clear(): void {
+ for (const e of this._rowElements) {
+ e.innerText = '';
+ }
+ }
+
+ public renderRows(start: number, end: number): void {
+ const cursorAbsoluteY = this._bufferService.buffer.ybase + this._bufferService.buffer.y;
+ const cursorX = Math.min(this._bufferService.buffer.x, this._bufferService.cols - 1);
+ const cursorBlink = this._optionsService.rawOptions.cursorBlink;
+
+ for (let y = start; y <= end; y++) {
+ const rowElement = this._rowElements[y];
+ rowElement.innerText = '';
+
+ const row = y + this._bufferService.buffer.ydisp;
+ const lineData = this._bufferService.buffer.lines.get(row);
+ const cursorStyle = this._optionsService.rawOptions.cursorStyle;
+ rowElement.appendChild(this._rowFactory.createRow(lineData!, row, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols));
+ }
+ }
+
+ private get _terminalSelector(): string {
+ return `.${TERMINAL_CLASS_PREFIX}${this._terminalClass}`;
+ }
+
+ private _onLinkHover(e: ILinkifierEvent): void {
+ this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, true);
+ }
+
+ private _onLinkLeave(e: ILinkifierEvent): void {
+ this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, false);
+ }
+
+ private _setCellUnderline(x: number, x2: number, y: number, y2: number, cols: number, enabled: boolean): void {
+ while (x !== x2 || y !== y2) {
+ const row = this._rowElements[y];
+ if (!row) {
+ return;
+ }
+ const span = row.children[x] as HTMLElement;
+ if (span) {
+ span.style.textDecoration = enabled ? 'underline' : 'none';
+ }
+ if (++x >= cols) {
+ x = 0;
+ y++;
+ }
+ }
+ }
+}