From da4858a76e4faad6e5ee49f9cebf80e3fa981de4 Mon Sep 17 00:00:00 2001 From: Benoit Averty Date: Fri, 18 Nov 2016 02:12:23 +0100 Subject: [PATCH] Unicode Support / Cursor issues Fix (#740) * Add container with a fixed width around unicode characters * Make container width dynamic and fix wide chars clipping issue * Finer control on the creation of containers for text inserted in the terminal --- lib/hterm.js | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++- package.json | 3 +- 2 files changed, 102 insertions(+), 3 deletions(-) diff --git a/lib/hterm.js b/lib/hterm.js index d56febdf..eb4ffd5e 100644 --- a/lib/hterm.js +++ b/lib/hterm.js @@ -1,10 +1,16 @@ import {hterm, lib} from 'hterm-umdjs'; -import fromCharCode from './utils/key-code'; +import runes from 'runes'; -const selection = require('./utils/selection'); +import fromCharCode from './utils/key-code'; +import selection from './utils/selection'; hterm.defaultStorage = new lib.Storage.Memory(); +// The current width of characters rendered in hterm +let charWidth; +// Containers to resize when char width changes +const containers = []; + // Provide selectAll to terminal viewport hterm.Terminal.prototype.selectAll = function () { // We need to clear the DOM range to reset anchorNode @@ -12,6 +18,25 @@ hterm.Terminal.prototype.selectAll = function () { selection.all(this); }; +const oldSetFontSize = hterm.Terminal.prototype.setFontSize; +hterm.Terminal.prototype.setFontSize = function (px) { + oldSetFontSize.call(this, px); + charWidth = this.scrollPort_.characterSize.width; + // @TODO Maybe clear old spans from the list of spans to resize ? + // Resize all containers to match the new whar width. + containers.forEach(container => { + if (container && container.style) { + container.style.width = `${container.wcNode ? charWidth * 2 : charWidth}px`; + } + }); +}; + +const oldSyncFontFamily = hterm.Terminal.prototype.syncFontFamily; +hterm.Terminal.prototype.syncFontFamily = function () { + oldSyncFontFamily.call(this); + this.setFontSize(); +}; + // override double click behavior to copy const oldMouse = hterm.Terminal.prototype.onMouse_; hterm.Terminal.prototype.onMouse_ = function (e) { @@ -23,6 +48,79 @@ hterm.Terminal.prototype.onMouse_ = function (e) { return oldMouse.call(this, e); }; +function containsNonLatinCodepoints(s) { + return /[^\u0000-\u00ff]/.test(s); +} +hterm.Terminal.IO.prototype.writeUTF8 = function (string) { + if (this.terminal_.io !== this) { + throw new Error('Attempt to print from inactive IO object.'); + } + + if (containsNonLatinCodepoints(string)) { + const splitString = runes(string); + const length = splitString.length; + this.terminal_.getTextAttributes().hasUnicode = true; + + for (let curChar = 0; curChar <= length; curChar++) { + this.terminal_.interpret(splitString[curChar]); + } + + this.terminal_.getTextAttributes().hasUnicode = false; + } else { + this.terminal_.interpret(string); + } +}; + +const oldCreateContainer = hterm.TextAttributes.prototype.createContainer; +hterm.TextAttributes.prototype.createContainer = function (text) { + const container = oldCreateContainer.call(this, text); + + if (container.style && text.length === 1 && containsNonLatinCodepoints(text)) { + container.style.width = `${container.wcNode ? charWidth * 2 : charWidth}px`; + container.style.display = 'inline-block'; + + // If the container has unicode text, the char can overlap neigbouring containers. We need + // to ensure that the text is not hidden behind other containers. + container.style.overflow = 'visible'; + container.style.position = 'relative'; + + // Remember this container to resize it later when font size changes. + containers.push(container); + } + + return container; +}; + +// Do not match containers when one of them has unicode text (unicode chars need to be alone in their containers) +const oldMatchesContainer = hterm.TextAttributes.prototype.matchesContainer; +hterm.TextAttributes.prototype.matchesContainer = function (obj) { + const content = typeof obj === 'string' ? obj : obj.textContent; + if (containsNonLatinCodepoints(content)) { + return false; + } + + if (this.hasUnicode) { + return false; + } + + return oldMatchesContainer.call(this, obj); +}; + +/** + * Override 'containersMatch' so that containers with unicode do not match anything. + */ +const oldContainersMatch = hterm.TextAttributes.containersMatch; +hterm.TextAttributes.containersMatch = function (obj1, obj2) { + const content1 = typeof obj1 === 'string' ? obj1 : obj1.textContent; + const content2 = typeof obj2 === 'string' ? obj2 : obj2.textContent; + + if (containsNonLatinCodepoints(content1) || containsNonLatinCodepoints(content2)) { + return false; + } + + return oldContainersMatch(obj1, obj2); +}; + // there's no option to turn off the size overlay hterm.Terminal.prototype.overlaySize = function () {}; diff --git a/package.json b/package.json index 4d5544a0..39d22557 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,8 @@ "reselect": "2.5.4", "seamless-immutable": "6.1.3", "semver": "5.3.0", - "uuid": "2.0.2" + "uuid": "2.0.2", + "runes": "^0.3.0" }, "devDependencies": { "ava": "^0.16.0",