510 lines
21 KiB
TypeScript
510 lines
21 KiB
TypeScript
/**
|
|
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
|
* @license MIT
|
|
*/
|
|
|
|
import { DomRendererRowFactory, RowCss } from 'browser/renderer/dom/DomRendererRowFactory';
|
|
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 { ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
|
import { ILinkifier2, ILinkifierEvent, ReadonlyColorSet } from 'browser/Types';
|
|
import { color } from 'common/Color';
|
|
import { EventEmitter } from 'common/EventEmitter';
|
|
import { Disposable, toDisposable } from 'common/Lifecycle';
|
|
import { IBufferService, IInstantiationService, IOptionsService } from 'common/services/Services';
|
|
|
|
|
|
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;
|
|
private _widthCache: WidthCache;
|
|
|
|
public dimensions: IRenderDimensions;
|
|
|
|
public readonly onRequestRedraw = this.register(new EventEmitter<IRequestRedrawEvent>()).event;
|
|
|
|
constructor(
|
|
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,
|
|
@IOptionsService private readonly _optionsService: IOptionsService,
|
|
@IBufferService private readonly _bufferService: IBufferService,
|
|
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,
|
|
@IThemeService private readonly _themeService: IThemeService
|
|
) {
|
|
super();
|
|
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 = this._document.createElement('div');
|
|
this._selectionContainer.classList.add(SELECTION_CLASS);
|
|
this._selectionContainer.setAttribute('aria-hidden', 'true');
|
|
|
|
this.dimensions = createRenderDimensions();
|
|
this._updateDimensions();
|
|
this.register(this._optionsService.onOptionChange(() => this._handleOptionsChanged()));
|
|
|
|
this.register(this._themeService.onChangeColors(e => this._injectCss(e)));
|
|
this._injectCss(this._themeService.colors);
|
|
|
|
this._rowFactory = instantiationService.createInstance(DomRendererRowFactory, document);
|
|
|
|
this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
|
|
this._screenElement.appendChild(this._rowContainer);
|
|
this._screenElement.appendChild(this._selectionContainer);
|
|
|
|
this.register(this._linkifier2.onShowLinkUnderline(e => this._handleLinkHover(e)));
|
|
this.register(this._linkifier2.onHideLinkUnderline(e => this._handleLinkLeave(e)));
|
|
|
|
this.register(toDisposable(() => {
|
|
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
|
|
this._rowContainer.remove();
|
|
this._selectionContainer.remove();
|
|
this._widthCache.dispose();
|
|
this._themeStyleElement.remove();
|
|
this._dimensionsStyleElement.remove();
|
|
}));
|
|
|
|
this._widthCache = new WidthCache(this._document, this._helperContainer);
|
|
this._widthCache.setFont(
|
|
this._optionsService.rawOptions.fontFamily,
|
|
this._optionsService.rawOptions.fontSize,
|
|
this._optionsService.rawOptions.fontWeight,
|
|
this._optionsService.rawOptions.fontWeightBold
|
|
);
|
|
this._setDefaultSpacing();
|
|
}
|
|
|
|
private _updateDimensions(): void {
|
|
const dpr = this._coreBrowserService.dpr;
|
|
this.dimensions.device.char.width = this._charSizeService.width * dpr;
|
|
this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * dpr);
|
|
this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._optionsService.rawOptions.letterSpacing);
|
|
this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._optionsService.rawOptions.lineHeight);
|
|
this.dimensions.device.char.left = 0;
|
|
this.dimensions.device.char.top = 0;
|
|
this.dimensions.device.canvas.width = this.dimensions.device.cell.width * this._bufferService.cols;
|
|
this.dimensions.device.canvas.height = this.dimensions.device.cell.height * this._bufferService.rows;
|
|
this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / dpr);
|
|
this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / dpr);
|
|
this.dimensions.css.cell.width = this.dimensions.css.canvas.width / this._bufferService.cols;
|
|
this.dimensions.css.cell.height = this.dimensions.css.canvas.height / this._bufferService.rows;
|
|
|
|
for (const element of this._rowElements) {
|
|
element.style.width = `${this.dimensions.css.canvas.width}px`;
|
|
element.style.height = `${this.dimensions.css.cell.height}px`;
|
|
element.style.lineHeight = `${this.dimensions.css.cell.height}px`;
|
|
// Make sure rows don't overflow onto following row
|
|
element.style.overflow = 'hidden';
|
|
}
|
|
|
|
if (!this._dimensionsStyleElement) {
|
|
this._dimensionsStyleElement = this._document.createElement('style');
|
|
this._screenElement.appendChild(this._dimensionsStyleElement);
|
|
}
|
|
|
|
const styles =
|
|
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` +
|
|
` display: inline-block;` + // TODO: find workaround for inline-block (creates ~20% render penalty)
|
|
` height: 100%;` +
|
|
` vertical-align: top;` +
|
|
`}`;
|
|
|
|
this._dimensionsStyleElement.textContent = styles;
|
|
|
|
this._selectionContainer.style.height = this._viewportElement.style.height;
|
|
this._screenElement.style.width = `${this.dimensions.css.canvas.width}px`;
|
|
this._screenElement.style.height = `${this.dimensions.css.canvas.height}px`;
|
|
}
|
|
|
|
private _injectCss(colors: ReadonlyColorSet): void {
|
|
if (!this._themeStyleElement) {
|
|
this._themeStyleElement = this._document.createElement('style');
|
|
this._screenElement.appendChild(this._themeStyleElement);
|
|
}
|
|
|
|
// Base CSS
|
|
let styles =
|
|
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +
|
|
` color: ${colors.foreground.css};` +
|
|
` font-family: ${this._optionsService.rawOptions.fontFamily};` +
|
|
` font-size: ${this._optionsService.rawOptions.fontSize}px;` +
|
|
` font-kerning: none;` +
|
|
` white-space: pre` +
|
|
`}`;
|
|
styles +=
|
|
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .xterm-dim {` +
|
|
` color: ${color.multiplyOpacity(colors.foreground, 0.5).css};` +
|
|
`}`;
|
|
// Text styles
|
|
styles +=
|
|
`${this._terminalSelector} span:not(.${RowCss.BOLD_CLASS}) {` +
|
|
` font-weight: ${this._optionsService.rawOptions.fontWeight};` +
|
|
`}` +
|
|
`${this._terminalSelector} span.${RowCss.BOLD_CLASS} {` +
|
|
` font-weight: ${this._optionsService.rawOptions.fontWeightBold};` +
|
|
`}` +
|
|
`${this._terminalSelector} span.${RowCss.ITALIC_CLASS} {` +
|
|
` font-style: italic;` +
|
|
`}`;
|
|
// Blink animation
|
|
styles +=
|
|
`@keyframes blink_box_shadow` + `_` + this._terminalClass + ` {` +
|
|
` 50% {` +
|
|
` border-bottom-style: hidden;` +
|
|
` }` +
|
|
`}`;
|
|
styles +=
|
|
`@keyframes blink_block` + `_` + this._terminalClass + ` {` +
|
|
` 0% {` +
|
|
` background-color: ${colors.cursor.css};` +
|
|
` color: ${colors.cursorAccent.css};` +
|
|
` }` +
|
|
` 50% {` +
|
|
` background-color: inherit;` +
|
|
` color: ${colors.cursor.css};` +
|
|
` }` +
|
|
`}`;
|
|
// Cursor
|
|
styles +=
|
|
`${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} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +
|
|
` background-color: ${colors.cursor.css} !important;` +
|
|
` color: ${colors.cursorAccent.css} !important;` +
|
|
`}` +
|
|
`${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;` +
|
|
`}` +
|
|
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} {` +
|
|
` border-bottom: 1px ${colors.cursor.css};` +
|
|
` border-bottom-style: solid;` +
|
|
` height: calc(100% - 1px);` +
|
|
`}`;
|
|
// Selection
|
|
styles +=
|
|
`${this._terminalSelector} .${SELECTION_CLASS} {` +
|
|
` position: absolute;` +
|
|
` top: 0;` +
|
|
` left: 0;` +
|
|
` z-index: 1;` +
|
|
` pointer-events: none;` +
|
|
`}` +
|
|
`${this._terminalSelector}.focus .${SELECTION_CLASS} div {` +
|
|
` position: absolute;` +
|
|
` background-color: ${colors.selectionBackgroundOpaque.css};` +
|
|
`}` +
|
|
`${this._terminalSelector} .${SELECTION_CLASS} div {` +
|
|
` position: absolute;` +
|
|
` background-color: ${colors.selectionInactiveBackgroundOpaque.css};` +
|
|
`}`;
|
|
// Colors
|
|
for (const [i, c] of colors.ansi.entries()) {
|
|
styles +=
|
|
`${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` +
|
|
`${this._terminalSelector} .${FG_CLASS_PREFIX}${i}.${RowCss.DIM_CLASS} { color: ${color.multiplyOpacity(c, 0.5).css}; }` +
|
|
`${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;
|
|
}
|
|
styles +=
|
|
`${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(colors.background).css}; }` +
|
|
`${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR}.${RowCss.DIM_CLASS} { color: ${color.multiplyOpacity(color.opaque(colors.background), 0.5).css}; }` +
|
|
`${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${colors.foreground.css}; }`;
|
|
|
|
this._themeStyleElement.textContent = styles;
|
|
}
|
|
|
|
/**
|
|
* default letter spacing
|
|
* Due to rounding issues in dimensions dpr calc glyph might render
|
|
* slightly too wide or too narrow. The method corrects the stacking offsets
|
|
* by applying a default letter-spacing for all chars.
|
|
* The value gets passed to the row factory to avoid setting this value again
|
|
* (render speedup is roughly 10%).
|
|
*/
|
|
private _setDefaultSpacing(): void {
|
|
// measure same char as in CharSizeService to get the base deviation
|
|
const spacing = this.dimensions.css.cell.width - this._widthCache.get('W', false, false);
|
|
this._rowContainer.style.letterSpacing = `${spacing}px`;
|
|
this._rowFactory.defaultSpacing = spacing;
|
|
}
|
|
|
|
public handleDevicePixelRatioChange(): void {
|
|
this._updateDimensions();
|
|
this._widthCache.clear();
|
|
this._setDefaultSpacing();
|
|
}
|
|
|
|
private _refreshRowElements(cols: number, rows: number): void {
|
|
// Add missing elements
|
|
for (let i = this._rowElements.length; i <= rows; i++) {
|
|
const row = this._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 handleResize(cols: number, rows: number): void {
|
|
this._refreshRowElements(cols, rows);
|
|
this._updateDimensions();
|
|
}
|
|
|
|
public handleCharSizeChanged(): void {
|
|
this._updateDimensions();
|
|
this._widthCache.clear();
|
|
this._setDefaultSpacing();
|
|
}
|
|
|
|
public handleBlur(): void {
|
|
this._rowContainer.classList.remove(FOCUS_CLASS);
|
|
this.renderRows(0, this._bufferService.rows - 1);
|
|
}
|
|
|
|
public handleFocus(): void {
|
|
this._rowContainer.classList.add(FOCUS_CLASS);
|
|
this.renderRows(this._bufferService.buffer.y, this._bufferService.buffer.y);
|
|
}
|
|
|
|
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
|
// Remove all selections
|
|
this._selectionContainer.replaceChildren();
|
|
this._rowFactory.handleSelectionChanged(start, end, columnSelectMode);
|
|
this.renderRows(0, this._bufferService.rows - 1);
|
|
|
|
// 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 = this._document.createDocumentFragment();
|
|
|
|
if (columnSelectMode) {
|
|
const isXFlipped = start[0] > end[0];
|
|
documentFragment.appendChild(
|
|
this._createSelectionElement(viewportCappedStartRow, isXFlipped ? end[0] : start[0], isXFlipped ? 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 = this._document.createElement('div');
|
|
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`;
|
|
return element;
|
|
}
|
|
|
|
public handleCursorMove(): void {
|
|
// No-op, the cursor is drawn when rows are drawn
|
|
}
|
|
|
|
private _handleOptionsChanged(): void {
|
|
// Force a refresh
|
|
this._updateDimensions();
|
|
// Refresh CSS
|
|
this._injectCss(this._themeService.colors);
|
|
// update spacing cache
|
|
this._widthCache.setFont(
|
|
this._optionsService.rawOptions.fontFamily,
|
|
this._optionsService.rawOptions.fontSize,
|
|
this._optionsService.rawOptions.fontWeight,
|
|
this._optionsService.rawOptions.fontWeightBold
|
|
);
|
|
this._setDefaultSpacing();
|
|
}
|
|
|
|
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`
|
|
*
|
|
* references:
|
|
* - https://github.com/testing-library/react-testing-library/issues/1146
|
|
* - https://github.com/jsdom/jsdom/issues/1245
|
|
*/
|
|
e.replaceChildren();
|
|
}
|
|
}
|
|
|
|
public renderRows(start: number, end: number): void {
|
|
const buffer = this._bufferService.buffer;
|
|
const cursorAbsoluteY = buffer.ybase + buffer.y;
|
|
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;
|
|
const rowElement = this._rowElements[y];
|
|
const lineData = buffer.lines.get(row);
|
|
if (!rowElement || !lineData) {
|
|
break;
|
|
}
|
|
rowElement.replaceChildren(
|
|
...this._rowFactory.createRow(
|
|
lineData,
|
|
row,
|
|
row === cursorAbsoluteY,
|
|
cursorStyle,
|
|
cursorInactiveStyle,
|
|
cursorX,
|
|
cursorBlink,
|
|
this.dimensions.css.cell.width,
|
|
this._widthCache,
|
|
-1,
|
|
-1
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
private get _terminalSelector(): string {
|
|
return `.${TERMINAL_CLASS_PREFIX}${this._terminalClass}`;
|
|
}
|
|
|
|
private _handleLinkHover(e: ILinkifierEvent): void {
|
|
this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, true);
|
|
}
|
|
|
|
private _handleLinkLeave(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 {
|
|
/**
|
|
* NOTE: The linkifier may send out of viewport y-values if:
|
|
* - negative y-value: the link started at a higher line
|
|
* - y-value >= maxY: the link ends at a line below viewport
|
|
*
|
|
* For negative y-values we can simply adjust x = 0,
|
|
* as higher up link start means, that everything from
|
|
* (0,0) is a link under top-down-left-right char progression
|
|
*
|
|
* Additionally there might be a small chance of out-of-sync x|y-values
|
|
* from a race condition of render updates vs. link event handler execution:
|
|
* - (sync) resize: chances terminal buffer in sync, schedules render update async
|
|
* - (async) link handler race condition: new buffer metrics, but still on old render state
|
|
* - (async) render update: brings term metrics and render state back in sync
|
|
*/
|
|
// clip coords into viewport
|
|
if (y < 0) x = 0;
|
|
if (y2 < 0) x2 = 0;
|
|
const maxY = this._bufferService.rows - 1;
|
|
y = Math.max(Math.min(y, maxY), 0);
|
|
y2 = Math.max(Math.min(y2, maxY), 0);
|
|
|
|
cols = Math.min(cols, this._bufferService.cols);
|
|
const buffer = this._bufferService.buffer;
|
|
const cursorAbsoluteY = buffer.ybase + buffer.y;
|
|
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) {
|
|
const row = i + buffer.ydisp;
|
|
const rowElement = this._rowElements[i];
|
|
const bufferline = buffer.lines.get(row);
|
|
if (!rowElement || !bufferline) {
|
|
break;
|
|
}
|
|
rowElement.replaceChildren(
|
|
...this._rowFactory.createRow(
|
|
bufferline,
|
|
row,
|
|
row === cursorAbsoluteY,
|
|
cursorStyle,
|
|
cursorInactiveStyle,
|
|
cursorX,
|
|
cursorBlink,
|
|
this.dimensions.css.cell.width,
|
|
this._widthCache,
|
|
enabled ? (i === y ? x : 0) : -1,
|
|
enabled ? ((i === y2 ? x2 : cols) - 1) : -1
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|