From c7ad4aa460710aa2062b2bcf06b015525a9aa3fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 15 Nov 2019 13:33:28 +0100 Subject: [PATCH] terminal option unicodeVersion --- demo/client.ts | 3 ++- src/InputHandler.ts | 12 +++++++++--- src/Terminal.test.ts | 4 ++-- src/browser/Linkifier.ts | 4 ++-- src/common/CharWidth.test.ts | 4 ++-- src/common/CharWidth.ts | 20 ++++++++++---------- src/common/services/OptionsService.ts | 3 ++- src/common/services/Services.ts | 2 ++ 8 files changed, 31 insertions(+), 21 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 5b76700ea..51c16d50d 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -256,7 +256,8 @@ function initOptions(term: TerminalType): void { fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], logLevel: ['debug', 'info', 'warn', 'error', 'off'], rendererType: ['dom', 'canvas'], - wordSeparator: null + wordSeparator: null, + unicodeVersion: ['6', '10'] }; const options = Object.keys((term)._core.options); const booleanOptions = []; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 28d17b3e6..d2ad6cd31 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 { wcwidth, wcwidth10 } from 'common/CharWidth'; +import { wcwidthV6, wcwidthV10 } from 'common/CharWidth'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { Disposable } from 'common/Lifecycle'; import { concat } from 'common/TypedArrayUtils'; @@ -127,6 +127,7 @@ export class InputHandler extends Disposable implements IInputHandler { private _stringDecoder: StringToUtf32 = new StringToUtf32(); private _utf8Decoder: Utf8ToUtf32 = new Utf8ToUtf32(); private _workCell: CellData = new CellData(); + private _wcwidth: (codepoint: number) => number; private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } @@ -146,6 +147,12 @@ export class InputHandler extends Disposable implements IInputHandler { private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser()) { super(); + this._wcwidth = this._optionsService.options.unicodeVersion === '10' ? wcwidthV10 : wcwidthV6; + this._optionsService.onOptionChange(option => { + if (option === 'unicodeVersion') { + this._wcwidth = this._optionsService.options.unicodeVersion === '10' ? wcwidthV10 : wcwidthV6; + } + }); this.register(this._parser); @@ -393,8 +400,7 @@ export class InputHandler extends Disposable implements IInputHandler { // calculate print space // expensive call, therefore we save width in line buffer - chWidth = wcwidth(code); - chWidth = wcwidth10(code); + chWidth = this._wcwidth(code); // get charset replacement character // charset is only defined for ASCII, therefore we only diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index b55a516b2..2335f0f63 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -7,7 +7,7 @@ import { assert, expect } from 'chai'; import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from './TestUtils.test'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; -import { wcwidth } from 'common/CharWidth'; +import { wcwidthV6 } from 'common/CharWidth'; import { IBufferService } from 'common/services/Services'; import { Linkifier } from 'browser/Linkifier'; import { MockLogService } from 'common/TestUtils.test'; @@ -762,7 +762,7 @@ describe('Terminal', () => { for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.buffer.x = term.cols - 1; term.wraparoundMode = false; - const width = wcwidth((0xD800 - 0xD800) * 0x400 + i - 0xDC00 + 0x10000); + const width = wcwidthV6((0xD800 - 0xD800) * 0x400 + i - 0xDC00 + 0x10000); if (width !== 1) { continue; } diff --git a/src/browser/Linkifier.ts b/src/browser/Linkifier.ts index 58aca70eb..f51ea1ca2 100644 --- a/src/browser/Linkifier.ts +++ b/src/browser/Linkifier.ts @@ -5,7 +5,7 @@ import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager, IMouseZone, IRegisteredLinkMatcher } from 'browser/Types'; import { IBufferStringIteratorResult } from 'common/buffer/Types'; -import { getStringCellWidth } from 'common/CharWidth'; +import { getStringCellWidthV6 } from 'common/CharWidth'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { ILogService, IBufferService } from 'common/services/Services'; @@ -278,7 +278,7 @@ export class Linkifier implements ILinkifier { return; } - const width = getStringCellWidth(uri); + const width = getStringCellWidthV6(uri); // FIXME: apply options.unicodeVersion 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 6b2493eef..8a857424e 100644 --- a/src/common/CharWidth.test.ts +++ b/src/common/CharWidth.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { wcwidth } from 'common/CharWidth'; +import { wcwidthV6 } from 'common/CharWidth'; it('wcwidth should match all values from the old implementation', function(): void { // old implementation @@ -177,6 +177,6 @@ it('wcwidth should match all values from the old implementation', function(): vo // test full BMP range old vs new implmenetation for (let i = 0; i < 65536; ++i) { - assert.equal(wcwidth(i), wcwidthOld(i), `mismatch for i: ${i}`); + assert.equal(wcwidthV6(i), wcwidthOld(i), `mismatch for i: ${i}`); } }); diff --git a/src/common/CharWidth.ts b/src/common/CharWidth.ts index ae826c89e..b03f74ad4 100644 --- a/src/common/CharWidth.ts +++ b/src/common/CharWidth.ts @@ -5,7 +5,7 @@ import { fill } from 'common/TypedArrayUtils'; -export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number { +export const wcwidthV6 = (function(opts: {nul: number, control: number}): (ucs: number) => number { // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c // combining characters const COMBINING_BMP = [ @@ -141,7 +141,7 @@ export const wcwidth = (function(opts: {nul: number, control: number}): (ucs: nu /** * Get the terminal cell width for a string. */ -export function getStringCellWidth(s: string): number { +export function getStringCellWidthV6(s: string): number { let result = 0; const length = s.length; for (let i = 0; i < length; ++i) { @@ -154,7 +154,7 @@ export function getStringCellWidth(s: string): number { // and therefore always should contain the second part // for any other string we still have to handle it somehow: // simply treat the lonely surrogate first as a single char (UCS-2 behavior) - return result + wcwidth(code); + return result + wcwidthV6(code); } const second = s.charCodeAt(i); // convert surrogate pair to high codepoint only for valid second part (UTF-16) @@ -162,10 +162,10 @@ export function getStringCellWidth(s: string): number { if (0xDC00 <= second && second <= 0xDFFF) { code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; } else { - result += wcwidth(second); + result += wcwidthV6(second); } } - result += wcwidth(code); + result += wcwidthV6(code); } return result; } @@ -377,7 +377,7 @@ for (let r = 0; r < WIDE_BMP.length; ++r) { fill(TABLE, 2, WIDE_BMP[r][0], WIDE_BMP[r][1] + 1); } -export function wcwidth10(num: number): number { +export function wcwidthV10(num: number): number { if (num < 32) { return 0; } @@ -390,7 +390,7 @@ export function wcwidth10(num: number): number { return wcwidthHigh(num); }; -export function getStringCellWidth10(s: string): number { +export function getStringCellWidthV10(s: string): number { let result = 0; const length = s.length; for (let i = 0; i < length; ++i) { @@ -403,7 +403,7 @@ export function getStringCellWidth10(s: string): number { // and therefore always should contain the second part // for any other string we still have to handle it somehow: // simply treat the lonely surrogate first as a single char (UCS-2 behavior) - return result + wcwidth10(code); + return result + wcwidthV10(code); } const second = s.charCodeAt(i); // convert surrogate pair to high codepoint only for valid second part (UTF-16) @@ -411,10 +411,10 @@ export function getStringCellWidth10(s: string): number { if (0xDC00 <= second && second <= 0xDFFF) { code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; } else { - result += wcwidth10(second); + result += wcwidthV10(second); } } - result += wcwidth10(code); + result += wcwidthV10(code); } return result; } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index d9ea60d5a..ccd38d47f 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -50,7 +50,8 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ screenKeys: false, cancelEvents: false, useFlowControl: false, - wordSeparator: ' ()[]{}\',:;"' + wordSeparator: ' ()[]{}\',:;"', + unicodeVersion: '6' }); /** diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 45d39a6af..380bc6c40 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -212,6 +212,7 @@ export interface IPartialTerminalOptions { scrollSensitivity?: number; tabStopWidth?: number; theme?: ITheme; + unicodeVersion?: '6' | '10'; windowsMode?: boolean; wordSeparator?: string; } @@ -244,6 +245,7 @@ export interface ITerminalOptions { scrollSensitivity: number; tabStopWidth: number; theme: ITheme; + unicodeVersion: '6' | '10'; windowsMode: boolean; wordSeparator: string;