Merge remote-tracking branch 'upstream/master' into pr/tisilent/4703

This commit is contained in:
Daniel Imms
2023-11-02 10:39:15 -07:00
295 changed files with 2464 additions and 707 deletions
+1
View File
@@ -113,6 +113,7 @@ export namespace css {
let $ctx: CanvasRenderingContext2D | undefined;
let $litmusColor: CanvasGradient | undefined;
if (!isNode) {
// This is guaranteed to run in the first window, so document should be correct
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
+5
View File
@@ -71,3 +71,8 @@ export class EventEmitter<T, U = void> implements IEventEmitter<T, U> {
export function forwardEvent<T>(from: IEvent<T>, to: IEventEmitter<T>): IDisposable {
return from(e => to.fire(e));
}
export function runAndSubscribe<T>(event: IEvent<T>, handler: (e: T | undefined) => any): IDisposable {
handler(undefined);
return event(e => handler(e));
}
+71 -53
View File
@@ -10,13 +10,14 @@ import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets';
import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
import { Disposable } from 'common/Lifecycle';
import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32 } from 'common/input/TextDecoder';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { EventEmitter } from 'common/EventEmitter';
import { IParsingState, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types';
import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content, UnderlineStyle } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { AttributeData } from 'common/buffer/AttributeData';
import { ICoreService, IBufferService, IOptionsService, ILogService, ICoreMouseService, ICharsetService, IUnicodeService, LogLevelEnum, IOscLinkService } from 'common/services/Services';
import { UnicodeService } from 'common/services/UnicodeService';
import { OscHandler } from 'common/parser/OscParser';
import { DcsHandler } from 'common/parser/DcsParser';
import { IBuffer } from 'common/buffer/Types';
@@ -48,7 +49,7 @@ const GLEVEL: { [key: string]: number } = { '(': 0, ')': 1, '*': 2, '+': 3, '-':
/**
* Document xterm VT features here that are currently unsupported
*/
// @vt: #E[Supported via xterm-addon-image.] DCS SIXEL "SIXEL Graphics" "DCS Ps ; Ps ; Ps ; q Pt ST" "Draw SIXEL image."
// @vt: #E[Supported via @xterm/addon-image.] DCS SIXEL "SIXEL Graphics" "DCS Ps ; Ps ; Ps ; q Pt ST" "Draw SIXEL image."
// @vt: #N DCS DECUDK "User Defined Keys" "DCS Ps ; Ps \| Pt ST" "Definitions for user-defined keys."
// @vt: #N DCS XTGETTCAP "Request Terminfo String" "DCS + q Pt ST" "Request Terminfo String."
// @vt: #N DCS XTSETTCAP "Set Terminfo Data" "DCS + p Pt ST" "Set Terminfo Data."
@@ -494,8 +495,13 @@ export class InputHandler extends Disposable implements IInputHandler {
this._onCursorMove.fire();
}
// Refresh any dirty rows accumulated as part of parsing
this._onRequestRefreshRows.fire(this._dirtyRowTracker.start, this._dirtyRowTracker.end);
// Refresh any dirty rows accumulated as part of parsing, fire only for rows within the
// _viewport_ which is relative to ydisp, not relative to ybase.
const viewportEnd = this._dirtyRowTracker.end + (this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp);
const viewportStart = this._dirtyRowTracker.start + (this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp);
if (viewportStart < this._bufferService.rows) {
this._onRequestRefreshRows.fire(Math.min(viewportStart, this._bufferService.rows - 1), Math.min(viewportEnd, this._bufferService.rows - 1));
}
}
public print(data: Uint32Array, start: number, end: number): void {
@@ -516,13 +522,10 @@ export class InputHandler extends Disposable implements IInputHandler {
bufferRow.setCellFromCodePoint(this._activeBuffer.x - 1, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
}
let precedingJoinState = this._parser.precedingJoinState;
for (let pos = start; pos < end; ++pos) {
code = data[pos];
// calculate print space
// expensive call, therefore we save width in line buffer
chWidth = this._unicodeService.wcwidth(code);
// get charset replacement character
// charset is only defined for ASCII, therefore we only
// search for an replacement char if code < 127
@@ -533,6 +536,12 @@ export class InputHandler extends Disposable implements IInputHandler {
}
}
const currentInfo = this._unicodeService.charProperties(code, precedingJoinState);
chWidth = UnicodeService.extractWidth(currentInfo);
const shouldJoin = UnicodeService.extractShouldJoin(currentInfo);
const oldWidth = shouldJoin ? UnicodeService.extractWidth(precedingJoinState) : 0;
precedingJoinState = currentInfo;
if (screenReaderMode) {
this._onA11yChar.fire(stringFromCodePoint(code));
}
@@ -540,34 +549,16 @@ export class InputHandler extends Disposable implements IInputHandler {
this._oscLinkService.addLineToLink(this._getCurrentLinkId(), this._activeBuffer.ybase + this._activeBuffer.y);
}
// insert combining char at last cursor position
// this._activeBuffer.x should never be 0 for a combining char
// since they always follow a cell consuming char
// therefore we can test for this._activeBuffer.x to avoid overflow left
if (!chWidth && this._activeBuffer.x) {
if (!bufferRow.getWidth(this._activeBuffer.x - 1)) {
// found empty cell after fullwidth, need to go 2 cells back
// it is save to step 2 cells back here
// since an empty cell is only set by fullwidth chars
bufferRow.addCodepointToCell(this._activeBuffer.x - 2, code);
} else {
bufferRow.addCodepointToCell(this._activeBuffer.x - 1, code);
}
continue;
}
// goto next line if ch would overflow
// NOTE: To avoid costly width checks here,
// the terminal does not allow a cols < 2.
if (this._activeBuffer.x + chWidth - 1 >= cols) {
if (this._activeBuffer.x + chWidth - oldWidth > cols) {
// autowrap - DECAWM
// automatically wraps to the beginning of the next line
if (wraparoundMode) {
// clear left over cells to the right
while (this._activeBuffer.x < cols) {
bufferRow.setCellFromCodePoint(this._activeBuffer.x++, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
}
this._activeBuffer.x = 0;
const oldRow = bufferRow;
let oldCol = this._activeBuffer.x - oldWidth;
this._activeBuffer.x = oldWidth;
this._activeBuffer.y++;
if (this._activeBuffer.y === this._activeBuffer.scrollBottom + 1) {
this._activeBuffer.y--;
@@ -582,6 +573,16 @@ export class InputHandler extends Disposable implements IInputHandler {
}
// row changed, get it again
bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!;
if (oldWidth > 0 && bufferRow instanceof BufferLine) {
// Combining character widens 1 column to 2.
// Move old character to next line.
bufferRow.copyCellsFrom(oldRow as BufferLine,
oldCol, 0, oldWidth, false);
}
// clear left over cells to the right
while (oldCol < cols) {
oldRow.setCellFromCodePoint(oldCol++, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
}
} else {
this._activeBuffer.x = cols - 1;
if (chWidth === 2) {
@@ -592,10 +593,27 @@ export class InputHandler extends Disposable implements IInputHandler {
}
}
// insert combining char at last cursor position
// this._activeBuffer.x should never be 0 for a combining char
// since they always follow a cell consuming char
// therefore we can test for this._activeBuffer.x to avoid overflow left
if (shouldJoin && this._activeBuffer.x) {
const offset = bufferRow.getWidth(this._activeBuffer.x - 1) ? 1 : 2;
// if empty cell after fullwidth, need to go 2 cells back
// it is save to step 2 cells back here
// since an empty cell is only set by fullwidth chars
bufferRow.addCodepointToCell(this._activeBuffer.x - offset,
code, chWidth);
for (let delta = chWidth - oldWidth; --delta >= 0; ) {
bufferRow.setCellFromCodePoint(this._activeBuffer.x++, 0, 0, curAttr.fg, curAttr.bg, curAttr.extended);
}
continue;
}
// insert mode: move characters to right
if (insertMode) {
// right shift cells according to the width
bufferRow.insertCells(this._activeBuffer.x, chWidth, this._activeBuffer.getNullCell(curAttr), curAttr);
bufferRow.insertCells(this._activeBuffer.x, chWidth - oldWidth, this._activeBuffer.getNullCell(curAttr), curAttr);
// test last cell - since the last cell has only room for
// a halfwidth char any fullwidth shifted there is lost
// and will be set to empty cell
@@ -617,20 +635,8 @@ export class InputHandler extends Disposable implements IInputHandler {
}
}
}
// store last char in Parser.precedingCodepoint for REP to work correctly
// This needs to check whether:
// - fullwidth + surrogates: reset
// - combining: only base char gets carried on (bug in xterm?)
if (end - start > 0) {
bufferRow.loadCell(this._activeBuffer.x - 1, this._workCell);
if (this._workCell.getWidth() === 2 || this._workCell.getCode() > 0xFFFF) {
this._parser.precedingCodepoint = 0;
} else if (this._workCell.isCombined()) {
this._parser.precedingCodepoint = this._workCell.getChars().charCodeAt(0);
} else {
this._parser.precedingCodepoint = this._workCell.content;
}
}
this._parser.precedingJoinState = precedingJoinState;
// handle wide chars: reset cell to the right if it is second cell of a wide char
if (this._activeBuffer.x < cols && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x) === 0 && !bufferRow.hasContent(this._activeBuffer.x)) {
@@ -1576,9 +1582,8 @@ export class InputHandler extends Disposable implements IInputHandler {
* If the character preceding REP is a control function or part of a control function,
* the effect of REP is not defined by this Standard.
*
* Since we propagate the terminal as xterm-256color we have to follow xterm's behavior:
* - fullwidth + surrogate chars are ignored
* - for combining chars only the base char gets repeated
* We extend xterm's behavior to allow repeating entire grapheme clusters.
* This isn't 100% xterm-compatible, but it seems saner and more useful.
* - text attrs are applied normally
* - wrap around is respected
* - any valid sequence resets the carried forward char
@@ -1592,16 +1597,29 @@ export class InputHandler extends Disposable implements IInputHandler {
* (NOOP for any other sequence in between or NON ASCII characters).
*/
public repeatPrecedingCharacter(params: IParams): boolean {
if (!this._parser.precedingCodepoint) {
const joinState = this._parser.precedingJoinState;
if (!joinState) {
return true;
}
// call print to insert the chars and handle correct wrapping
const length = params.params[0] || 1;
const data = new Uint32Array(length);
for (let i = 0; i < length; ++i) {
data[i] = this._parser.precedingCodepoint;
const chWidth = UnicodeService.extractWidth(joinState);
const x = this._activeBuffer.x - chWidth;
const bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!;
const text = bufferRow.getString(x);
const data = new Uint32Array(text.length * length);
let idata = 0;
for (let itext = 0; itext < text.length; ) {
const ch = text.codePointAt(itext) || 0;
data[idata++] = ch;
itext += ch > 0xffff ? 2 : 1;
}
this.print(data, 0, data.length);
let tlength = idata;
for (let i = 1; i < length; ++i) {
data.copyWithin(tlength, 0, idata);
tlength += idata;
}
this.print(data, 0, tlength);
return true;
}
+2 -1
View File
@@ -12,8 +12,9 @@ interface INavigator {
// We're declaring a navigator global here as we expect it in all runtimes (node and browser), but
// we want this module to live in common.
declare const navigator: INavigator;
declare const process: unknown;
export const isNode = (typeof navigator === 'undefined') ? true : false;
export const isNode = (typeof process !== 'undefined') ? true : false;
const userAgent = (isNode) ? 'node' : navigator.userAgent;
const platform = (isNode) ? 'node' : navigator.platform;
+18 -4
View File
@@ -3,7 +3,8 @@
* @license MIT
*/
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services';
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, ICoreMouseService, ICharsetService, UnicodeCharProperties, UnicodeCharWidth, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services';
import { UnicodeService } from 'common/services/UnicodeService';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { clone } from 'common/Clone';
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
@@ -11,7 +12,7 @@ import { IBufferSet, IBuffer } from 'common/buffer/Types';
import { BufferSet } from 'common/buffer/BufferSet';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes, IAttributeData, IOscLinkData, IDisposable } from 'common/Types';
import { UnicodeV6 } from 'common/input/UnicodeV6';
import { IDecorationOptions, IDecoration } from 'xterm';
import { IDecorationOptions, IDecoration } from '@xterm/xterm';
export class MockBufferService implements IBufferService {
public serviceBrand: any;
@@ -77,7 +78,7 @@ export class MockCharsetService implements ICharsetService {
export class MockCoreService implements ICoreService {
public serviceBrand: any;
public isCursorInitialized: boolean = false;
public isCursorInitialized: boolean = true;
public isCursorHidden: boolean = false;
public isFocused: boolean = false;
public modes: IModes = {
@@ -168,7 +169,20 @@ export class MockUnicodeService implements IUnicodeService {
public versions: string[] = [];
public activeVersion: string = '';
public onChange: IEvent<string> = new EventEmitter<string>().event;
public wcwidth = (codepoint: number): number => this._provider.wcwidth(codepoint);
public wcwidth = (codepoint: number): UnicodeCharWidth => this._provider.wcwidth(codepoint);
public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties {
let width = this.wcwidth(codepoint);
let shouldJoin = width === 0 && preceding !== 0;
if (shouldJoin) {
const oldWidth = UnicodeService.extractWidth(preceding);
if (oldWidth === 0) {
shouldJoin = false;
} else if (oldWidth > width) {
width = oldWidth;
}
}
return UnicodeService.createPropertyValue(0, width, shouldJoin);
}
public getStringCellWidth(s: string): number {
throw new Error('Method not implemented.');
}
+2 -2
View File
@@ -9,7 +9,7 @@ import { Attributes, UnderlineStyle } from 'common/buffer/Constants'; // eslint-
import { IBufferSet } from 'common/buffer/Types';
import { IParams } from 'common/parser/Types';
import { ICoreMouseService, ICoreService, IOptionsService, IUnicodeService } from 'common/services/Services';
import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from 'xterm';
import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from '@xterm/xterm';
export interface ICoreTerminal {
coreMouseService: ICoreMouseService;
@@ -236,7 +236,7 @@ export interface IBufferLine {
loadCell(index: number, cell: ICellData): ICellData;
setCell(index: number, cell: ICellData): void;
setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number, eAttrs: IExtendedAttrs): void;
addCodepointToCell(index: number, codePoint: number): void;
addCodepointToCell(index: number, codePoint: number, width: number): void;
insertCells(pos: number, n: number, ch: ICellData, eraseAttr?: IAttributeData): void;
deleteCells(pos: number, n: number, fill: ICellData, eraseAttr?: IAttributeData): void;
replaceCells(start: number, end: number, fill: ICellData, eraseAttr?: IAttributeData, respectProtect?: boolean): void;
+3 -3
View File
@@ -443,7 +443,7 @@ describe('BufferLine', function(): void {
describe('addCharToCell', () => {
it('should set width to 1 for empty cell', () => {
const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.addCodepointToCell(0, '\u0301'.charCodeAt(0));
line.addCodepointToCell(0, '\u0301'.charCodeAt(0), 0);
const cell = line.loadCell(0, new CellData());
// chars contains single combining char
// width is set to 1
@@ -456,7 +456,7 @@ describe('BufferLine', function(): void {
const cell = line .loadCell(0, new CellData());
cell.setFromCharData([123, 'e\u0301', 1, 'e\u0301'.charCodeAt(1)]);
line.setCell(0, cell);
line.addCodepointToCell(0, '\u0301'.charCodeAt(0));
line.addCodepointToCell(0, '\u0301'.charCodeAt(0), 0);
line.loadCell(0, cell);
// chars contains 3 chars
// width is set to 1
@@ -469,7 +469,7 @@ describe('BufferLine', function(): void {
const cell = line .loadCell(0, new CellData());
cell.setFromCharData([123, 'e', 1, 'e'.charCodeAt(1)]);
line.setCell(0, cell);
line.addCodepointToCell(0, '\u0301'.charCodeAt(0));
line.addCodepointToCell(0, '\u0301'.charCodeAt(0), 0);
line.loadCell(0, cell);
// chars contains 2 chars
// width is set to 1
+6 -2
View File
@@ -227,7 +227,7 @@ export class BufferLine implements IBufferLine {
* onto a leading char. Since we already set the attrs
* by the previous `setDataFromCodePoint` call, we can omit it here.
*/
public addCodepointToCell(index: number, codePoint: number): void {
public addCodepointToCell(index: number, codePoint: number, width: number): void {
let content = this._data[index * CELL_SIZE + Cell.CONTENT];
if (content & Content.IS_COMBINED_MASK) {
// we already have a combined string, simply add
@@ -245,8 +245,12 @@ export class BufferLine implements IBufferLine {
// simply set the data in the cell buffer with a width of 1
content = codePoint | (1 << Content.WIDTH_SHIFT);
}
this._data[index * CELL_SIZE + Cell.CONTENT] = content;
}
if (width) {
content &= ~Content.WIDTH_MASK;
content |= width << Content.WIDTH_SHIFT;
}
this._data[index * CELL_SIZE + Cell.CONTENT] = content;
}
public insertCells(pos: number, n: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void {
+1 -1
View File
@@ -5,7 +5,7 @@
import { assert } from 'chai';
import { getRangeLength } from 'common/buffer/BufferRange';
import { IBufferRange } from 'xterm';
import { IBufferRange } from '@xterm/xterm';
describe('BufferRange', () => {
describe('getRangeLength', () => {
+1 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IBufferRange } from 'xterm';
import { IBufferRange } from '@xterm/xterm';
export function getRangeLength(range: IBufferRange, bufferCols: number): number {
if (range.start.y > range.end.y) {
+15
View File
@@ -106,6 +106,21 @@ describe('Keyboard', () => {
it('should return \\x1b[5B for ctrl+down', () => {
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B
});
it('should return \\x08 for ctrl+backspace', () => {
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 8 }).key, '\x08');
});
it('should return \\x1b\\x7f for alt+backspace', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 8 }).key, '\x1b\x7f');
});
it('should return \\x1b\\x08 for ctrl+alt+backspace', () => {
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, altKey: true, keyCode: 8 }).key, '\x1b\x08');
});
it('should return \\x1b[3;2~ for shift+delete', () => {
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 46 }).key, '\x1b[3;2~');
});
it('should return \\x1b[3;3~ for alt+delete', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 46 }).key, '\x1b[3;3~');
});
describe('On non-macOS platforms', () => {
// Evalueate alt + arrow key movement, which is a feature of terminal emulators but not VT100
+2 -3
View File
@@ -83,11 +83,10 @@ export function evaluateKeyboardEvent(
break;
case 8:
// backspace
result.key = ev.ctrlKey ? '\b' : C0.DEL; // ^H or ^?
if (ev.altKey) {
result.key = C0.ESC + C0.DEL; // \e ^?
break;
result.key = C0.ESC + result.key;
}
result.key = C0.DEL; // ^?
break;
case 9:
// tab
+18 -5
View File
@@ -2,9 +2,8 @@
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IUnicodeVersionProvider } from 'common/services/Services';
type CharWidth = 0 | 1 | 2;
import { IUnicodeVersionProvider, UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';
import { UnicodeService } from 'common/services/UnicodeService';
const BMP_COMBINING = [
[0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
@@ -121,12 +120,26 @@ export class UnicodeV6 implements IUnicodeVersionProvider {
}
}
public wcwidth(num: number): CharWidth {
public wcwidth(num: number): UnicodeCharWidth {
if (num < 32) return 0;
if (num < 127) return 1;
if (num < 65536) return table[num] as CharWidth;
if (num < 65536) return table[num] as UnicodeCharWidth;
if (bisearch(num, HIGH_COMBINING)) return 0;
if ((num >= 0x20000 && num <= 0x2fffd) || (num >= 0x30000 && num <= 0x3fffd)) return 2;
return 1;
}
public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties {
let width = this.wcwidth(codepoint);
let shouldJoin = width === 0 && preceding !== 0;
if (shouldJoin) {
const oldWidth = UnicodeService.extractWidth(preceding);
if (oldWidth === 0) {
shouldJoin = false;
} else if (oldWidth > width) {
width = oldWidth;
}
}
return UnicodeService.createPropertyValue(0, width, shouldJoin);
}
}
+9 -9
View File
@@ -230,7 +230,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
export class EscapeSequenceParser extends Disposable implements IEscapeSequenceParser {
public initialState: number;
public currentState: number;
public precedingCodepoint: number;
public precedingJoinState: number; // UnicodeJoinProperties
// buffers over several parse calls
protected _params: Params;
@@ -271,7 +271,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._params = new Params(); // defaults to 32 storable params/subparams
this._params.addParam(0); // ZDM
this._collect = 0;
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
// set default fallback handlers and handler lookup containers
this._printHandlerFb = (data, start, end): void => { };
@@ -448,7 +448,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._params.reset();
this._params.addParam(0); // ZDM
this._collect = 0;
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
// abort pending continuation from async handler
// Here the RESET type indicates, that the next parse call will
// ignore any saved stack, instead continues sync with next codepoint from GROUND
@@ -610,7 +610,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
// cleanup before continuing with the main sync loop
this._parseStack.state = ParserStackType.NONE;
start = this._parseStack.chunkPos + 1;
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
this.currentState = this._parseStack.transition & TableAccess.TRANSITION_STATE_MASK;
}
}
@@ -653,7 +653,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
case ParserAction.EXECUTE:
if (this._executeHandlers[code]) this._executeHandlers[code]();
else this._executeHandlerFb(code);
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
break;
case ParserAction.IGNORE:
break;
@@ -688,7 +688,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
if (j < 0) {
this._csiHandlerFb(this._collect << 8 | code, this._params);
}
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
break;
case ParserAction.PARAM:
// inner loop: digits (0x30 - 0x39) and ; (0x3b) and : (0x3a)
@@ -727,7 +727,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
if (jj < 0) {
this._escHandlerFb(this._collect << 8 | code);
}
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
break;
case ParserAction.CLEAR:
this._params.reset();
@@ -758,7 +758,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._params.reset();
this._params.addParam(0); // ZDM
this._collect = 0;
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
break;
case ParserAction.OSC_START:
this._oscParser.start();
@@ -783,7 +783,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._params.reset();
this._params.addParam(0); // ZDM
this._collect = 0;
this.precedingCodepoint = 0;
this.precedingJoinState = 0;
break;
}
this.currentState = transition & TableAccess.TRANSITION_STATE_MASK;
+5 -4
View File
@@ -146,11 +146,12 @@ export type PrintFallbackHandlerType = PrintHandlerType;
*/
export interface IEscapeSequenceParser extends IDisposable {
/**
* Preceding codepoint to get REP working correctly.
* This must be set by the print handler as last action.
* It gets reset by the parser for any valid sequence beside REP itself.
* Preceding grapheme-join-state.
* Used for joining grapheme clusters across calls to `print`.
* Also used by REP to check if repeating a character is allowed.
* It gets reset by the parser for any valid sequence besides text.
*/
precedingCodepoint: number;
precedingJoinState: number; // More specifically: UnicodeJoinProperties
/**
* Reset the parser to its initial state (handlers are kept).
+1 -1
View File
@@ -5,7 +5,7 @@
import { assert } from 'chai';
import { AddonManager, ILoadedAddon } from './AddonManager';
import { ITerminalAddon } from 'xterm';
import { ITerminalAddon } from '@xterm/xterm';
class TestAddonManager extends AddonManager {
public get addons(): ILoadedAddon[] {
+1 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { ITerminalAddon, IDisposable, Terminal } from 'xterm';
import { ITerminalAddon, IDisposable, Terminal } from '@xterm/xterm';
export interface ILoadedAddon {
instance: ITerminalAddon;
+1 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm';
import { IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from '@xterm/xterm';
import { IBuffer } from 'common/buffer/Types';
import { BufferLineApiView } from 'common/public/BufferLineApiView';
import { CellData } from 'common/buffer/CellData';
+1 -1
View File
@@ -5,7 +5,7 @@
import { CellData } from 'common/buffer/CellData';
import { IBufferLine, ICellData } from 'common/Types';
import { IBufferCell as IBufferCellApi, IBufferLine as IBufferLineApi } from 'xterm';
import { IBufferCell as IBufferCellApi, IBufferLine as IBufferLineApi } from '@xterm/xterm';
export class BufferLineApiView implements IBufferLineApi {
constructor(private _line: IBufferLine) { }
+1 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from 'xterm';
import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from '@xterm/xterm';
import { BufferApiView } from 'common/public/BufferApiView';
import { EventEmitter } from 'common/EventEmitter';
import { ICoreTerminal } from 'common/Types';
+1 -1
View File
@@ -4,7 +4,7 @@
*/
import { IParams } from 'common/parser/Types';
import { IDisposable, IFunctionIdentifier, IParser } from 'xterm';
import { IDisposable, IFunctionIdentifier, IParser } from '@xterm/xterm';
import { ICoreTerminal } from 'common/Types';
export class ParserApi implements IParser {
+1 -1
View File
@@ -4,7 +4,7 @@
*/
import { ICoreTerminal } from 'common/Types';
import { IUnicodeHandling, IUnicodeVersionProvider } from 'xterm';
import { IUnicodeHandling, IUnicodeVersionProvider } from '@xterm/xterm';
export class UnicodeApi implements IUnicodeHandling {
constructor(private _core: ICoreTerminal) { }
+1 -1
View File
@@ -9,7 +9,7 @@ import { Disposable, toDisposable } from 'common/Lifecycle';
import { IDecorationService, IInternalDecoration } from 'common/services/Services';
import { SortedList } from 'common/SortedList';
import { IColor } from 'common/Types';
import { IDecoration, IDecorationOptions, IMarker } from 'xterm';
import { IDecoration, IDecorationOptions, IMarker } from '@xterm/xterm';
// Work variables to avoid garbage collection
let $xmin = 0;
+1
View File
@@ -18,6 +18,7 @@ export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
cursorInactiveStyle: 'outline',
customGlyphs: true,
drawBoldTextInBrightColors: true,
documentOverride: null,
fastScrollModifier: 'alt',
fastScrollSensitivity: 5,
fontFamily: 'courier-new, courier, monospace',
+34 -3
View File
@@ -7,7 +7,7 @@ import { IEvent, IEventEmitter } from 'common/EventEmitter';
import { IBuffer, IBufferSet } from 'common/buffer/Types';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColor, CursorStyle, CursorInactiveStyle, IOscLinkData } from 'common/Types';
import { createDecorator } from 'common/services/ServiceRegistry';
import { IDecorationOptions, IDecoration, ILinkHandler, IWindowsPty, ILogger } from 'xterm';
import { IDecorationOptions, IDecoration, ILinkHandler, IWindowsPty, ILogger } from '@xterm/xterm';
export const IBufferService = createDecorator<IBufferService>('BufferService');
export interface IBufferService {
@@ -217,6 +217,7 @@ export interface ITerminalOptions {
cursorInactiveStyle?: CursorInactiveStyle;
customGlyphs?: boolean;
disableStdin?: boolean;
documentOverride?: any | null;
drawBoldTextInBrightColors?: boolean;
fastScrollModifier?: 'none' | 'alt' | 'ctrl' | 'shift';
fastScrollSensitivity?: number;
@@ -296,6 +297,29 @@ export interface IOscLinkService {
getLinkData(linkId: number): IOscLinkData | undefined;
}
/*
* Width and Grapheme_Cluster_Break properties of a character as a bit mask.
*
* bit 0: shouldJoin - should combine with preceding character.
* bit 1..2: wcwidth - see UnicodeCharWidth.
* bit 3..31: class of character (currently only 4 bits are used).
* This is used to determined grapheme clustering - i.e. which codepoints
* are to be combined into a single compound character.
*
* Use the UnicodeService static function createPropertyValue to create a
* UnicodeCharProperties; use extractShouldJoin, extractWidth, and
* extractCharKind to extract the components.
*/
export type UnicodeCharProperties = number;
/**
* Width in columns of a character.
* In a CJK context, "half-width" characters (such as Latin) are width 1,
* while "full-width" characters (such as Kanji) are 2 columns wide.
* Combining characters (such as accents) are width 0.
*/
export type UnicodeCharWidth = 0 | 1 | 2;
export const IUnicodeService = createDecorator<IUnicodeService>('UnicodeService');
export interface IUnicodeService {
serviceBrand: undefined;
@@ -311,13 +335,20 @@ export interface IUnicodeService {
/**
* Unicode version dependent
*/
wcwidth(codepoint: number): number;
wcwidth(codepoint: number): UnicodeCharWidth;
getStringCellWidth(s: string): number;
/**
* Return character width and type for grapheme clustering.
* If preceding != 0, it is the return code from the previous character;
* in that case the result specifies if the characters should be joined.
*/
charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties;
}
export interface IUnicodeVersionProvider {
readonly version: string;
wcwidth(ucs: number): 0 | 1 | 2;
wcwidth(ucs: number): UnicodeCharWidth;
charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties;
}
export const IDecorationService = createDecorator<IDecorationService>('DecorationService');
@@ -12,6 +12,9 @@ class DummyProvider implements IUnicodeVersionProvider {
public wcwidth(n: number): 0 | 1 | 2 {
return 2;
}
public charProperties(codepoint: number): number {
return UnicodeService.createPropertyValue(0, this.wcwidth(codepoint));
}
}
describe('unicode provider', () => {
+28 -3
View File
@@ -2,9 +2,10 @@
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { EventEmitter } from 'common/EventEmitter';
import { UnicodeV6 } from 'common/input/UnicodeV6';
import { IUnicodeService, IUnicodeVersionProvider } from 'common/services/Services';
import { IUnicodeService, IUnicodeVersionProvider, UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';
export class UnicodeService implements IUnicodeService {
public serviceBrand: any;
@@ -16,6 +17,19 @@ export class UnicodeService implements IUnicodeService {
private readonly _onChange = new EventEmitter<string>();
public readonly onChange = this._onChange.event;
public static extractShouldJoin(value: UnicodeCharProperties): boolean {
return (value & 1) !== 0;
}
public static extractWidth(value: UnicodeCharProperties): UnicodeCharWidth {
return ((value >> 1) & 0x3) as UnicodeCharWidth;
}
public static extractCharKind(value: UnicodeCharProperties): number {
return value >> 3;
}
public static createPropertyValue(state: number, width: number, shouldJoin: boolean = false): UnicodeCharProperties {
return ((state & 0xffffff) << 3) | ((width & 3) << 1) | (shouldJoin?1:0);
}
constructor() {
const defaultProvider = new UnicodeV6();
this.register(defaultProvider);
@@ -51,12 +65,13 @@ export class UnicodeService implements IUnicodeService {
/**
* Unicode version dependent interface.
*/
public wcwidth(num: number): number {
public wcwidth(num: number): UnicodeCharWidth {
return this._activeProvider.wcwidth(num);
}
public getStringCellWidth(s: string): number {
let result = 0;
let precedingInfo = 0;
const length = s.length;
for (let i = 0; i < length; ++i) {
let code = s.charCodeAt(i);
@@ -79,8 +94,18 @@ export class UnicodeService implements IUnicodeService {
result += this.wcwidth(second);
}
}
result += this.wcwidth(code);
const currentInfo = this.charProperties(code, precedingInfo);
let chWidth = UnicodeService.extractWidth(currentInfo);
if (UnicodeService.extractShouldJoin(currentInfo)) {
chWidth -= UnicodeService.extractWidth(precedingInfo);
}
result += chWidth;
precedingInfo = currentInfo;
}
return result;
}
public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties {
return this._activeProvider.charProperties(codepoint, preceding);
}
}