diff --git a/src/Terminal.ts b/src/Terminal.ts index 4c0cd0f8a4..c31f320fc3 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -52,6 +52,7 @@ import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; import { KeyboardResultType, ICharset } from './core/Types'; import { clone } from './common/Clone'; +import { registerRtlCharacterJoiners } from './Unicode'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -752,7 +753,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // Listen for mouse events and translate // them into terminal mouse protocols. this.bindMouse(); - } private _setupRenderer(): void { @@ -762,6 +762,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } this.register(this.renderer); + registerRtlCharacterJoiners(this); } /** diff --git a/src/Unicode.test.ts b/src/Unicode.test.ts new file mode 100644 index 0000000000..f8356c4592 --- /dev/null +++ b/src/Unicode.test.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { createUnicodeRangeJoiner } from './Unicode'; + +describe('createUnicodeRangeJoiner', () => { + it('should join charcaters within the range', () => { + // Join a-z + const joiner = createUnicodeRangeJoiner(97, 122); + assert.deepEqual(joiner('ABCabc aBc abC Abc'), [ + [3, 6], // abc + [11, 13], // ab + [16, 18] // bc + ]); + }); +}); diff --git a/src/Unicode.ts b/src/Unicode.ts new file mode 100644 index 0000000000..e3638394aa --- /dev/null +++ b/src/Unicode.ts @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + */ + +import { Terminal } from 'xterm'; + +export function registerRtlCharacterJoiners(terminal: Terminal): void { + // Hebrew + terminal.registerCharacterJoiner(createUnicodeRangeJoiner(0x0590, 0x05FF)); + // Arabic (Kurdish, Persian, Urdu) + terminal.registerCharacterJoiner(createUnicodeRangeJoiner(0x0600, 0x06FF)); + // Arabic Supplement + terminal.registerCharacterJoiner(createUnicodeRangeJoiner(0x0750, 0x077F)); + // Thanana (Dhivehi, Maldavian) + terminal.registerCharacterJoiner(createUnicodeRangeJoiner(0x0780, 0x07BF)); + // Arabic Extended-A + terminal.registerCharacterJoiner(createUnicodeRangeJoiner(0x08A0, 0x08FF)); + // Arabic Presentation Forms-A + terminal.registerCharacterJoiner(createUnicodeRangeJoiner(0xFB50, 0xFDFF)); + // Arabic Presentation Forms-B + terminal.registerCharacterJoiner(createUnicodeRangeJoiner(0xFE70, 0xFEFF)); + // Rumi Numeral Symbols + terminal.registerCharacterJoiner(createUnicodeRangeJoiner(0x10E60, 0x10E60)); + // Arabic Mathematical Alphabetic Symbols + terminal.registerCharacterJoiner(createUnicodeRangeJoiner(0x1EE00, 0x1EEFF)); +} + +export function createUnicodeRangeJoiner(rangeStart: number, rangeEnd: number): (text: string) => [number, number][] { + return (text: string) => { + let start = -1; + let length = 0; + const result: [number, number][] = []; + for (let i = 0; i < text.length; i++) { + const code = text.charCodeAt(i); + if (code >= rangeStart && code <= rangeEnd || code === 32) { + // Add to range + if (start === -1) { + start = i; + } + length++; + } else { + if (length > 1) { + // Finish range + result.push([start, start + length]); + } + // Clear a single match + start = -1; + length = 0; + } + } + if (length > 0) { + result.push([start, start + length]); + } + return result; + }; +}