Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multiple unicode version support #1714

Closed
wants to merge 13 commits into from
3 changes: 2 additions & 1 deletion demo/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ function initOptions(term: TerminalType): void {
fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
rendererType: ['dom', 'canvas'],
unicodeVersion: (term as any)._core.unicodeManager.registeredVersions.map(String),
experimentalBufferLineImpl: ['JsArray', 'TypedArray']
};
const options = Object.keys((<any>term)._core.options);
Expand All @@ -219,7 +220,7 @@ function initOptions(term: TerminalType): void {
booleanOptions.push(o);
break;
case 'number':
numberOptions.push(o);
numberOptions.push(o);
break;
default:
if (Object.keys(stringOptions).indexOf(o) === -1) {
Expand Down
2 changes: 1 addition & 1 deletion src/Buffer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ describe('Buffer', () => {
});

it('fullwidth combining with emoji - match emoji cell', () => {
const input = 'Lots of ¥\u0301 make me 😃.';
const input = 'Lots of ¥\u0301 make me very 😃.';
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
Expand Down
2 changes: 1 addition & 1 deletion src/InputHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { C0, C1 } from './common/data/EscapeSequences';
import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer';
import { FLAGS } from './renderer/Types';
import { wcwidth } from './CharWidth';
import { EscapeSequenceParser } from './EscapeSequenceParser';
import { ICharset } from './core/Types';
import { Disposable } from './common/Lifecycle';
Expand Down Expand Up @@ -336,6 +335,7 @@ export class InputHandler extends Disposable implements IInputHandler {
const wraparoundMode: boolean = this._terminal.wraparoundMode;
const insertMode: boolean = this._terminal.insertMode;
const curAttr: number = this._terminal.curAttr;
const wcwidth = this._terminal.unicodeManager.wcwidth;
let bufferRow = buffer.lines.get(buffer.y + buffer.ybase);

this._terminal.updateRange(buffer.y);
Expand Down
4 changes: 2 additions & 2 deletions src/Linkifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes,
import { MouseZone } from './ui/MouseZoneManager';
import { EventEmitter } from './common/EventEmitter';
import { CHAR_DATA_ATTR_INDEX } from './Buffer';
import { getStringCellWidth } from './CharWidth';

/**
* The Linkifier applies links to rows shortly after they have been refreshed.
Expand Down Expand Up @@ -256,7 +255,8 @@ export class Linkifier extends EventEmitter implements ILinkifier {
* @param fg The link color for hover event.
*/
private _addLink(x: number, y: number, uri: string, matcher: ILinkMatcher, fg: number): void {
const width = getStringCellWidth(uri);
// FIXME: to support unicode version runtime switch replace this by endIndex calculation
const width = this._terminal.unicodeManager.getStringCellWidth(uri);
const x1 = x % this._terminal.cols;
const y1 = y + Math.floor(x / this._terminal.cols);
let x2 = (x1 + width) % this._terminal.cols;
Expand Down
6 changes: 6 additions & 0 deletions src/Terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { DomRenderer } from './renderer/dom/DomRenderer';
import { IKeyboardEvent } from './common/Types';
import { evaluateKeyboardEvent } from './core/input/Keyboard';
import { KeyboardResultType, ICharset } from './core/Types';
import { UnicodeVersionManager } from './UnicodeManager';

// Let it work inside Node.js for automated testing purposes.
const document = (typeof window !== 'undefined') ? window.document : null;
Expand Down Expand Up @@ -106,6 +107,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
theme: null,
rightClickSelectsWord: Browser.isMac,
rendererType: 'canvas',
unicodeVersion: '11',
experimentalBufferLineImpl: 'JsArray'
};

Expand Down Expand Up @@ -194,6 +196,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
private _userScrolling: boolean;

private _inputHandler: InputHandler;
public unicodeManager: UnicodeVersionManager;
public soundManager: SoundManager;
public renderer: IRenderer;
public selectionManager: SelectionManager;
Expand Down Expand Up @@ -300,6 +303,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
// this._writeStopped = false;
this._userScrolling = false;

this.unicodeManager = new UnicodeVersionManager();
this.register(this.unicodeManager);
this._inputHandler = new InputHandler(this);
this.register(this._inputHandler);
// Reuse renderer if the Terminal is being recreated via a reset call.
Expand Down Expand Up @@ -494,6 +499,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
}
break;
case 'tabStopWidth': this.buffers.setupTabStops(); break;
case 'unicodeVersion': this.unicodeManager.activeVersion = parseFloat(value); break;
case 'experimentalBufferLineImpl':
this.buffers.normal.setBufferLineFactory(value);
this.buffers.alt.setBufferLineFactory(value);
Expand Down
19 changes: 19 additions & 0 deletions src/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export interface IInputHandlingTerminal extends IEventEmitter {
handleTitle(title: string): void;
index(): void;
reverseIndex(): void;
unicodeManager: IUnicodeVersionManager;
}

export interface IViewport extends IDisposable {
Expand Down Expand Up @@ -221,6 +222,7 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce
viewport: IViewport;
bracketedPasteMode: boolean;
applicationCursor: boolean;
unicodeManager: IUnicodeVersionManager;

/**
* Emit the 'data' event and populate the given data.
Expand Down Expand Up @@ -529,3 +531,20 @@ export interface IBufferLine {
export interface IBufferLineConstructor {
new(cols: number, fillCharData?: CharData, isWrapped?: boolean): IBufferLine;
}

/**
* Interface for unicode version implementations.
*/
export interface IUnicodeVersionProvider {
version: number;
wcwidth(ucs: number): number;
}

export interface IUnicodeVersionManager {
addRegisterListener(callback: (version: number, manager: IUnicodeVersionManager) => void): void;
removeRegisterListener(callback: (version: number, provider: IUnicodeVersionManager) => void): void;
registeredVersions: number[];
activeVersion: number;
wcwidth(ucs: number): number;
getStringCellWidth(s: string): number;
}
110 changes: 110 additions & 0 deletions src/UnicodeManager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { UnicodeVersionManager } from './UnicodeManager';
import { IUnicodeVersionProvider } from './Types';

const VERSION_DUMMY1: IUnicodeVersionProvider = {
version: 15,
wcwidth: (n: number) => n
};
const VERSION_DUMMY2: IUnicodeVersionProvider = {
version: 17,
wcwidth: (n: number) => n
};

describe('UnicodeProvider', function(): void {
describe('static part', function(): void {

it('provided default versions', function(): void {
assert.deepEqual(UnicodeVersionManager.registeredVersions, [6, 11]);
});

it('add version', function(): void {
UnicodeVersionManager.registerVersion(VERSION_DUMMY1);
assert.deepEqual(UnicodeVersionManager.registeredVersions, [6, 11, 15]);
delete UnicodeVersionManager.versions[15];
});

it('register callback', function(): void {
let gotCalled = false;
UnicodeVersionManager.addRegisterListener((version) => {
assert.equal(version, 15);
gotCalled = true;
});
UnicodeVersionManager.registerVersion(VERSION_DUMMY1);
assert.equal(gotCalled, true);
delete UnicodeVersionManager.versions[15];
UnicodeVersionManager.removeAllRegisterListener();
});

it('remove callback', function(): void {
let gotCalled = false;
const listener = (version: number) => {
assert.equal(version, 15);
gotCalled = true;
};
UnicodeVersionManager.addRegisterListener(listener);
UnicodeVersionManager.registerVersion(VERSION_DUMMY1);
assert.equal(gotCalled, true);
gotCalled = false;
UnicodeVersionManager.removeRegisterListener(listener);
UnicodeVersionManager.registerVersion(VERSION_DUMMY2);
assert.equal(gotCalled, false);
delete UnicodeVersionManager.versions[15];
delete UnicodeVersionManager.versions[17];
UnicodeVersionManager.removeAllRegisterListener();
});
});
describe('instance', function(): void {
let provider: UnicodeVersionManager;

beforeEach(function(): void {
provider = new UnicodeVersionManager();
});

it('highest version activated by default', function(): void {
assert.equal(provider.activeVersion, 11);
});

it('activate exact', function(): void {
assert.throws(() => provider.activeVersion = 5);
assert.throws(() => provider.activeVersion = 7);
assert.throws(() => provider.activeVersion = 10);
assert.throws(() => provider.activeVersion = 12);
assert.throws(() => provider.activeVersion = 200);
assert.doesNotThrow(() => provider.activeVersion = 6);
assert.doesNotThrow(() => provider.activeVersion = 11);
});

it('register/remove callback', function(): void {
let gotCalled = false;
const listener = (version: number, prov: UnicodeVersionManager) => {
assert.equal(version, 15);
assert.equal(prov, provider);
gotCalled = true;
};
provider.addRegisterListener(listener);
UnicodeVersionManager.registerVersion(VERSION_DUMMY1);
assert.equal(gotCalled, true);
gotCalled = false;
provider.removeRegisterListener(listener);
UnicodeVersionManager.registerVersion(VERSION_DUMMY2);
assert.equal(gotCalled, false);
delete UnicodeVersionManager.versions[15];
delete UnicodeVersionManager.versions[17];
UnicodeVersionManager.removeAllRegisterListener();
provider.dispose();
});

it('unicode test', function(): void {
const data = '🔷🔷🔷🔷🔷';
provider.activeVersion = 6;
assert.equal(provider.getStringCellWidth(data), 5);
provider.activeVersion = 11;
assert.equal(provider.getStringCellWidth(data), 10);
});
});
});
Loading