diff --git a/src/Viewport.test.ts b/src/Viewport.test.ts index 5b106b43..4fa77ec0 100644 --- a/src/Viewport.test.ts +++ b/src/Viewport.test.ts @@ -2,11 +2,11 @@ import { assert } from 'chai'; import { Viewport } from './Viewport'; describe('Viewport', () => { - var terminal; - var viewportElement; - var charMeasureElement; - var viewport; - var scrollAreaElement; + let terminal; + let viewportElement; + let charMeasure; + let viewport; + let scrollAreaElement; const CHARACTER_HEIGHT = 10; @@ -34,21 +34,17 @@ describe('Viewport', () => { height: 0 } }; - charMeasureElement = { - getBoundingClientRect: () => { - return { width: null, height: CHARACTER_HEIGHT }; - } + charMeasure = { + height: CHARACTER_HEIGHT }; - viewport = new Viewport(terminal, viewportElement, scrollAreaElement, charMeasureElement); + viewport = new Viewport(terminal, viewportElement, scrollAreaElement, charMeasure); }); describe('refresh', () => { it('should set the line-height of the terminal', () => { assert.equal(viewportElement.style.lineHeight, CHARACTER_HEIGHT + 'px'); assert.equal(terminal.rowContainer.style.lineHeight, CHARACTER_HEIGHT + 'px'); - charMeasureElement.getBoundingClientRect = () => { - return { width: null, height: 1 }; - }; + charMeasure.height = 1; viewport.refresh(); assert.equal(viewportElement.style.lineHeight, '1px'); assert.equal(terminal.rowContainer.style.lineHeight, '1px'); @@ -59,9 +55,7 @@ describe('Viewport', () => { terminal.rows = 1; viewport.refresh(); assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px'); - charMeasureElement.getBoundingClientRect = () => { - return { width: null, height: 20 }; - }; + charMeasure.height = 20; viewport.refresh(); assert.equal(viewportElement.style.height, 20 + 'px'); }); diff --git a/src/Viewport.ts b/src/Viewport.ts index 3aa1319f..013ae65a 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -3,6 +3,7 @@ */ import { ITerminal } from './Interfaces'; +import { CharMeasure } from './utils/CharMeasure'; /** * Represents the viewport of a terminal, the visible area within the larger buffer of output. @@ -24,7 +25,7 @@ export class Viewport { private terminal: ITerminal, private viewportElement: HTMLElement, private scrollArea: HTMLElement, - private charMeasureElement: HTMLElement + private charMeasure: CharMeasure ) { this.currentRowHeight = 0; this.lastRecordedBufferLength = 0; @@ -43,21 +44,20 @@ export class Viewport { * @param charSize A character size measurement bounding rect object, if it doesn't exist it will * be created. */ - private refresh(charSize?: ClientRect): void { - var size = charSize || this.charMeasureElement.getBoundingClientRect(); - if (size.height > 0) { - var rowHeightChanged = size.height !== this.currentRowHeight; + private refresh(): void { + if (this.charMeasure.height > 0) { + var rowHeightChanged = this.charMeasure.height !== this.currentRowHeight; if (rowHeightChanged) { - this.currentRowHeight = size.height; - this.viewportElement.style.lineHeight = size.height + 'px'; - this.terminal.rowContainer.style.lineHeight = size.height + 'px'; + this.currentRowHeight = this.charMeasure.height; + this.viewportElement.style.lineHeight = this.charMeasure.height + 'px'; + this.terminal.rowContainer.style.lineHeight = this.charMeasure.height + 'px'; } var viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows; if (rowHeightChanged || viewportHeightChanged) { this.lastRecordedViewportHeight = this.terminal.rows; - this.viewportElement.style.height = size.height * this.terminal.rows + 'px'; + this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px'; } - this.scrollArea.style.height = (size.height * this.lastRecordedBufferLength) + 'px'; + this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px'; } } @@ -74,9 +74,8 @@ export class Viewport { this.refresh(); } else { // If size has changed, refresh viewport - var size = this.charMeasureElement.getBoundingClientRect(); - if (size.height !== this.currentRowHeight) { - this.refresh(size); + if (this.charMeasure.height !== this.currentRowHeight) { + this.refresh(); } } diff --git a/src/utils/CharMeasure.ts b/src/utils/CharMeasure.ts new file mode 100644 index 00000000..60684a2c --- /dev/null +++ b/src/utils/CharMeasure.ts @@ -0,0 +1,52 @@ +/** + * @module xterm/utils/CharMeasure + * @license MIT + */ + +import { EventEmitter } from '../EventEmitter.js'; + +/** + * Utility class that measures the size of a character. + */ +export class CharMeasure extends EventEmitter { + private _parentElement: HTMLElement; + private _measureElement: HTMLElement; + private _width: number; + private _height: number; + + constructor(parentElement: HTMLElement) { + super(); + this._parentElement = parentElement; + } + + public get width(): number { + return this._width; + } + + public get height(): number { + return this._height; + } + + public measure(): void { + const oldWidth = this._width; + const oldHeight = this._height; + + if (!this._measureElement) { + this._measureElement = document.createElement('span'); + this._measureElement.style.position = 'absolute'; + this._measureElement.style.top = '0'; + this._measureElement.style.left = '-9999em'; + this._measureElement.textContent = 'W'; + } + + this._parentElement.appendChild(this._measureElement); + const geometry = this._measureElement.getBoundingClientRect(); + this._width = geometry.width; + this._height = geometry.height; + this._parentElement.removeChild(this._measureElement); + + if (this._width !== oldWidth || this._height !== oldHeight) { + this.emit('charsizechanged'); + } + } +} diff --git a/src/xterm.css b/src/xterm.css index 4877f86e..3f4f4bbe 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -116,6 +116,10 @@ overflow-y: scroll; } +.terminal .xterm-wide-char { + display: inline-block; +} + .terminal .xterm-rows { position: absolute; left: 0; diff --git a/src/xterm.js b/src/xterm.js index cd304186..9ac656b5 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -15,6 +15,7 @@ import { EventEmitter } from './EventEmitter.js'; import { Viewport } from './Viewport.js'; import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard.js'; import { CircularList } from './utils/CircularList.js'; +import { CharMeasure } from './utils/CharMeasure.js'; import * as Browser from './utils/Browser'; import * as Keyboard from './utils/Keyboard'; @@ -567,17 +568,21 @@ Terminal.prototype.open = function(parent) { this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this); this.helperContainer.appendChild(this.compositionView); - this.charMeasureElement = document.createElement('div'); - this.charMeasureElement.classList.add('xterm-char-measure-element'); - this.charMeasureElement.innerHTML = 'W'; - this.helperContainer.appendChild(this.charMeasureElement); + this.charSizeStyleElement = document.createElement('style'); + this.helperContainer.appendChild(this.charSizeStyleElement); for (; i < this.rows; i++) { this.insertRow(); } this.parent.appendChild(this.element); - this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasureElement); + this.charMeasure = new CharMeasure(this.rowContainer); + this.charMeasure.on('charsizechanged', function () { + self.updateCharSizeCSS(); + }); + this.charMeasure.measure(); + + this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); // Setup loop that draws to screen this.queueRefresh(0, this.rows - 1); @@ -636,6 +641,13 @@ Terminal.loadAddon = function(addon, callback) { } }; +/** + * Updates the helper CSS class with any changes necessary after the terminal's + * character width has been changed. + */ +Terminal.prototype.updateCharSizeCSS = function() { + this.charSizeStyleElement.textContent = '.xterm-wide-char{width:' + (this.charMeasure.width * 2) + 'px;}'; +} /** * XTerm mouse events @@ -1182,6 +1194,9 @@ Terminal.prototype.refresh = function(start, end) { } } + if (ch_width === 2) { + out += ''; + } switch (ch) { case '&': out += '&'; @@ -1200,6 +1215,9 @@ Terminal.prototype.refresh = function(start, end) { } break; } + if (ch_width === 2) { + out += ''; + } attr = data; } @@ -2945,6 +2963,8 @@ Terminal.prototype.resize = function(x, y) { this.scrollTop = 0; this.scrollBottom = y - 1; + this.charMeasure.measure(); + this.queueRefresh(0, this.rows - 1); this.normal = null;