mirror of
https://github.com/quine-global/hyper.git
synced 2026-01-16 13:48:41 -09:00
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:
parent
d35fb48dd2
commit
687df4ed06
1 changed files with 157 additions and 49 deletions
206
lib/hterm.js
206
lib/hterm.js
|
|
@ -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_;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue