From 188e197e20d3c678fc6f5d478e17699caea01128 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 31 Dec 2016 15:19:54 -0800 Subject: [PATCH 1/4] Wrap wide chars in a span/class Part of #439 --- src/xterm.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/xterm.js b/src/xterm.js index 1d220652..fcb60bf5 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -1182,6 +1182,9 @@ Terminal.prototype.refresh = function(start, end, queue) { } } + if (ch_width === 2) { + out += ''; + } switch (ch) { case '&': out += '&'; @@ -1200,6 +1203,9 @@ Terminal.prototype.refresh = function(start, end, queue) { } break; } + if (ch_width === 2) { + out += ''; + } attr = data; } From 4f18d842f9ee355387628b9f9f4cbb1b0ea02e9a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 31 Dec 2016 15:46:17 -0800 Subject: [PATCH 2/4] Add CharMeasure util class --- src/utils/CharMeasure.ts | 52 ++++++++++++++++++++++++++++++++++++++++ src/xterm.js | 6 +++++ 2 files changed, 58 insertions(+) create mode 100644 src/utils/CharMeasure.ts 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.js b/src/xterm.js index fcb60bf5..826531e2 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'; @@ -583,6 +584,9 @@ Terminal.prototype.open = function(parent) { } this.parent.appendChild(this.element); + this.charMeasure = new CharMeasure(this.rowContainer); + this.charMeasure.measure(); + this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasureElement); // Draw the screen. @@ -2951,6 +2955,8 @@ Terminal.prototype.resize = function(x, y) { this.scrollTop = 0; this.scrollBottom = y - 1; + this.charMeasure.measure(); + this.refresh(0, this.rows - 1); this.normal = null; From 74483fb2948bbfcb42e6d22b6a3f764144195c95 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 31 Dec 2016 15:56:53 -0800 Subject: [PATCH 3/4] Use CharMeasure in Viewport and to style wide chars Fixes #439 --- src/Viewport.ts | 25 ++++++++++++------------- src/xterm.css | 4 ++++ src/xterm.js | 18 +++++++++++++----- 3 files changed, 29 insertions(+), 18 deletions(-) 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/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 826531e2..999dc08b 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -574,10 +574,8 @@ 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(); @@ -585,9 +583,12 @@ Terminal.prototype.open = function(parent) { this.parent.appendChild(this.element); 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.charMeasureElement); + this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); // Draw the screen. this.refresh(0, this.rows - 1); @@ -645,6 +646,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 From 07a8b8f4f77d85c2a4f3b07a6ab7f730858abc9e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 31 Dec 2016 18:06:49 -0800 Subject: [PATCH 4/4] Fix tests --- src/Viewport.test.ts | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) 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'); });