Merge pull request #3285 from Tyriar/dom_ligatures

Support for ligatures in DOM renderer
This commit is contained in:
Daniel Imms
2021-04-02 10:45:41 -07:00
committed by GitHub
10 changed files with 110 additions and 43 deletions
-1
View File
@@ -36,7 +36,6 @@
*/ */
.xterm { .xterm {
font-feature-settings: "liga" 0;
position: relative; position: relative;
user-select: none; user-select: none;
-ms-user-select: none; -ms-user-select: none;
+2 -1
View File
@@ -157,7 +157,8 @@ function createTerminal(): void {
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0; const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
term = new Terminal({ term = new Terminal({
windowsMode: isWindows windowsMode: isWindows,
fontFamily: 'Fira Code, courier-new, courier, monospace'
} as ITerminalOptions); } as ITerminalOptions);
// Load addons // Load addons
+1 -1
View File
@@ -555,7 +555,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
private _createRenderer(): IRenderer { private _createRenderer(): IRenderer {
switch (this.options.rendererType) { switch (this.options.rendererType) {
case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager!.colors, this.screenElement!, this.linkifier, this.linkifier2, this._instantiationService); case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager!.colors, this.screenElement!, this.linkifier, this.linkifier2);
case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier, this.linkifier2); case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier, this.linkifier2);
default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`);
} }
+14 -1
View File
@@ -5,7 +5,7 @@
import { IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm'; import { IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm';
import { IEvent, EventEmitter } from 'common/EventEmitter'; import { IEvent, EventEmitter } from 'common/EventEmitter';
import { ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler } from 'browser/Types'; import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler } from 'browser/Types';
import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types';
@@ -411,3 +411,16 @@ export class MockRenderService implements IRenderService {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
} }
export class MockCharacterJoinerService implements ICharacterJoinerService {
public serviceBrand: undefined;
public register(handler: (text: string) => [number, number][]): number {
return 0;
}
public deregister(joinerId: number): boolean {
return true;
}
public getJoinedCharacters(row: number): [number, number][] {
return [];
}
}
+1 -1
View File
@@ -33,7 +33,7 @@ export class Renderer extends Disposable implements IRenderer {
private readonly _screenElement: HTMLElement, private readonly _screenElement: HTMLElement,
linkifier: ILinkifier, linkifier: ILinkifier,
linkifier2: ILinkifier2, linkifier2: ILinkifier2,
instantiationService: IInstantiationService, @IInstantiationService instantiationService: IInstantiationService,
@IBufferService private readonly _bufferService: IBufferService, @IBufferService private readonly _bufferService: IBufferService,
@ICharSizeService private readonly _charSizeService: ICharSizeService, @ICharSizeService private readonly _charSizeService: ICharSizeService,
@IOptionsService private readonly _optionsService: IOptionsService @IOptionsService private readonly _optionsService: IOptionsService
+4 -3
View File
@@ -9,7 +9,7 @@ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { Disposable } from 'common/Lifecycle'; import { Disposable } from 'common/Lifecycle';
import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types'; import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types';
import { ICharSizeService } from 'browser/services/Services'; import { ICharSizeService } from 'browser/services/Services';
import { IOptionsService, IBufferService } from 'common/services/Services'; import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter'; import { EventEmitter, IEvent } from 'common/EventEmitter';
import { color } from 'browser/Color'; import { color } from 'browser/Color';
import { removeElementFromParent } from 'browser/Dom'; import { removeElementFromParent } from 'browser/Dom';
@@ -49,6 +49,7 @@ export class DomRenderer extends Disposable implements IRenderer {
private readonly _viewportElement: HTMLElement, private readonly _viewportElement: HTMLElement,
private readonly _linkifier: ILinkifier, private readonly _linkifier: ILinkifier,
private readonly _linkifier2: ILinkifier2, private readonly _linkifier2: ILinkifier2,
@IInstantiationService instantiationService: IInstantiationService,
@ICharSizeService private readonly _charSizeService: ICharSizeService, @ICharSizeService private readonly _charSizeService: ICharSizeService,
@IOptionsService private readonly _optionsService: IOptionsService, @IOptionsService private readonly _optionsService: IOptionsService,
@IBufferService private readonly _bufferService: IBufferService @IBufferService private readonly _bufferService: IBufferService
@@ -80,7 +81,7 @@ export class DomRenderer extends Disposable implements IRenderer {
this._updateDimensions(); this._updateDimensions();
this._injectCss(); this._injectCss();
this._rowFactory = new DomRendererRowFactory(document, this._optionsService, this._colors); this._rowFactory = instantiationService.createInstance(DomRendererRowFactory, document, this._colors);
this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass); this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
this._screenElement.appendChild(this._rowContainer); this._screenElement.appendChild(this._rowContainer);
@@ -364,7 +365,7 @@ export class DomRenderer extends Disposable implements IRenderer {
const row = y + this._bufferService.buffer.ydisp; const row = y + this._bufferService.buffer.ydisp;
const lineData = this._bufferService.buffer.lines.get(row); const lineData = this._bufferService.buffer.lines.get(row);
const cursorStyle = this._optionsService.options.cursorStyle; const cursorStyle = this._optionsService.options.cursorStyle;
rowElement.appendChild(this._rowFactory.createRow(lineData!, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols)); rowElement.appendChild(this._rowFactory.createRow(lineData!, row, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols));
} }
} }
@@ -12,6 +12,7 @@ import { IBufferLine } from 'common/Types';
import { CellData } from 'common/buffer/CellData'; import { CellData } from 'common/buffer/CellData';
import { MockOptionsService } from 'common/TestUtils.test'; import { MockOptionsService } from 'common/TestUtils.test';
import { css } from 'browser/Color'; import { css } from 'browser/Color';
import { MockCharacterJoinerService } from 'browser/TestUtils.test';
describe('DomRendererRowFactory', () => { describe('DomRendererRowFactory', () => {
let dom: jsdom.JSDOM; let dom: jsdom.JSDOM;
@@ -20,7 +21,7 @@ describe('DomRendererRowFactory', () => {
beforeEach(() => { beforeEach(() => {
dom = new jsdom.JSDOM(''); dom = new jsdom.JSDOM('');
rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }), { rowFactory = new DomRendererRowFactory(dom.window.document, {
background: css.toColor('#010101'), background: css.toColor('#010101'),
foreground: css.toColor('#020202'), foreground: css.toColor('#020202'),
ansi: [ ansi: [
@@ -43,13 +44,13 @@ describe('DomRendererRowFactory', () => {
css.toColor('#34e2e2'), css.toColor('#34e2e2'),
css.toColor('#eeeeec') css.toColor('#eeeeec')
] ]
} as any); } as any, new MockCharacterJoinerService(), new MockOptionsService({ drawBoldTextInBrightColors: true }));
lineData = createEmptyLineData(2); lineData = createEmptyLineData(2);
}); });
describe('createRow', () => { describe('createRow', () => {
it('should not create anything for an empty row', () => { it('should not create anything for an empty row', () => {
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
'' ''
); );
@@ -59,7 +60,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)])); lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)]));
// There should be no element for the following "empty" cell // There should be no element for the following "empty" cell
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '', 0, 0])); lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '', 0, 0]));
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
'<span style="width: 10px;">語</span>' '<span style="width: 10px;">語</span>'
); );
@@ -67,7 +68,7 @@ describe('DomRendererRowFactory', () => {
it('should add class for cursor and cursor style', () => { it('should add class for cursor and cursor style', () => {
for (const style of ['block', 'bar', 'underline']) { for (const style of ['block', 'bar', 'underline']) {
const fragment = rowFactory.createRow(lineData, true, style, 0, false, 5, 20); const fragment = rowFactory.createRow(lineData, 0, true, style, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
`<span class="xterm-cursor xterm-cursor-${style}"> </span>` `<span class="xterm-cursor xterm-cursor-${style}"> </span>`
); );
@@ -75,7 +76,7 @@ describe('DomRendererRowFactory', () => {
}); });
it('should add class for cursor blink', () => { it('should add class for cursor blink', () => {
const fragment = rowFactory.createRow(lineData, true, 'block', 0, true, 5, 20); const fragment = rowFactory.createRow(lineData, 0, true, 'block', 0, true, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
`<span class="xterm-cursor xterm-cursor-blink xterm-cursor-block"> </span>` `<span class="xterm-cursor xterm-cursor-blink xterm-cursor-block"> </span>`
); );
@@ -84,7 +85,7 @@ describe('DomRendererRowFactory', () => {
it('should not render cells that go beyond the terminal\'s columns', () => { it('should not render cells that go beyond the terminal\'s columns', () => {
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); 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(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 1); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 1);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
'<span>a</span>' '<span>a</span>'
); );
@@ -95,7 +96,7 @@ describe('DomRendererRowFactory', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.BOLD; cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.BOLD;
lineData.setCell(0, cell); lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
'<span class="xterm-bold">a</span>' '<span class="xterm-bold">a</span>'
); );
@@ -105,7 +106,7 @@ describe('DomRendererRowFactory', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.ITALIC; cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.ITALIC;
lineData.setCell(0, cell); lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
'<span class="xterm-italic">a</span>' '<span class="xterm-italic">a</span>'
); );
@@ -115,7 +116,7 @@ describe('DomRendererRowFactory', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.DIM; cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.DIM;
lineData.setCell(0, cell); lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
'<span class="xterm-dim">a</span>' '<span class="xterm-dim">a</span>'
); );
@@ -125,7 +126,7 @@ describe('DomRendererRowFactory', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE; cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE;
lineData.setCell(0, cell); lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
'<span class="xterm-underline">a</span>' '<span class="xterm-underline">a</span>'
); );
@@ -138,7 +139,7 @@ describe('DomRendererRowFactory', () => {
cell.fg &= ~Attributes.PCOLOR_MASK; cell.fg &= ~Attributes.PCOLOR_MASK;
cell.fg |= i; cell.fg |= i;
lineData.setCell(0, cell); lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
`<span class="xterm-fg-${i}">a</span>` `<span class="xterm-fg-${i}">a</span>`
); );
@@ -152,7 +153,7 @@ describe('DomRendererRowFactory', () => {
cell.bg &= ~Attributes.PCOLOR_MASK; cell.bg &= ~Attributes.PCOLOR_MASK;
cell.bg |= i; cell.bg |= i;
lineData.setCell(0, cell); lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
`<span class="xterm-bg-${i}">a</span>` `<span class="xterm-bg-${i}">a</span>`
); );
@@ -164,7 +165,7 @@ describe('DomRendererRowFactory', () => {
cell.fg |= Attributes.CM_P16 | 2 | FgFlags.INVERSE; cell.fg |= Attributes.CM_P16 | 2 | FgFlags.INVERSE;
cell.bg |= Attributes.CM_P16 | 1; cell.bg |= Attributes.CM_P16 | 1;
lineData.setCell(0, cell); lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
'<span class="xterm-fg-1 xterm-bg-2">a</span>' '<span class="xterm-fg-1 xterm-bg-2">a</span>'
); );
@@ -175,7 +176,7 @@ describe('DomRendererRowFactory', () => {
cell.fg |= FgFlags.INVERSE; cell.fg |= FgFlags.INVERSE;
cell.bg |= Attributes.CM_P16 | 1; cell.bg |= Attributes.CM_P16 | 1;
lineData.setCell(0, cell); lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
'<span class="xterm-fg-1 xterm-bg-257">a</span>' '<span class="xterm-fg-1 xterm-bg-257">a</span>'
); );
@@ -185,7 +186,7 @@ describe('DomRendererRowFactory', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg |= Attributes.CM_P16 | 1 | FgFlags.INVERSE; cell.fg |= Attributes.CM_P16 | 1 | FgFlags.INVERSE;
lineData.setCell(0, cell); lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
'<span class="xterm-fg-257 xterm-bg-1">a</span>' '<span class="xterm-fg-257 xterm-bg-1">a</span>'
); );
@@ -198,7 +199,7 @@ describe('DomRendererRowFactory', () => {
cell.fg &= ~Attributes.PCOLOR_MASK; cell.fg &= ~Attributes.PCOLOR_MASK;
cell.fg |= i; cell.fg |= i;
lineData.setCell(0, cell); lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
`<span class="xterm-bold xterm-fg-${i + 8}">a</span>` `<span class="xterm-bold xterm-fg-${i + 8}">a</span>`
); );
@@ -210,7 +211,7 @@ describe('DomRendererRowFactory', () => {
cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3; cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3;
cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6; cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6;
lineData.setCell(0, cell); lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
'<span style="color:#010203;background-color:#040506;">a</span>' '<span style="color:#010203;background-color:#040506;">a</span>'
); );
@@ -221,7 +222,7 @@ describe('DomRendererRowFactory', () => {
cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3 | FgFlags.INVERSE; cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3 | FgFlags.INVERSE;
cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6; cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6;
lineData.setCell(0, cell); lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment), assert.equal(getFragmentHtml(fragment),
'<span style="color:#040506;background-color:#010203;">a</span>' '<span style="color:#040506;background-color:#010203;">a</span>'
); );
@@ -10,6 +10,8 @@ import { CellData } from 'common/buffer/CellData';
import { IOptionsService } from 'common/services/Services'; import { IOptionsService } from 'common/services/Services';
import { color, rgba } from 'browser/Color'; import { color, rgba } from 'browser/Color';
import { IColorSet, IColor } from 'browser/Types'; import { IColorSet, IColor } from 'browser/Types';
import { ICharacterJoinerService } from 'browser/services/Services';
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
export const BOLD_CLASS = 'xterm-bold'; export const BOLD_CLASS = 'xterm-bold';
export const DIM_CLASS = 'xterm-dim'; export const DIM_CLASS = 'xterm-dim';
@@ -26,8 +28,9 @@ export class DomRendererRowFactory {
constructor( constructor(
private readonly _document: Document, private readonly _document: Document,
private readonly _optionsService: IOptionsService, private _colors: IColorSet,
private _colors: IColorSet @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService,
@IOptionsService private readonly _optionsService: IOptionsService
) { ) {
} }
@@ -35,9 +38,11 @@ export class DomRendererRowFactory {
this._colors = colors; this._colors = colors;
} }
public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment { public createRow(lineData: IBufferLine, row: number, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment {
const fragment = this._document.createDocumentFragment(); const fragment = this._document.createDocumentFragment();
const joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
console.log('joinedRanges', joinedRanges.map(e => e[0] + '->' + e[1]).join(','));
// Find the line length first, this prevents the need to output a bunch of // 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 // 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 // loop below because of the colCount feature (which can be removed after we
@@ -53,18 +58,59 @@ export class DomRendererRowFactory {
for (let x = 0; x < lineLength; x++) { for (let x = 0; x < lineLength; x++) {
lineData.loadCell(x, this._workCell); lineData.loadCell(x, this._workCell);
const width = this._workCell.getWidth(); let width = this._workCell.getWidth();
// The character to the left is a wide character, drawing is owned by the char at x-1 // The character to the left is a wide character, drawing is owned by the char at x-1
if (width === 0) { if (width === 0) {
continue; continue;
} }
// If true, indicates that the current character(s) to draw were joined.
let isJoined = false;
let lastCharX = x;
// Process any joined character ranges as needed. Because of how the
// ranges are produced, we know that they are valid for the characters
// and attributes of our input.
let cell = this._workCell;
if (joinedRanges.length > 0 && x === joinedRanges[0][0]) {
isJoined = true;
const range = joinedRanges.shift()!;
// We already know the exact start and end column of the joined range,
// so we get the string and width representing it directly
cell = new JoinedCellData(
this._workCell,
lineData.translateToString(true, range[0], range[1]),
range[1] - range[0]
);
// Skip over the cells occupied by this range in the loop
lastCharX = range[1] - 1;
// Recalculate width
width = cell.getWidth();
}
const charElement = this._document.createElement('span'); const charElement = this._document.createElement('span');
if (width > 1) { if (width > 1) {
charElement.style.width = `${cellWidth * width}px`; charElement.style.width = `${cellWidth * width}px`;
} }
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
// joined. The workaround here is to show a cursor around the whole ligature so it shows up,
// the cursor looks the same when on any character of the ligature though
if (cursorX >= x && cursorX <= lastCharX) {
cursorX = x;
}
}
if (isCursorRow && x === cursorX) { if (isCursorRow && x === cursorX) {
charElement.classList.add(CURSOR_CLASS); charElement.classList.add(CURSOR_CLASS);
@@ -85,33 +131,33 @@ export class DomRendererRowFactory {
} }
} }
if (this._workCell.isBold()) { if (cell.isBold()) {
charElement.classList.add(BOLD_CLASS); charElement.classList.add(BOLD_CLASS);
} }
if (this._workCell.isItalic()) { if (cell.isItalic()) {
charElement.classList.add(ITALIC_CLASS); charElement.classList.add(ITALIC_CLASS);
} }
if (this._workCell.isDim()) { if (cell.isDim()) {
charElement.classList.add(DIM_CLASS); charElement.classList.add(DIM_CLASS);
} }
if (this._workCell.isUnderline()) { if (cell.isUnderline()) {
charElement.classList.add(UNDERLINE_CLASS); charElement.classList.add(UNDERLINE_CLASS);
} }
if (this._workCell.isInvisible()) { if (cell.isInvisible()) {
charElement.textContent = WHITESPACE_CELL_CHAR; charElement.textContent = WHITESPACE_CELL_CHAR;
} else { } else {
charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR; charElement.textContent = cell.getChars() || WHITESPACE_CELL_CHAR;
} }
let fg = this._workCell.getFgColor(); let fg = cell.getFgColor();
let fgColorMode = this._workCell.getFgColorMode(); let fgColorMode = cell.getFgColorMode();
let bg = this._workCell.getBgColor(); let bg = cell.getBgColor();
let bgColorMode = this._workCell.getBgColorMode(); let bgColorMode = cell.getBgColorMode();
const isInverse = !!this._workCell.isInverse(); const isInverse = !!cell.isInverse();
if (isInverse) { if (isInverse) {
const temp = fg; const temp = fg;
fg = bg; fg = bg;
@@ -125,7 +171,7 @@ export class DomRendererRowFactory {
switch (fgColorMode) { switch (fgColorMode) {
case Attributes.CM_P16: case Attributes.CM_P16:
case Attributes.CM_P256: case Attributes.CM_P256:
if (this._workCell.isBold() && fg < 8 && this._optionsService.options.drawBoldTextInBrightColors) { if (cell.isBold() && fg < 8 && this._optionsService.options.drawBoldTextInBrightColors) {
fg += 8; fg += 8;
} }
if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg])) { if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg])) {
@@ -168,6 +214,8 @@ export class DomRendererRowFactory {
} }
fragment.appendChild(charElement); fragment.appendChild(charElement);
x = lastCharX;
} }
return fragment; return fragment;
} }
@@ -42,6 +42,8 @@ export class ServiceCollection {
} }
export class InstantiationService implements IInstantiationService { export class InstantiationService implements IInstantiationService {
public serviceBrand: undefined;
private readonly _services: ServiceCollection = new ServiceCollection(); private readonly _services: ServiceCollection = new ServiceCollection();
constructor() { constructor() {
+2
View File
@@ -157,6 +157,8 @@ type GetLeadingNonServiceArgs<Args> =
export const IInstantiationService = createDecorator<IInstantiationService>('InstantiationService'); export const IInstantiationService = createDecorator<IInstantiationService>('InstantiationService');
export interface IInstantiationService { export interface IInstantiationService {
serviceBrand: undefined;
setService<T>(id: IServiceIdentifier<T>, instance: T): void; setService<T>(id: IServiceIdentifier<T>, instance: T): void;
getService<T>(id: IServiceIdentifier<T>): T | undefined; getService<T>(id: IServiceIdentifier<T>): T | undefined;
createInstance<Ctor extends new (...args: any[]) => any, R extends InstanceType<Ctor>>(t: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R; createInstance<Ctor extends new (...args: any[]) => any, R extends InstanceType<Ctor>>(t: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;