Comparing version 5.3.0-beta.20 to 5.3.0-beta.21
{ | ||
"name": "xterm", | ||
"description": "Full xterm terminal, in your browser", | ||
"version": "5.3.0-beta.20", | ||
"version": "5.3.0-beta.21", | ||
"main": "lib/xterm.js", | ||
@@ -6,0 +6,0 @@ "style": "css/xterm.css", |
@@ -6,3 +6,4 @@ /** | ||
import { BOLD_CLASS, CURSOR_BLINK_CLASS, CURSOR_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DIM_CLASS, DomRendererRowFactory, ITALIC_CLASS } from 'browser/renderer/dom/DomRendererRowFactory'; | ||
import { DomRendererRowFactory, RowCss } from 'browser/renderer/dom/DomRendererRowFactory'; | ||
import { WidthCache } from 'browser/renderer/dom/WidthCache'; | ||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; | ||
@@ -19,2 +20,3 @@ import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils'; | ||
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; | ||
@@ -29,2 +31,3 @@ const ROW_CONTAINER_CLASS = 'xterm-rows'; | ||
/** | ||
@@ -44,3 +47,3 @@ * A fallback renderer for when canvas is slow. This is not meant to be | ||
private _selectionContainer: HTMLElement; | ||
private _cellToRowElements: Int16Array[] = []; | ||
private _widthCache: WidthCache; | ||
@@ -98,3 +101,13 @@ public dimensions: IRenderDimensions; | ||
this._dimensionsStyle.dispose(); | ||
this._widthCache.dispose(); | ||
})); | ||
this._widthCache = new WidthCache(document); | ||
this._widthCache.setFont( | ||
this._optionsService.rawOptions.fontFamily, | ||
this._optionsService.rawOptions.fontSize, | ||
this._optionsService.rawOptions.fontWeight, | ||
this._optionsService.rawOptions.fontWeightBold | ||
); | ||
this._setDefaultSpacing(); | ||
} | ||
@@ -131,6 +144,5 @@ | ||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` + | ||
` display: inline-block;` + | ||
` display: inline-block;` + // TODO: find workaround for inline-block (creates ~20% render penalty) | ||
` height: 100%;` + | ||
` vertical-align: top;` + | ||
` width: ${this.dimensions.css.cell.width}px` + | ||
`}`; | ||
@@ -156,2 +168,4 @@ | ||
` font-size: ${this._optionsService.rawOptions.fontSize}px;` + | ||
` font-kerning: none;` + | ||
` white-space: pre` + | ||
`}`; | ||
@@ -164,9 +178,9 @@ styles += | ||
styles += | ||
`${this._terminalSelector} span:not(.${BOLD_CLASS}) {` + | ||
`${this._terminalSelector} span:not(.${RowCss.BOLD_CLASS}) {` + | ||
` font-weight: ${this._optionsService.rawOptions.fontWeight};` + | ||
`}` + | ||
`${this._terminalSelector} span.${BOLD_CLASS} {` + | ||
`${this._terminalSelector} span.${RowCss.BOLD_CLASS} {` + | ||
` font-weight: ${this._optionsService.rawOptions.fontWeightBold};` + | ||
`}` + | ||
`${this._terminalSelector} span.${ITALIC_CLASS} {` + | ||
`${this._terminalSelector} span.${RowCss.ITALIC_CLASS} {` + | ||
` font-style: italic;` + | ||
@@ -188,3 +202,3 @@ `}`; | ||
` 50% {` + | ||
` background-color: ${colors.cursorAccent.css};` + | ||
` background-color: inherit;` + | ||
` color: ${colors.cursor.css};` + | ||
@@ -195,5 +209,5 @@ ` }` + | ||
styles += | ||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} ,` + | ||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} ,` + | ||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} ` + | ||
`${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} ` + | ||
`{` + | ||
@@ -203,16 +217,16 @@ ` outline: 1px solid ${colors.cursor.css};` + | ||
`}` + | ||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}:not(.${CURSOR_STYLE_BLOCK_CLASS}) {` + | ||
`${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} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` + | ||
`${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} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` + | ||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + | ||
` background-color: ${colors.cursor.css};` + | ||
` color: ${colors.cursorAccent.css};` + | ||
`}` + | ||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` + | ||
`${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} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` + | ||
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} {` + | ||
` border-bottom: 1px ${colors.cursor.css};` + | ||
@@ -243,3 +257,3 @@ ` border-bottom-style: solid;` + | ||
`${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` + | ||
`${this._terminalSelector} .${FG_CLASS_PREFIX}${i}.${DIM_CLASS} { color: ${color.multiplyOpacity(c, 0.5).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}; }`; | ||
@@ -249,3 +263,3 @@ } | ||
`${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(colors.background).css}; }` + | ||
`${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR}.${DIM_CLASS} { color: ${color.multiplyOpacity(color.opaque(colors.background), 0.5).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}; }`; | ||
@@ -256,4 +270,21 @@ | ||
/** | ||
* 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(); | ||
} | ||
@@ -281,2 +312,4 @@ | ||
this._updateDimensions(); | ||
this._widthCache.clear(); | ||
this._setDefaultSpacing(); | ||
} | ||
@@ -294,6 +327,3 @@ | ||
// Remove all selections | ||
while (this._selectionContainer.children.length) { | ||
this._selectionContainer.removeChild(this._selectionContainer.children[0]); | ||
} | ||
this._selectionContainer.replaceChildren(); | ||
this._rowFactory.handleSelectionChanged(start, end, columnSelectMode); | ||
@@ -368,2 +398,10 @@ this.renderRows(0, this._bufferService.rows - 1); | ||
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(); | ||
} | ||
@@ -385,15 +423,29 @@ | ||
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 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; | ||
for (let y = start; y <= end; y++) { | ||
const row = y + buffer.ydisp; | ||
const rowElement = this._rowElements[y]; | ||
const row = y + this._bufferService.buffer.ydisp; | ||
const lineData = this._bufferService.buffer.lines.get(row); | ||
const cursorStyle = this._optionsService.rawOptions.cursorStyle; | ||
if (!this._cellToRowElements[y] || this._cellToRowElements[y].length !== this._bufferService.cols) { | ||
this._cellToRowElements[y] = new Int16Array(this._bufferService.cols); | ||
const lineData = buffer.lines.get(row); | ||
if (!rowElement || !lineData) { | ||
break; | ||
} | ||
rowElement.replaceChildren(this._rowFactory.createRow(lineData!, row, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.css.cell.width, this._bufferService.cols, this._cellToRowElements[y])); | ||
rowElement.replaceChildren( | ||
...this._rowFactory.createRow( | ||
lineData, | ||
row, | ||
row === cursorAbsoluteY, | ||
cursorStyle, | ||
cursorX, | ||
cursorBlink, | ||
this.dimensions.css.cell.width, | ||
this._widthCache, | ||
-1, | ||
-1 | ||
) | ||
); | ||
} | ||
@@ -430,38 +482,40 @@ } | ||
*/ | ||
// clip coords into viewport | ||
if (y < 0) x = 0; | ||
if (y2 < 0) x2 = 0; | ||
// avoid out-of-sync y-values, simply clamp into valid area | ||
const maxY = this._cellToRowElements.length - 1; | ||
const maxY = this._bufferService.rows - 1; | ||
y = Math.max(Math.min(y, maxY), 0); | ||
y2 = Math.max(Math.min(y2, maxY), 0); | ||
const elemY = this._cellToRowElements[y]; | ||
const elemY2 = this._cellToRowElements[y2]; | ||
if (x >= elemY.length || x2 >= elemY2.length) { | ||
// avoid out-of-sync x-values | ||
// simply exit early, gets fixed by the next render update | ||
return; | ||
} | ||
x = elemY[x]; | ||
x2 = elemY2[x2]; | ||
if (x === -1 || x2 === -1) { | ||
return; | ||
} | ||
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; | ||
while (x !== x2 || y !== y2) { | ||
const row = this._rowElements[y]; | ||
if (!row) { | ||
return; | ||
// 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; | ||
} | ||
const span = row.children[x] as HTMLElement; | ||
if (span) { | ||
span.style.textDecoration = enabled ? 'underline' : 'none'; | ||
} | ||
if (++x >= cols) { | ||
x = 0; | ||
y++; | ||
} | ||
rowElement.replaceChildren( | ||
...this._rowFactory.createRow( | ||
bufferline, | ||
row, | ||
row === cursorAbsoluteY, | ||
cursorStyle, | ||
cursorX, | ||
cursorBlink, | ||
this.dimensions.css.cell.width, | ||
this._widthCache, | ||
enabled ? (i === y ? x : 0) : -1, | ||
enabled ? ((i === y2 ? x2 : cols) - 1) : -1 | ||
) | ||
); | ||
} | ||
} | ||
} |
/** | ||
* Copyright (c) 2018 The xterm.js authors. All rights reserved. | ||
* Copyright (c) 2018, 2023 The xterm.js authors. All rights reserved. | ||
* @license MIT | ||
@@ -8,3 +8,3 @@ */ | ||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; | ||
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; | ||
import { WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; | ||
import { CellData } from 'common/buffer/CellData'; | ||
@@ -17,15 +17,20 @@ import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; | ||
import { AttributeData } from 'common/buffer/AttributeData'; | ||
import { WidthCache } from 'browser/renderer/dom/WidthCache'; | ||
export const BOLD_CLASS = 'xterm-bold'; | ||
export const DIM_CLASS = 'xterm-dim'; | ||
export const ITALIC_CLASS = 'xterm-italic'; | ||
export const UNDERLINE_CLASS = 'xterm-underline'; | ||
export const OVERLINE_CLASS = 'xterm-overline'; | ||
export const STRIKETHROUGH_CLASS = 'xterm-strikethrough'; | ||
export const CURSOR_CLASS = 'xterm-cursor'; | ||
export const CURSOR_BLINK_CLASS = 'xterm-cursor-blink'; | ||
export const CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block'; | ||
export const CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar'; | ||
export const CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline'; | ||
export const enum RowCss { | ||
BOLD_CLASS = 'xterm-bold', | ||
DIM_CLASS = 'xterm-dim', | ||
ITALIC_CLASS = 'xterm-italic', | ||
UNDERLINE_CLASS = 'xterm-underline', | ||
OVERLINE_CLASS = 'xterm-overline', | ||
STRIKETHROUGH_CLASS = 'xterm-strikethrough', | ||
CURSOR_CLASS = 'xterm-cursor', | ||
CURSOR_BLINK_CLASS = 'xterm-cursor-blink', | ||
CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block', | ||
CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar', | ||
CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline' | ||
} | ||
export class DomRendererRowFactory { | ||
@@ -38,2 +43,4 @@ private _workCell: CellData = new CellData(); | ||
public defaultSpacing = 0; | ||
constructor( | ||
@@ -55,28 +62,38 @@ private readonly _document: Document, | ||
public createRow(lineData: IBufferLine, row: number, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number, cellMap: Int16Array): DocumentFragment { | ||
// NOTE: `cellMap` maps cell positions to a span element index in a row. | ||
// All positions should be updated, even skipped ones after wide chars or left overs at the end, | ||
// otherwise the mouse hover logic might mark the wrong elements as underlined. | ||
public createRow( | ||
lineData: IBufferLine, | ||
row: number, | ||
isCursorRow: boolean, | ||
cursorStyle: string | undefined, | ||
cursorX: number, | ||
cursorBlink: boolean, | ||
cellWidth: number, | ||
widthCache: WidthCache, | ||
linkStart: number, | ||
linkEnd: number | ||
): HTMLSpanElement[] { | ||
const fragment = this._document.createDocumentFragment(); | ||
const elements: HTMLSpanElement[] = []; | ||
const joinedRanges = this._characterJoinerService.getJoinedCharacters(row); | ||
const colors = this._themeService.colors; | ||
const joinedRanges = this._characterJoinerService.getJoinedCharacters(row); | ||
// Find the line length first, this prevents the need to output a bunch of | ||
// empty cells at the end. This cannot easily be integrated into the main | ||
// loop below because of the colCount feature (which can be removed after we | ||
// properly support reflow and disallow data to go beyond the right-side of | ||
// the viewport). | ||
let lineLength = 0; | ||
for (let x = Math.min(lineData.length, cols) - 1; x >= 0; x--) { | ||
if (lineData.loadCell(x, this._workCell).getCode() !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) { | ||
lineLength = x + 1; | ||
break; | ||
} | ||
let lineLength = lineData.getNoBgTrimmedLength(); | ||
if (isCursorRow && lineLength < cursorX + 1) { | ||
lineLength = cursorX + 1; | ||
} | ||
const colors = this._themeService.colors; | ||
let elemIndex = -1; | ||
let charElement: HTMLSpanElement | undefined; | ||
let cellAmount = 0; | ||
let text = ''; | ||
let oldBg = 0; | ||
let oldFg = 0; | ||
let oldExt = 0; | ||
let oldLinkHover: number | boolean = false; | ||
let oldSpacing = 0; | ||
let spacing = 0; | ||
const classes: string[] = []; | ||
let x = 0; | ||
for (; x < lineLength; x++) { | ||
const hasHover = linkStart !== -1 && linkEnd !== -1; | ||
for (let x = 0; x < lineLength; x++) { | ||
lineData.loadCell(x, this._workCell); | ||
@@ -86,5 +103,3 @@ let width = this._workCell.getWidth(); | ||
// The character to the left is a wide character, drawing is owned by the char at x-1 | ||
// still have to update cellMap with current element index | ||
if (width === 0) { | ||
cellMap[x] = elemIndex; | ||
continue; | ||
@@ -120,12 +135,62 @@ } | ||
const charElement = this._document.createElement('span'); | ||
if (width > 1) { | ||
charElement.style.width = `${cellWidth * width}px`; | ||
const isInSelection = this._isCellInSelection(x, row); | ||
const isCursorCell = isCursorRow && x === cursorX; | ||
const isLinkHover = hasHover && x >= linkStart && x <= linkEnd; | ||
// get chars to render for this cell | ||
let chars = cell.getChars() || WHITESPACE_CELL_CHAR; | ||
if (chars === ' ' && (cell.isUnderline() || cell.isOverline())) { | ||
chars = '\xa0'; | ||
} | ||
// lookup char render width and calc spacing | ||
spacing = width * cellWidth - widthCache.get(chars, cell.isBold(), cell.isItalic()); | ||
if (!charElement) { | ||
charElement = this._document.createElement('span'); | ||
} else { | ||
/** | ||
* 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 | ||
* - 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 | ||
&& isLinkHover === oldLinkHover | ||
&& spacing === oldSpacing | ||
&& !isCursorCell | ||
&& !isJoined | ||
) { | ||
// no span alterations, thus only account chars skipping all code below | ||
text += chars; | ||
cellAmount++; | ||
continue; | ||
} else { | ||
/** | ||
* cannot merge: | ||
* - apply left-over text to old span | ||
* - create new span, reset state holders cellAmount & text | ||
*/ | ||
if (cellAmount) { | ||
charElement.textContent = text; | ||
} | ||
charElement = this._document.createElement('span'); | ||
cellAmount = 0; | ||
text = ''; | ||
} | ||
} | ||
// preserve conditions for next merger eval round | ||
oldBg = cell.bg; | ||
oldFg = cell.fg; | ||
oldExt = cell.extended.ext; | ||
oldLinkHover = isLinkHover; | ||
oldSpacing = spacing; | ||
if (isJoined) { | ||
// Ligatures in the DOM renderer must use display inline, as they may not show with | ||
// inline-block if they are outside the bounds of the element | ||
charElement.style.display = 'inline'; | ||
// The DOM renderer colors the background of the cursor but for ligatures all cells are | ||
@@ -139,44 +204,38 @@ // joined. The workaround here is to show a cursor around the whole ligature so it shows up, | ||
if (!this._coreService.isCursorHidden && isCursorRow && x === cursorX) { | ||
charElement.classList.add(CURSOR_CLASS); | ||
if (!this._coreService.isCursorHidden && isCursorCell) { | ||
classes.push(RowCss.CURSOR_CLASS); | ||
if (cursorBlink) { | ||
charElement.classList.add(CURSOR_BLINK_CLASS); | ||
classes.push(RowCss.CURSOR_BLINK_CLASS); | ||
} | ||
switch (cursorStyle) { | ||
case 'bar': | ||
charElement.classList.add(CURSOR_STYLE_BAR_CLASS); | ||
break; | ||
case 'underline': | ||
charElement.classList.add(CURSOR_STYLE_UNDERLINE_CLASS); | ||
break; | ||
default: | ||
charElement.classList.add(CURSOR_STYLE_BLOCK_CLASS); | ||
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()) { | ||
charElement.classList.add(BOLD_CLASS); | ||
classes.push(RowCss.BOLD_CLASS); | ||
} | ||
if (cell.isItalic()) { | ||
charElement.classList.add(ITALIC_CLASS); | ||
classes.push(RowCss.ITALIC_CLASS); | ||
} | ||
if (cell.isDim()) { | ||
charElement.classList.add(DIM_CLASS); | ||
classes.push(RowCss.DIM_CLASS); | ||
} | ||
if (cell.isInvisible()) { | ||
charElement.textContent = WHITESPACE_CELL_CHAR; | ||
text = WHITESPACE_CELL_CHAR; | ||
} else { | ||
charElement.textContent = cell.getChars() || WHITESPACE_CELL_CHAR; | ||
text = cell.getChars() || WHITESPACE_CELL_CHAR; | ||
} | ||
if (cell.isUnderline()) { | ||
charElement.classList.add(`${UNDERLINE_CLASS}-${cell.extended.underlineStyle}`); | ||
if (charElement.textContent === ' ') { | ||
charElement.textContent = '\xa0'; // = | ||
classes.push(`${RowCss.UNDERLINE_CLASS}-${cell.extended.underlineStyle}`); | ||
if (text === ' ') { | ||
text = '\xa0'; // = | ||
} | ||
@@ -197,5 +256,5 @@ if (!cell.isUnderlineColorDefault()) { | ||
if (cell.isOverline()) { | ||
charElement.classList.add(OVERLINE_CLASS); | ||
if (charElement.textContent === ' ') { | ||
charElement.textContent = '\xa0'; // = | ||
classes.push(RowCss.OVERLINE_CLASS); | ||
if (text === ' ') { | ||
text = '\xa0'; // = | ||
} | ||
@@ -205,5 +264,10 @@ } | ||
if (cell.isStrikethrough()) { | ||
charElement.classList.add(STRIKETHROUGH_CLASS); | ||
classes.push(RowCss.STRIKETHROUGH_CLASS); | ||
} | ||
// apply link hover underline late, effectively overrides any previous text-decoration settings | ||
if (isLinkHover) { | ||
charElement.style.textDecoration = 'underline'; | ||
} | ||
let fg = cell.getFgColor(); | ||
@@ -246,3 +310,2 @@ let fgColorMode = cell.getFgColorMode(); | ||
// Apply selection foreground if applicable | ||
const isInSelection = this._isCellInSelection(x, row); | ||
if (!isTop) { | ||
@@ -265,3 +328,3 @@ if (colors.selectionForeground && isInSelection) { | ||
if (isTop) { | ||
charElement.classList.add(`xterm-decoration-top`); | ||
classes.push('xterm-decoration-top'); | ||
} | ||
@@ -275,3 +338,3 @@ | ||
resolvedBg = colors.ansi[bg]; | ||
charElement.classList.add(`xterm-bg-${bg}`); | ||
classes.push(`xterm-bg-${bg}`); | ||
break; | ||
@@ -286,3 +349,3 @@ case Attributes.CM_RGB: | ||
resolvedBg = colors.foreground; | ||
charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`); | ||
classes.push(`xterm-bg-${INVERTED_DEFAULT_COLOR}`); | ||
} else { | ||
@@ -308,3 +371,3 @@ resolvedBg = colors.background; | ||
if (!this._applyMinimumContrast(charElement, resolvedBg, colors.ansi[fg], cell, bgOverride, undefined)) { | ||
charElement.classList.add(`xterm-fg-${fg}`); | ||
classes.push(`xterm-fg-${fg}`); | ||
} | ||
@@ -326,3 +389,3 @@ break; | ||
if (isInverse) { | ||
charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`); | ||
classes.push(`xterm-fg-${INVERTED_DEFAULT_COLOR}`); | ||
} | ||
@@ -332,15 +395,31 @@ } | ||
fragment.appendChild(charElement); | ||
cellMap[x] = ++elemIndex; | ||
// apply CSS classes | ||
// slightly faster than using classList by omitting | ||
// checks for doubled entries (code above should not have doublets) | ||
if (classes.length) { | ||
charElement.className = classes.join(' '); | ||
classes.length = 0; | ||
} | ||
// exclude conditions for cell merging - never merge these | ||
if (!isCursorCell && !isInSelection && !isJoined) { | ||
cellAmount++; | ||
} else { | ||
charElement.textContent = text; | ||
} | ||
// apply letter-spacing rule | ||
if (spacing !== this.defaultSpacing) { | ||
charElement.style.letterSpacing = `${spacing}px`; | ||
} | ||
elements.push(charElement); | ||
x = lastCharX; | ||
} | ||
// since the loop above might exit early not handling all cells, | ||
// also set remaining cell positions to last element index | ||
if (x < cols - 1) { | ||
cellMap.subarray(x).fill(++elemIndex); | ||
// postfix text of last merged span | ||
if (charElement && cellAmount) { | ||
charElement.textContent = text; | ||
} | ||
return fragment; | ||
return elements; | ||
} | ||
@@ -347,0 +426,0 @@ |
@@ -10,4 +10,9 @@ /** | ||
import { Disposable } from 'common/Lifecycle'; | ||
import { ITerminalOptions } from 'common/Types'; | ||
const enum MeasureSettings { | ||
REPEAT = 32 | ||
} | ||
export class CharSizeService extends Disposable implements ICharSizeService { | ||
@@ -71,4 +76,6 @@ public serviceBrand: undefined; | ||
this._measureElement.classList.add('xterm-char-measure-element'); | ||
this._measureElement.textContent = 'W'; | ||
this._measureElement.textContent = 'W'.repeat(MeasureSettings.REPEAT); | ||
this._measureElement.setAttribute('aria-hidden', 'true'); | ||
this._measureElement.style.whiteSpace = 'pre'; | ||
this._measureElement.style.fontKerning = 'none'; | ||
this._parentElement.appendChild(this._measureElement); | ||
@@ -90,3 +97,3 @@ } | ||
if (geometry.width !== 0 && geometry.height !== 0) { | ||
this._result.width = geometry.width; | ||
this._result.width = geometry.width / MeasureSettings.REPEAT; | ||
this._result.height = Math.ceil(geometry.height); | ||
@@ -93,0 +100,0 @@ } |
@@ -8,3 +8,3 @@ /** | ||
import { stringFromCodePoint } from 'common/input/TextDecoder'; | ||
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content, BgFlags, FgFlags } from 'common/buffer/Constants'; | ||
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content, BgFlags, FgFlags, Attributes } from 'common/buffer/Constants'; | ||
import { CellData } from 'common/buffer/CellData'; | ||
@@ -467,2 +467,11 @@ import { AttributeData, ExtendedAttrs } from 'common/buffer/AttributeData'; | ||
public getNoBgTrimmedLength(): number { | ||
for (let i = this.length - 1; i >= 0; --i) { | ||
if ((this._data[i * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK) || (this._data[i * CELL_SIZE + Cell.BG] & Attributes.CM_MASK)) { | ||
return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT); | ||
} | ||
} | ||
return 0; | ||
} | ||
public copyCellsFrom(src: BufferLine, srcCol: number, destCol: number, length: number, applyInReverse: boolean): void { | ||
@@ -469,0 +478,0 @@ const srcData = src._data; |
@@ -245,2 +245,3 @@ /** | ||
getTrimmedLength(): number; | ||
getNoBgTrimmedLength(): number; | ||
translateToString(trimRight?: boolean, startCol?: number, endCol?: number): string; | ||
@@ -247,0 +248,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2332511
111
24366