Fix unicode display: CJK, powerline, tmux (#1536)

* Fix unicode display with braille compatibility

* Remove duplicated styling

* Clean up changes, fix CJK display only

* Remove exising hterm overides for unicode

* Add insertString override (no changes)

* Wrap unicode chars in individual spans

* fix linting

* Fix unicode encapsulation

* Fix multiple rendering of wide chars
This commit is contained in:
Thibaut Tiberghien 2017-05-20 07:48:12 +08:00 committed by Guillermo Rauch
parent d35fb48dd2
commit 687df4ed06

View file

@ -86,21 +86,17 @@ lib.wc.substr = function (str, start, optWidth) {
const chars = runes(str); const chars = runes(str);
let startIndex; let startIndex;
let endIndex; let endIndex;
let width = 0; let width;
for (let i = 0; i < chars.length; i++) { for (startIndex = 0, width = 0; startIndex < chars.length; startIndex++) {
const codePoint = chars[i].codePointAt(0); width += lib.wc.charWidth(chars[startIndex].codePointAt(0));
const charWidth = lib.wc.charWidth(codePoint); if (width > start) {
if ((width + charWidth) > start) {
startIndex = i;
break; break;
} }
width += charWidth;
} }
if (optWidth) { if (optWidth) {
width = 0; for (endIndex = startIndex, width = 0; endIndex < chars.length && width < optWidth; endIndex++) {
for (endIndex = startIndex; endIndex < chars.length && width < optWidth; endIndex++) {
width += lib.wc.charWidth(chars[endIndex].charCodeAt(0)); width += lib.wc.charWidth(chars[endIndex].charCodeAt(0));
} }
@ -120,28 +116,6 @@ hterm.Keyboard.prototype.onTextInput_ = function (e) {
runes(e.data).forEach(this.terminal.onVTKeystroke.bind(this.terminal)); runes(e.data).forEach(this.terminal.onVTKeystroke.bind(this.terminal));
}; };
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)) {
this.terminal_.interpret(string);
return;
}
runes(string).forEach(rune => {
this.terminal_.getTextAttributes().unicodeNode = containsNonLatinCodepoints(rune);
this.terminal_.interpret(rune);
this.terminal_.getTextAttributes().unicodeNode = false;
});
};
const oldIsDefault = hterm.TextAttributes.prototype.isDefault;
hterm.TextAttributes.prototype.isDefault = function () {
return !this.unicodeNode && oldIsDefault.call(this);
};
const oldSetFontSize = hterm.Terminal.prototype.setFontSize; const oldSetFontSize = hterm.Terminal.prototype.setFontSize;
hterm.Terminal.prototype.setFontSize = function (px) { hterm.Terminal.prototype.setFontSize = function (px) {
oldSetFontSize.call(this, px); oldSetFontSize.call(this, px);
@ -153,7 +127,7 @@ hterm.Terminal.prototype.setFontSize = function (px) {
doc.head.appendChild(unicodeNodeStyle); doc.head.appendChild(unicodeNodeStyle);
} }
unicodeNodeStyle.innerHTML = ` unicodeNodeStyle.innerHTML = `
.unicode-node { .unicode-node:not(.wc-node) {
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
width: ${this.scrollPort_.characterSize.width}px; width: ${this.scrollPort_.characterSize.width}px;
@ -161,23 +135,6 @@ hterm.Terminal.prototype.setFontSize = function (px) {
`; `;
}; };
const oldCreateContainer = hterm.TextAttributes.prototype.createContainer;
hterm.TextAttributes.prototype.createContainer = function (text) {
const container = oldCreateContainer.call(this, text);
if (container.style && runes(text).length === 1 && containsNonLatinCodepoints(text)) {
container.className += ' unicode-node';
}
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) {
return oldMatchesContainer.call(this, obj) &&
!this.unicodeNode &&
!containsNonLatinCodepoints(obj.textContent);
};
// there's no option to turn off the size overlay // there's no option to turn off the size overlay
hterm.Terminal.prototype.overlaySize = function () {}; hterm.Terminal.prototype.overlaySize = function () {};
@ -388,6 +345,157 @@ hterm.Terminal.prototype.focusHyperCaret = function () {
this.hyperCaret.focus(); this.hyperCaret.focus();
}; };
// patch for unicode encapsulation
// see comments in original code
hterm.Screen.prototype.insertString = function (str) {
let cursorNode = this.cursorNode_;
let cursorNodeText = cursorNode.textContent;
this.cursorRowNode_.removeAttribute('line-overflow');
const strWidth = lib.wc.strWidth(str);
this.cursorPosition.column += strWidth;
let offset = this.cursorOffset_;
let reverseOffset = hterm.TextAttributes.nodeWidth(cursorNode) - offset;
if (reverseOffset < 0) {
const ws = lib.f.getWhitespace(-reverseOffset);
if (!(this.textAttributes.underline ||
this.textAttributes.strikethrough ||
this.textAttributes.background ||
this.textAttributes.wcNode ||
this.textAttributes.tileData !== null)) {
str = ws + str;
} else if (cursorNode.nodeType === 3 ||
!(cursorNode.wcNode ||
cursorNode.tileNode ||
cursorNode.style.textDecoration ||
cursorNode.style.backgroundColor)) {
cursorNode.textContent = (cursorNodeText += ws);
} else {
const wsNode = cursorNode.ownerDocument.createTextNode(ws);
this.cursorRowNode_.insertBefore(wsNode, cursorNode.nextSibling);
this.cursorNode_ = cursorNode = wsNode;
this.cursorOffset_ = offset = -reverseOffset;
cursorNodeText = ws;
}
reverseOffset = 0;
}
this.wrapUnicode(cursorNode);
if (this.textAttributes.matchesContainer(cursorNode)) {
if (reverseOffset === 0) {
cursorNode.textContent = cursorNodeText + str;
} else if (offset === 0) {
cursorNode.textContent = str + cursorNodeText;
} else {
cursorNode.textContent =
hterm.TextAttributes.nodeSubstr(cursorNode, 0, offset) +
str + hterm.TextAttributes.nodeSubstr(cursorNode, offset);
}
this.cursorOffset_ += strWidth;
this.wrapUnicode(cursorNode);
return;
}
if (offset === 0) {
const previousSibling = cursorNode.previousSibling;
if (previousSibling &&
this.textAttributes.matchesContainer(previousSibling)) {
previousSibling.textContent += str;
this.cursorNode_ = previousSibling;
this.cursorOffset_ = lib.wc.strWidth(previousSibling.textContent);
this.wrapUnicode(previousSibling);
return;
}
const newNode = this.textAttributes.createContainer(str);
this.cursorRowNode_.insertBefore(newNode, cursorNode);
this.cursorNode_ = newNode;
this.cursorOffset_ = strWidth;
this.wrapUnicode(newNode);
return;
}
if (reverseOffset === 0) {
const nextSibling = cursorNode.nextSibling;
if (nextSibling &&
this.textAttributes.matchesContainer(nextSibling)) {
nextSibling.textContent = str + nextSibling.textContent;
this.cursorNode_ = nextSibling;
this.cursorOffset_ = lib.wc.strWidth(str);
this.wrapUnicode(nextSibling);
return;
}
const newNode = this.textAttributes.createContainer(str);
this.cursorRowNode_.insertBefore(newNode, nextSibling);
this.cursorNode_ = newNode;
this.cursorOffset_ = hterm.TextAttributes.nodeWidth(newNode);
this.wrapUnicode(newNode);
return;
}
this.splitNode_(cursorNode, offset);
const newNode = this.textAttributes.createContainer(str);
this.cursorRowNode_.insertBefore(newNode, cursorNode.nextSibling);
this.cursorNode_ = newNode;
this.cursorOffset_ = strWidth;
this.wrapUnicode(newNode);
};
// patch for unicode encapsulation
hterm.Screen.prototype.splitNode_ = function (node, offset) {
const afterNode = node.cloneNode(false);
const textContent = node.textContent;
node.textContent = hterm.TextAttributes.nodeSubstr(node, 0, offset);
afterNode.textContent = lib.wc.substr(textContent, offset);
this.wrapUnicode(node);
this.wrapUnicode(afterNode);
if (afterNode.textContent) {
node.parentNode.insertBefore(afterNode, node.nextSibling);
}
if (!node.textContent) {
node.parentNode.removeChild(node);
}
};
// encapsulate unicode chars in individual spans
// keep them in the parent node with the rest of the content
// maintains styling and overflow display in divs with a background color
hterm.Screen.prototype.wrapUnicode = function (node) {
const nodeContent = node.textContent;
if (containsNonLatinCodepoints(nodeContent) && !node.className.includes('wc-node')) {
const doc = this.terminal.document_;
if (!node.className.includes('unicode-parent-node')) {
node.className += ' unicode-parent-node';
}
node.textContent = null;
let strBuffer = '';
runes(nodeContent).forEach(rune => {
if (containsNonLatinCodepoints(rune)) {
if (strBuffer !== '') {
const stringNode = doc.createTextNode(strBuffer);
node.appendChild(stringNode);
strBuffer = '';
}
const unicodeNode = doc.createElement('span');
unicodeNode.className = 'unicode-node';
unicodeNode.textContent = rune;
node.appendChild(unicodeNode);
} else {
strBuffer += rune;
}
});
if (strBuffer !== '') {
const stringNode = doc.createTextNode(strBuffer);
node.appendChild(stringNode);
}
}
};
// ensure no text node contains unicode
const oldCreateContainer = hterm.TextAttributes.prototype.createContainer;
hterm.TextAttributes.prototype.createContainer = function (text) {
if (this.isDefault() && text && containsNonLatinCodepoints(text)) {
const span = this.document_.createElement('span');
span.textContent = text;
return span;
}
return oldCreateContainer.call(this, text);
};
hterm.Screen.prototype.syncSelectionCaret = function () { hterm.Screen.prototype.syncSelectionCaret = function () {
const p = this.terminal.hyperCaret; const p = this.terminal.hyperCaret;
const doc = this.terminal.document_; const doc = this.terminal.document_;