Merge remote-tracking branch 'upstream/master' into pr/tisilent/4703
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Vendored
+5
-4
@@ -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).
|
||||
|
||||
@@ -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[] {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminalAddon, IDisposable, Terminal } from 'xterm';
|
||||
import { ITerminalAddon, IDisposable, Terminal } from '@xterm/xterm';
|
||||
|
||||
export interface ILoadedAddon {
|
||||
instance: ITerminalAddon;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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) { }
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) { }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user