diff --git a/demo/client.ts b/demo/client.ts index 51c16d50..347a9d65 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -257,7 +257,7 @@ function initOptions(term: TerminalType): void { logLevel: ['debug', 'info', 'warn', 'error', 'off'], rendererType: ['dom', 'canvas'], wordSeparator: null, - unicodeVersion: ['6', '10'] + unicodeVersion: ['6', '11'] }; const options = Object.keys((term)._core.options); const booleanOptions = []; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index d2ad6cd3..e439a7d9 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -7,7 +7,7 @@ import { IInputHandler, IInputHandlingTerminal } from './Types'; import { C0, C1 } from 'common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; -import { wcwidthV6, wcwidthV10 } from 'common/CharWidth'; +import { wcwidthV6, wcwidthV11 } from 'common/CharWidth'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { Disposable } from 'common/Lifecycle'; import { concat } from 'common/TypedArrayUtils'; @@ -147,10 +147,10 @@ export class InputHandler extends Disposable implements IInputHandler { private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser()) { super(); - this._wcwidth = this._optionsService.options.unicodeVersion === '10' ? wcwidthV10 : wcwidthV6; + this._wcwidth = this._optionsService.options.unicodeVersion === '11' ? wcwidthV11 : wcwidthV6; this._optionsService.onOptionChange(option => { if (option === 'unicodeVersion') { - this._wcwidth = this._optionsService.options.unicodeVersion === '10' ? wcwidthV10 : wcwidthV6; + this._wcwidth = this._optionsService.options.unicodeVersion === '11' ? wcwidthV11 : wcwidthV6; } }); diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 2335f0f6..5f7f0721 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -10,7 +10,7 @@ import { CellData } from 'common/buffer/CellData'; import { wcwidthV6 } from 'common/CharWidth'; import { IBufferService } from 'common/services/Services'; import { Linkifier } from 'browser/Linkifier'; -import { MockLogService } from 'common/TestUtils.test'; +import { MockLogService, MockOptionsService } from 'common/TestUtils.test'; import { IRegisteredLinkMatcher, IMouseZoneManager, IMouseZone } from 'browser/Types'; const INIT_COLS = 80; @@ -1371,7 +1371,7 @@ describe('Terminal', () => { class TestLinkifier extends Linkifier { constructor(bufferService: IBufferService) { - super(bufferService, new MockLogService()); + super(bufferService, new MockLogService(), new MockOptionsService()); Linkifier._timeBeforeLatency = 0; } diff --git a/src/Terminal.ts b/src/Terminal.ts index 6a039a0e..b4c0e4e6 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -281,7 +281,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); this.register(this._inputHandler); - this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService); + this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService, this.optionsService); if (this.options.windowsMode) { this._windowsMode = applyWindowsMode(this); diff --git a/src/browser/Linkifier.test.ts b/src/browser/Linkifier.test.ts index 128f786d..a4b4d2f5 100644 --- a/src/browser/Linkifier.test.ts +++ b/src/browser/Linkifier.test.ts @@ -9,12 +9,12 @@ import { IBufferLine } from 'common/Types'; import { Linkifier } from 'browser/Linkifier'; import { BufferLine } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; -import { MockLogService, MockBufferService } from 'common/TestUtils.test'; +import { MockLogService, MockBufferService, MockOptionsService } from 'common/TestUtils.test'; import { IBufferService } from 'common/services/Services'; class TestLinkifier extends Linkifier { constructor(bufferService: IBufferService) { - super(bufferService, new MockLogService()); + super(bufferService, new MockLogService(), new MockOptionsService()); Linkifier._timeBeforeLatency = 0; } diff --git a/src/browser/Linkifier.ts b/src/browser/Linkifier.ts index f51ea1ca..072f7972 100644 --- a/src/browser/Linkifier.ts +++ b/src/browser/Linkifier.ts @@ -5,9 +5,9 @@ import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager, IMouseZone, IRegisteredLinkMatcher } from 'browser/Types'; import { IBufferStringIteratorResult } from 'common/buffer/Types'; -import { getStringCellWidthV6 } from 'common/CharWidth'; +import { getStringCellWidthV6, getStringCellWidthV11 } from 'common/CharWidth'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { ILogService, IBufferService } from 'common/services/Services'; +import { ILogService, IBufferService, IOptionsService } from 'common/services/Services'; /** * Limit of the unwrapping line expansion (overscan) at the top and bottom @@ -45,7 +45,8 @@ export class Linkifier implements ILinkifier { constructor( protected readonly _bufferService: IBufferService, - private readonly _logService: ILogService + private readonly _logService: ILogService, + private readonly _optionsService: IOptionsService ) { this._rowsToLinkify = { start: undefined, @@ -277,8 +278,7 @@ export class Linkifier implements ILinkifier { if (!this._mouseZoneManager || !this._element) { return; } - - const width = getStringCellWidthV6(uri); // FIXME: apply options.unicodeVersion + const width = this._optionsService.options.unicodeVersion === '11' ? getStringCellWidthV11(uri) : getStringCellWidthV6(uri); const x1 = x % this._bufferService.cols; const y1 = y + Math.floor(x / this._bufferService.cols); let x2 = (x1 + width) % this._bufferService.cols; diff --git a/src/common/CharWidth.test.ts b/src/common/CharWidth.test.ts index 8a857424..ff6eb3a6 100644 --- a/src/common/CharWidth.test.ts +++ b/src/common/CharWidth.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { wcwidthV6 } from 'common/CharWidth'; +import { wcwidthV6, getStringCellWidthV6, getStringCellWidthV11 } from 'common/CharWidth'; it('wcwidth should match all values from the old implementation', function(): void { // old implementation @@ -180,3 +180,10 @@ it('wcwidth should match all values from the old implementation', function(): vo assert.equal(wcwidthV6(i), wcwidthOld(i), `mismatch for i: ${i}`); } }); + +it('wcwidth V6 vs. V11', () => { + const widthV6 = getStringCellWidthV6('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣'); + assert.equal(widthV6, 10); + const widthV10 = getStringCellWidthV11('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣'); + assert.equal(widthV10, 20); +}); diff --git a/src/common/CharWidth.ts b/src/common/CharWidth.ts index 852b99af..ca7cd2d1 100644 --- a/src/common/CharWidth.ts +++ b/src/common/CharWidth.ts @@ -159,7 +159,7 @@ export function getStringCellWidthV6(s: string): number { /** * Unicode version 10 */ -const BMP_COMBINING_V10 = [ +const BMP_COMBINING_V11 = [ [0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2], [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0605], [0x0610, 0x061A], @@ -234,7 +234,7 @@ const BMP_COMBINING_V10 = [ [0xFFF9, 0xFFFB] ]; -const HIGH_COMBINING_V10 = [ +const HIGH_COMBINING_V11 = [ [0x101FD, 0x101FD], [0x102E0, 0x102E0], [0x10376, 0x1037A], [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], @@ -277,7 +277,7 @@ const HIGH_COMBINING_V10 = [ [0xE0100, 0xE01EF] ]; -const BMP_WIDE_V10 = [ +const BMP_WIDE_V11 = [ [0x1100, 0x115F], [0x231A, 0x231B], [0x2329, 0x232A], [0x23E9, 0x23EC], [0x23F0, 0x23F0], [0x23F3, 0x23F3], [0x25FD, 0x25FE], [0x2614, 0x2615], [0x2648, 0x2653], @@ -301,7 +301,7 @@ const BMP_WIDE_V10 = [ [0xFFE0, 0xFFE6] ]; -const HIGH_WIDE_V10 = [ +const HIGH_WIDE_V11 = [ [0x16FE0, 0x16FE3], [0x17000, 0x187F7], [0x18800, 0x18AF2], [0x1B000, 0x1B11E], [0x1B150, 0x1B152], [0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1F004, 0x1F004], @@ -322,27 +322,27 @@ const HIGH_WIDE_V10 = [ [0x1FA90, 0x1FA95], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD] ]; -const BMP_TABLE_V10 = new Uint8Array(65536); -fill(BMP_TABLE_V10, 1); -BMP_TABLE_V10[0] = 0; -fill(BMP_TABLE_V10, 0, 1, 32); -fill(BMP_TABLE_V10, 0, 0x7f, 0xa0); -for (let r = 0; r < BMP_COMBINING_V10.length; ++r) { - fill(BMP_TABLE_V10, 0, BMP_COMBINING_V10[r][0], BMP_COMBINING_V10[r][1] + 1); +const BMP_TABLE_V11 = new Uint8Array(65536); +fill(BMP_TABLE_V11, 1); +BMP_TABLE_V11[0] = 0; +fill(BMP_TABLE_V11, 0, 1, 32); +fill(BMP_TABLE_V11, 0, 0x7f, 0xa0); +for (let r = 0; r < BMP_COMBINING_V11.length; ++r) { + fill(BMP_TABLE_V11, 0, BMP_COMBINING_V11[r][0], BMP_COMBINING_V11[r][1] + 1); } -for (let r = 0; r < BMP_WIDE_V10.length; ++r) { - fill(BMP_TABLE_V10, 2, BMP_WIDE_V10[r][0], BMP_WIDE_V10[r][1] + 1); +for (let r = 0; r < BMP_WIDE_V11.length; ++r) { + fill(BMP_TABLE_V11, 2, BMP_WIDE_V11[r][0], BMP_WIDE_V11[r][1] + 1); } -export function wcwidthV10(num: number): number { +export function wcwidthV11(num: number): number { if (num < 32) return 0; if (num < 127) return 1; - if (num < 65536) return BMP_TABLE_V10[num]; - if (bisearch(num, HIGH_COMBINING_V10)) return 0; - if (bisearch(num, HIGH_WIDE_V10)) return 2; + if (num < 65536) return BMP_TABLE_V11[num]; + if (bisearch(num, HIGH_COMBINING_V11)) return 0; + if (bisearch(num, HIGH_WIDE_V11)) return 2; return 1; } -export function getStringCellWidthV10(s: string): number { - return getStringCellWidthPrivate(wcwidthV10, s); +export function getStringCellWidthV11(s: string): number { + return getStringCellWidthPrivate(wcwidthV11, s); } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 380bc6c4..b86c635e 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -212,7 +212,7 @@ export interface IPartialTerminalOptions { scrollSensitivity?: number; tabStopWidth?: number; theme?: ITheme; - unicodeVersion?: '6' | '10'; + unicodeVersion?: '6' | '11'; windowsMode?: boolean; wordSeparator?: string; } @@ -245,7 +245,7 @@ export interface ITerminalOptions { scrollSensitivity: number; tabStopWidth: number; theme: ITheme; - unicodeVersion: '6' | '10'; + unicodeVersion: '6' | '11'; windowsMode: boolean; wordSeparator: string; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index a086d030..4cd23544 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -198,6 +198,11 @@ declare module 'xterm' { */ theme?: ITheme; + /** + * Unicode version to be used for wcwidth. Default is version 6. + */ + unicodeVersion?: '6' | '11'; + /** * Whether "Windows mode" is enabled. Because Windows backends winpty and * conpty operate by doing line wrapping on their side, xterm.js does not