From 012468785b13ab9035dff84ed1f74424eed24bd4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 16 Aug 2021 12:23:34 -0700 Subject: [PATCH 1/9] Clean up serialize addon --- .../src/SerializeAddon.ts | 93 +++++----- .../test/SerializeAddon.api.ts | 164 +++++++++--------- .../typings/xterm-addon-serialize.d.ts | 14 +- 3 files changed, 134 insertions(+), 137 deletions(-) diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index 0caaaa03..5cd834b8 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -13,7 +13,10 @@ function constrain(value: number, low: number, high: number): number { // TODO: Refine this template class later abstract class BaseSerializeHandler { - constructor(private _buffer: IBuffer) { } + constructor( + protected readonly _buffer: IBuffer + ) { + } public serialize(startRow: number, endRow: number): string { // we need two of them to flip between old and new cell @@ -71,8 +74,6 @@ function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean { && cell1.isDim() === cell2.isDim(); } - - class StringSerializeHandler extends BaseSerializeHandler { private _rowIndex: number = 0; private _allRows: string[] = new Array(); @@ -83,7 +84,7 @@ class StringSerializeHandler extends BaseSerializeHandler { // we can see a full colored cell and a null cell that only have background the same style // but the information isn't preserved by null cell itself // so wee need to record it when required. - private _cursorStyle: IBufferCell = this._buffer1.getNullCell(); + private _cursorStyle: IBufferCell = this._buffer.getNullCell(); // where exact the cursor styles comes from // because we can't copy the cell directly @@ -92,7 +93,7 @@ class StringSerializeHandler extends BaseSerializeHandler { private _cursorStyleCol: number = 0; // this is a null cell for reference for checking whether background is empty or not - private _backgroundCell: IBufferCell = this._buffer1.getNullCell(); + private _backgroundCell: IBufferCell = this._buffer.getNullCell(); private _firstRow: number = 0; private _lastCursorRow: number = 0; @@ -100,8 +101,11 @@ class StringSerializeHandler extends BaseSerializeHandler { private _lastContentCursorRow: number = 0; private _lastContentCursorCol: number = 0; - constructor(private _buffer1: IBuffer, private _terminal: Terminal) { - super(_buffer1); + constructor( + buffer: IBuffer, + private readonly _terminal: Terminal + ) { + super(buffer); } protected _beforeSerialize(rows: number, start: number, end: number): void { @@ -111,14 +115,14 @@ class StringSerializeHandler extends BaseSerializeHandler { this._firstRow = start; } - private _thisRowLastChar: IBufferCell = this._buffer1.getNullCell(); - private _thisRowLastSecondChar: IBufferCell = this._buffer1.getNullCell(); - private _nextRowFirstChar: IBufferCell = this._buffer1.getNullCell(); + private _thisRowLastChar: IBufferCell = this._buffer.getNullCell(); + private _thisRowLastSecondChar: IBufferCell = this._buffer.getNullCell(); + private _nextRowFirstChar: IBufferCell = this._buffer.getNullCell(); protected _rowEnd(row: number, isLastRow: boolean): void { // if there is colorful empty cell at line end, whe must pad it back, or the the color block will missing if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) { // use clear right to set background. - this._currentRow += `\x1b[${this._nullCellCount}X`; + this._currentRow += `\u001b[${this._nullCellCount}X`; } let rowSeparator = ''; @@ -127,13 +131,13 @@ class StringSerializeHandler extends BaseSerializeHandler { if (!isLastRow) { // Enable BCE if (row - this._firstRow >= this._terminal.rows) { - this._buffer1.getLine(this._cursorStyleRow)?.getCell(this._cursorStyleCol, this._backgroundCell); + this._buffer.getLine(this._cursorStyleRow)?.getCell(this._cursorStyleCol, this._backgroundCell); } // Fetch current line - const currentLine = this._buffer1.getLine(row)!; + const currentLine = this._buffer.getLine(row)!; // Fetch next line - const nextLine = this._buffer1.getLine(row + 1)!; + const nextLine = this._buffer.getLine(row + 1)!; if (!nextLine.isWrapped) { // just insert the line break @@ -187,15 +191,15 @@ class StringSerializeHandler extends BaseSerializeHandler { // insert enough character to force the wrap rowSeparator = '-'.repeat(this._nullCellCount + 1); // move back and erase next line head - rowSeparator += '\x1b[1D\x1b[1X'; + rowSeparator += '\u001b[1D\u001b[1X'; if (this._nullCellCount > 0) { // do these because we filled the last several null slot, which we shouldn't - rowSeparator += '\x1b[A'; - rowSeparator += `\x1b[${currentLine.length - this._nullCellCount}C`; - rowSeparator += `\x1b[${this._nullCellCount}X`; - rowSeparator += `\x1b[${currentLine.length - this._nullCellCount}D`; - rowSeparator += '\x1b[B'; + rowSeparator += '\u001b[A'; + rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}C`; + rowSeparator += `\u001b[${this._nullCellCount}X`; + rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}D`; + rowSeparator += '\u001b[B'; } // This is content and need the be serialized even it is invisible. @@ -285,20 +289,20 @@ class StringSerializeHandler extends BaseSerializeHandler { if (this._nullCellCount > 0) { // use clear right to set background. if (!equalBg(this._cursorStyle, this._backgroundCell)) { - this._currentRow += `\x1b[${this._nullCellCount}X`; + this._currentRow += `\u001b[${this._nullCellCount}X`; } // use move right to move cursor. - this._currentRow += `\x1b[${this._nullCellCount}C`; + this._currentRow += `\u001b[${this._nullCellCount}C`; this._nullCellCount = 0; } this._lastContentCursorRow = this._lastCursorRow = row; this._lastContentCursorCol = this._lastCursorCol = col; - this._currentRow += `\x1b[${sgrSeq.join(';')}m`; + this._currentRow += `\u001b[${sgrSeq.join(';')}m`; // update the last cursor style - const line = this._buffer1.getLine(row); + const line = this._buffer.getLine(row); if (line !== undefined) { line.getCell(col, this._cursorStyle); this._cursorStyleRow = row; @@ -317,10 +321,10 @@ class StringSerializeHandler extends BaseSerializeHandler { // because style change is handled by previous stage // use move right when background is empty, use clear right when there is background. if (equalBg(this._cursorStyle, this._backgroundCell)) { - this._currentRow += `\x1b[${this._nullCellCount}C`; + this._currentRow += `\u001b[${this._nullCellCount}C`; } else { - this._currentRow += `\x1b[${this._nullCellCount}X`; - this._currentRow += `\x1b[${this._nullCellCount}C`; + this._currentRow += `\u001b[${this._nullCellCount}X`; + this._currentRow += `\u001b[${this._nullCellCount}C`; } this._nullCellCount = 0; } @@ -338,7 +342,7 @@ class StringSerializeHandler extends BaseSerializeHandler { // the fixup is only required for data without scrollback // because it will always be placed at last line otherwise - if (this._buffer1.length - this._firstRow <= this._terminal.rows) { + if (this._buffer.length - this._firstRow <= this._terminal.rows) { rowEnd = this._lastContentCursorRow + 1 - this._firstRow; this._lastCursorCol = this._lastContentCursorCol; this._lastCursorRow = this._lastContentCursorRow; @@ -354,8 +358,8 @@ class StringSerializeHandler extends BaseSerializeHandler { } // restore the cursor - const realCursorRow = this._buffer1.baseY + this._buffer1.cursorY; - const realCursorCol = this._buffer1.cursorX; + const realCursorRow = this._buffer.baseY + this._buffer.cursorY; + const realCursorCol = this._buffer.cursorX; const cursorMoved = (realCursorRow !== this._lastCursorRow || realCursorCol !== this._lastCursorCol); @@ -379,7 +383,6 @@ class StringSerializeHandler extends BaseSerializeHandler { moveRight(realCursorCol - this._lastCursorCol); } - return content; } } @@ -393,14 +396,11 @@ export class SerializeAddon implements ITerminalAddon { this._terminal = terminal; } - private _getString(buffer: IBuffer, scrollback?: number): string { + private _serializeBuffer(terminal: Terminal, buffer: IBuffer, scrollback?: number): string { const maxRows = buffer.length; - const handler = new StringSerializeHandler(buffer, this._terminal!); - - const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + this!._terminal!.rows, 0, maxRows); - const result = handler.serialize(maxRows - correctRows, maxRows); - - return result; + const handler = new StringSerializeHandler(buffer, terminal); + const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + terminal.rows, 0, maxRows); + return handler.serialize(maxRows - correctRows, maxRows); } public serialize(scrollback?: number): string { @@ -409,17 +409,16 @@ export class SerializeAddon implements ITerminalAddon { throw new Error('Cannot use addon until it has been loaded'); } - if (this._terminal.buffer.active.type === 'normal') { - return this._getString(this._terminal.buffer.active, scrollback); + // Normal buffer + let content = this._serializeBuffer(this._terminal, this._terminal.buffer.normal, scrollback); + + // Alternate buffer + if (this._terminal.buffer.active.type === 'alternate') { + const alternativeScreenContent = this._serializeBuffer(this._terminal, this._terminal.buffer.alternate, undefined); + content += `\u001b[?1049h\u001b[H${alternativeScreenContent}`; } - const normalScreenContent = this._getString(this._terminal.buffer.normal, scrollback); - // alt screen don't have scrollback - const alternativeScreenContent = this._getString(this._terminal.buffer.alternate, undefined); - - return normalScreenContent - + '\u001b[?1049h\u001b[H' - + alternativeScreenContent; + return content; } public dispose(): void { } diff --git a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts index c46b3815..c8b7302f 100644 --- a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts +++ b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts @@ -89,8 +89,6 @@ describe('SerializeAddon', () => { }); it('empty content', async function(): Promise { - const rows = 10; - const cols = 10; assert.equal(await page.evaluate(`serializeAddon.serialize();`), ''); }); @@ -177,17 +175,17 @@ describe('SerializeAddon', () => { const cols = 10; const line = '+'.repeat(cols); const lines: string[] = [ - mkSGR(FG_P16_GREEN) + line, // Workaround: If we clear all flags a the end, serialize will use \x1b[0m to clear instead of the sepcific disable sequence - mkSGR(INVERSE) + line, - mkSGR(BOLD) + line, - mkSGR(UNDERLINED) + line, - mkSGR(BLINK) + line, - mkSGR(INVISIBLE) + line, - mkSGR(NO_INVERSE) + line, - mkSGR(NO_BOLD) + line, - mkSGR(NO_UNDERLINED) + line, - mkSGR(NO_BLINK) + line, - mkSGR(NO_INVISIBLE) + line + sgr(FG_P16_GREEN) + line, // Workaround: If we clear all flags a the end, serialize will use \x1b[0m to clear instead of the sepcific disable sequence + sgr(INVERSE) + line, + sgr(BOLD) + line, + sgr(UNDERLINED) + line, + sgr(BLINK) + line, + sgr(INVISIBLE) + line, + sgr(NO_INVERSE) + line, + sgr(NO_BOLD) + line, + sgr(NO_UNDERLINED) + line, + sgr(NO_BLINK) + line, + sgr(NO_INVISIBLE) + line ]; const rows = lines.length; await writeSync(page, lines.join('\\r\\n')); @@ -209,16 +207,16 @@ describe('SerializeAddon', () => { const cols = 10; const line = '+'.repeat(cols); const lines: string[] = [ - mkSGR(FG_P16_RED) + line, // fg Red, - mkSGR(UNDERLINED) + line, // fg Red, Underlined - mkSGR(FG_P16_GREEN) + line, // fg Green, Underlined - mkSGR(INVERSE) + line, // fg Green, Underlined, Inverse - mkSGR(NO_INVERSE) + line, // fg Green, Underlined - mkSGR(INVERSE) + line, // fg Green, Underlined, Inverse - mkSGR(BG_P16_YELLOW) + line, // fg Green, bg Yellow, Underlined, Inverse - mkSGR(FG_RESET) + line, // bg Yellow, Underlined, Inverse - mkSGR(BG_RESET) + line, // Underlined, Inverse - mkSGR(NORMAL) + line // Back to normal + sgr(FG_P16_RED) + line, // fg Red, + sgr(UNDERLINED) + line, // fg Red, Underlined + sgr(FG_P16_GREEN) + line, // fg Green, Underlined + sgr(INVERSE) + line, // fg Green, Underlined, Inverse + sgr(NO_INVERSE) + line, // fg Green, Underlined + sgr(INVERSE) + line, // fg Green, Underlined, Inverse + sgr(BG_P16_YELLOW) + line, // fg Green, bg Yellow, Underlined, Inverse + sgr(FG_RESET) + line, // bg Yellow, Underlined, Inverse + sgr(BG_RESET) + line, // Underlined, Inverse + sgr(NORMAL) + line // Back to normal ]; await writeSync(page, lines.join('\\r\\n')); assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); @@ -228,19 +226,19 @@ describe('SerializeAddon', () => { const cols = 10; const line = '+'.repeat(cols); const lines: string[] = [ - mkSGR(FG_P16_RED) + line, // fg Red - mkSGR(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow - mkSGR(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic - mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow - mkSGR(FG_RESET, ITALIC) + line, // bg Yellow, Italic - mkSGR(BG_RESET) + line, // Italic - mkSGR(NORMAL) + line, // Back to normal - mkSGR(FG_P16_RED) + line, // fg Red - mkSGR(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow - mkSGR(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic - mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow - mkSGR(FG_RESET, ITALIC) + line, // bg Yellow, Italic - mkSGR(BG_RESET) + line // Italic + sgr(FG_P16_RED) + line, // fg Red + sgr(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow + sgr(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic + sgr(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow + sgr(FG_RESET, ITALIC) + line, // bg Yellow, Italic + sgr(BG_RESET) + line, // Italic + sgr(NORMAL) + line, // Back to normal + sgr(FG_P16_RED) + line, // fg Red + sgr(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow + sgr(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic + sgr(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow + sgr(FG_RESET, ITALIC) + line, // bg Yellow, Italic + sgr(BG_RESET) + line // Italic ]; await writeSync(page, lines.join('\\r\\n')); assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); @@ -250,16 +248,16 @@ describe('SerializeAddon', () => { const cols = 10; const line = '+'.repeat(cols); const lines: string[] = [ - mkSGR(FG_P256_RED) + line, // fg Red 256, - mkSGR(UNDERLINED) + line, // fg Red 256, Underlined - mkSGR(FG_P256_GREEN) + line, // fg Green 256, Underlined - mkSGR(INVERSE) + line, // fg Green 256, Underlined, Inverse - mkSGR(NO_INVERSE) + line, // fg Green 256, Underlined - mkSGR(INVERSE) + line, // fg Green 256, Underlined, Inverse - mkSGR(BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256, Underlined, Inverse - mkSGR(FG_RESET) + line, // bg Yellow 256, Underlined, Inverse - mkSGR(BG_RESET) + line, // Underlined, Inverse - mkSGR(NORMAL) + line // Back to normal + sgr(FG_P256_RED) + line, // fg Red 256, + sgr(UNDERLINED) + line, // fg Red 256, Underlined + sgr(FG_P256_GREEN) + line, // fg Green 256, Underlined + sgr(INVERSE) + line, // fg Green 256, Underlined, Inverse + sgr(NO_INVERSE) + line, // fg Green 256, Underlined + sgr(INVERSE) + line, // fg Green 256, Underlined, Inverse + sgr(BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256, Underlined, Inverse + sgr(FG_RESET) + line, // bg Yellow 256, Underlined, Inverse + sgr(BG_RESET) + line, // Underlined, Inverse + sgr(NORMAL) + line // Back to normal ]; await writeSync(page, lines.join('\\r\\n')); assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); @@ -269,19 +267,19 @@ describe('SerializeAddon', () => { const cols = 10; const line = '+'.repeat(cols); const lines: string[] = [ - mkSGR(FG_P256_RED) + line, // fg Red 256 - mkSGR(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256 - mkSGR(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic - mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256 - mkSGR(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic - mkSGR(BG_RESET) + line, // Italic - mkSGR(NORMAL) + line, // Back to normal - mkSGR(FG_P256_RED) + line, // fg Red 256 - mkSGR(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256 - mkSGR(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic - mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256 - mkSGR(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic - mkSGR(BG_RESET) + line // Italic + sgr(FG_P256_RED) + line, // fg Red 256 + sgr(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256 + sgr(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic + sgr(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256 + sgr(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic + sgr(BG_RESET) + line, // Italic + sgr(NORMAL) + line, // Back to normal + sgr(FG_P256_RED) + line, // fg Red 256 + sgr(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256 + sgr(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic + sgr(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256 + sgr(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic + sgr(BG_RESET) + line // Italic ]; await writeSync(page, lines.join('\\r\\n')); assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); @@ -291,16 +289,16 @@ describe('SerializeAddon', () => { const cols = 10; const line = '+'.repeat(cols); const lines: string[] = [ - mkSGR(FG_RGB_RED) + line, // fg Red RGB, - mkSGR(UNDERLINED) + line, // fg Red RGB, Underlined - mkSGR(FG_RGB_GREEN) + line, // fg Green RGB, Underlined - mkSGR(INVERSE) + line, // fg Green RGB, Underlined, Inverse - mkSGR(NO_INVERSE) + line, // fg Green RGB, Underlined - mkSGR(INVERSE) + line, // fg Green RGB, Underlined, Inverse - mkSGR(BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB, Underlined, Inverse - mkSGR(FG_RESET) + line, // bg Yellow RGB, Underlined, Inverse - mkSGR(BG_RESET) + line, // Underlined, Inverse - mkSGR(NORMAL) + line // Back to normal + sgr(FG_RGB_RED) + line, // fg Red RGB, + sgr(UNDERLINED) + line, // fg Red RGB, Underlined + sgr(FG_RGB_GREEN) + line, // fg Green RGB, Underlined + sgr(INVERSE) + line, // fg Green RGB, Underlined, Inverse + sgr(NO_INVERSE) + line, // fg Green RGB, Underlined + sgr(INVERSE) + line, // fg Green RGB, Underlined, Inverse + sgr(BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB, Underlined, Inverse + sgr(FG_RESET) + line, // bg Yellow RGB, Underlined, Inverse + sgr(BG_RESET) + line, // Underlined, Inverse + sgr(NORMAL) + line // Back to normal ]; await writeSync(page, lines.join('\\r\\n')); assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); @@ -310,19 +308,19 @@ describe('SerializeAddon', () => { const cols = 10; const line = '+'.repeat(cols); const lines: string[] = [ - mkSGR(FG_RGB_RED) + line, // fg Red RGB - mkSGR(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB - mkSGR(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic - mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB - mkSGR(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic - mkSGR(BG_RESET) + line, // Italic - mkSGR(NORMAL) + line, // Back to normal - mkSGR(FG_RGB_RED) + line, // fg Red RGB - mkSGR(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB - mkSGR(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic - mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB - mkSGR(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic - mkSGR(BG_RESET) + line // Italic + sgr(FG_RGB_RED) + line, // fg Red RGB + sgr(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB + sgr(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic + sgr(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB + sgr(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic + sgr(BG_RESET) + line, // Italic + sgr(NORMAL) + line, // Back to normal + sgr(FG_RGB_RED) + line, // fg Red RGB + sgr(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB + sgr(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic + sgr(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB + sgr(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic + sgr(BG_RESET) + line // Italic ]; await writeSync(page, lines.join('\\r\\n')); assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); @@ -503,7 +501,7 @@ function digitsString(length: number, from: number = 0, sgr: string = ''): strin return s; } -function mkSGR(...seq: string[]): string { +function sgr(...seq: string[]): string { return `\x1b[${seq.join(';')}m`; } diff --git a/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts b/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts index 58be9a97..b55ee303 100644 --- a/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts +++ b/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts @@ -3,7 +3,6 @@ * @license MIT */ - import { Terminal, ITerminalAddon } from 'xterm'; declare module 'xterm-addon-serialize' { @@ -21,12 +20,13 @@ declare module 'xterm-addon-serialize' { public activate(terminal: Terminal): void; /** - * Serializes terminal rows into a string that can be written back to the terminal - * to restore the state. The cursor will also be positioned to the correct cell. - * When restoring a terminal it is best to do before `Terminal.open` is called - * to avoid wasting CPU cycles rendering incomplete frames. - * @param scrollback The number of rows in scrollback buffer to serialize, starting from the bottom of the - * scrollback buffer. This defaults to the all available rows in the scrollback buffer. + * Serializes terminal rows into a string that can be written back to the terminal to restore + * the state. The cursor will also be positioned to the correct cell. When restoring a terminal + * it is best to do before `Terminal.open` is called to avoid wasting CPU cycles rendering + * incomplete frames. + * @param scrollback The number of rows in scrollback buffer to serialize, starting from the + * bottom of the scrollback buffer. This defaults to the all available rows in the scrollback + * buffer. */ public serialize(scrollback?: number): string; From 6affe68ca3504921f5e8c1d67a4c697b26c784cf Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 16 Aug 2021 14:44:39 -0700 Subject: [PATCH 2/9] Expose modes api Part of #3417 --- src/browser/Terminal.ts | 46 +++++++++++++++--------------- src/browser/TestUtils.test.ts | 4 ++- src/browser/public/Terminal.ts | 23 ++++++++++++++- src/common/CoreTerminal.ts | 22 +++++++------- src/common/Types.d.ts | 4 ++- typings/xterm.d.ts | 52 ++++++++++++++++++++++++++++++++++ 6 files changed, 114 insertions(+), 37 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 004f2314..cc6498bd 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -267,8 +267,8 @@ export class Terminal extends CoreTerminal implements ITerminal { * Binds the desired focus behavior on a given terminal object. */ private _onTextAreaFocus(ev: KeyboardEvent): void { - if (this._coreService.decPrivateModes.sendFocus) { - this._coreService.triggerDataEvent(C0.ESC + '[I'); + if (this.coreService.decPrivateModes.sendFocus) { + this.coreService.triggerDataEvent(C0.ESC + '[I'); } this.updateCursorStyle(ev); this.element!.classList.add('focus'); @@ -292,8 +292,8 @@ export class Terminal extends CoreTerminal implements ITerminal { // screen readers reading it out. this.textarea!.value = ''; this.refresh(this.buffer.y, this.buffer.y); - if (this._coreService.decPrivateModes.sendFocus) { - this._coreService.triggerDataEvent(C0.ESC + '[O'); + if (this.coreService.decPrivateModes.sendFocus) { + this.coreService.triggerDataEvent(C0.ESC + '[O'); } this.element!.classList.remove('focus'); this._onBlur.fire(); @@ -340,7 +340,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } copyHandler(event, this._selectionService!); })); - const pasteHandlerWrapper = (event: ClipboardEvent): void => handlePasteEvent(event, this.textarea!, this._coreService); + const pasteHandlerWrapper = (event: ClipboardEvent): void => handlePasteEvent(event, this.textarea!, this.coreService); this.register(addDisposableDomListener(this.textarea!, 'paste', pasteHandlerWrapper)); this.register(addDisposableDomListener(this.element!, 'paste', pasteHandlerWrapper)); @@ -526,7 +526,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); // apply mouse event classes set by escape codes before terminal was attached - if (this._coreMouseService.areMouseEventsActive) { + if (this.coreMouseService.areMouseEventsActive) { this._selectionService.disable(); this.element.classList.add('enable-mouse-events'); } else { @@ -644,7 +644,7 @@ export class Terminal extends CoreTerminal implements ITerminal { return false; } - return self._coreMouseService.triggerMouseEvent({ + return self.coreMouseService.triggerMouseEvent({ col: pos.x - 33, // FIXME: why -33 here? row: pos.y - 33, button: but, @@ -699,11 +699,11 @@ export class Terminal extends CoreTerminal implements ITerminal { } } }; - this.register(this._coreMouseService.onProtocolChange(events => { + this.register(this.coreMouseService.onProtocolChange(events => { // apply global changes on events if (events) { if (this.optionsService.options.logLevel === 'debug') { - this._logService.debug('Binding to mouse events:', this._coreMouseService.explainEvents(events)); + this._logService.debug('Binding to mouse events:', this.coreMouseService.explainEvents(events)); } this.element!.classList.add('enable-mouse-events'); this._selectionService!.disable(); @@ -746,7 +746,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } })); // force initial onProtocolChange so we dont miss early mouse requests - this._coreMouseService.activeProtocol = this._coreMouseService.activeProtocol; + this.coreMouseService.activeProtocol = this.coreMouseService.activeProtocol; /** * "Always on" event listeners. @@ -758,7 +758,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // Don't send the mouse button to the pty if mouse events are disabled or // if the selection manager is having selection forced (ie. a modifier is // held). - if (!this._coreMouseService.areMouseEventsActive || this._selectionService!.shouldForceSelection(ev)) { + if (!this.coreMouseService.areMouseEventsActive || this._selectionService!.shouldForceSelection(ev)) { return; } @@ -791,12 +791,12 @@ export class Terminal extends CoreTerminal implements ITerminal { } // Construct and send sequences - const sequence = C0.ESC + (this._coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B'); + const sequence = C0.ESC + (this.coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B'); let data = ''; for (let i = 0; i < Math.abs(amount); i++) { data += sequence; } - this._coreService.triggerDataEvent(data, true); + this.coreService.triggerDataEvent(data, true); } return; } @@ -812,13 +812,13 @@ export class Terminal extends CoreTerminal implements ITerminal { }, { passive: false })); this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => { - if (this._coreMouseService.areMouseEventsActive) return; + if (this.coreMouseService.areMouseEventsActive) return; this.viewport!.onTouchStart(ev); return this.cancel(ev); }, { passive: true })); this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => { - if (this._coreMouseService.areMouseEventsActive) return; + if (this.coreMouseService.areMouseEventsActive) return; if (!this.viewport!.onTouchMove(ev)) { return this.cancel(ev); } @@ -860,8 +860,8 @@ export class Terminal extends CoreTerminal implements ITerminal { * Display the cursor element */ private _showCursor(): void { - if (!this._coreService.isCursorInitialized) { - this._coreService.isCursorInitialized = true; + if (!this.coreService.isCursorInitialized) { + this.coreService.isCursorInitialized = true; this.refresh(this.buffer.y, this.buffer.y); } } @@ -872,7 +872,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } public paste(data: string): void { - paste(data, this.textarea!, this._coreService); + paste(data, this.textarea!, this.coreService); } /** @@ -1025,7 +1025,7 @@ export class Terminal extends CoreTerminal implements ITerminal { return false; } - const result = evaluateKeyboardEvent(event, this._coreService.decPrivateModes.applicationCursorKeys, this.browser.isMac, this.options.macOptionIsMeta); + const result = evaluateKeyboardEvent(event, this.coreService.decPrivateModes.applicationCursorKeys, this.browser.isMac, this.options.macOptionIsMeta); this.updateCursorStyle(event); @@ -1061,7 +1061,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this._onKey.fire({ key: result.key, domEvent: event }); this._showCursor(); - this._coreService.triggerDataEvent(result.key, true); + this.coreService.triggerDataEvent(result.key, true); // Cancel events when not in screen reader mode so events don't get bubbled up and handled by // other listeners. When screen reader mode is enabled, this could cause issues if the event @@ -1138,7 +1138,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this._onKey.fire({ key, domEvent: ev }); this._showCursor(); - this._coreService.triggerDataEvent(key, true); + this.coreService.triggerDataEvent(key, true); return true; } @@ -1247,12 +1247,12 @@ export class Terminal extends CoreTerminal implements ITerminal { case WindowsOptionsReportType.GET_WIN_SIZE_PIXELS: const canvasWidth = this._renderService.dimensions.scaledCanvasWidth.toFixed(0); const canvasHeight = this._renderService.dimensions.scaledCanvasHeight.toFixed(0); - this._coreService.triggerDataEvent(`${C0.ESC}[4;${canvasHeight};${canvasWidth}t`); + this.coreService.triggerDataEvent(`${C0.ESC}[4;${canvasHeight};${canvasWidth}t`); break; case WindowsOptionsReportType.GET_CELL_SIZE_PIXELS: const cellWidth = this._renderService.dimensions.scaledCellWidth.toFixed(0); const cellHeight = this._renderService.dimensions.scaledCellHeight.toFixed(0); - this._coreService.triggerDataEvent(`${C0.ESC}[6;${cellHeight};${cellWidth}t`); + this.coreService.triggerDataEvent(`${C0.ESC}[6;${cellHeight};${cellWidth}t`); break; } } diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 9d5373c8..daa6843c 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -13,7 +13,7 @@ import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, I import { Buffer } from 'common/buffer/Buffer'; import * as Browser from 'common/Platform'; import { Terminal } from 'browser/Terminal'; -import { IUnicodeService, IOptionsService } from 'common/services/Services'; +import { IUnicodeService, IOptionsService, ICoreService, ICoreMouseService } from 'common/services/Services'; import { IFunctionIdentifier, IParams } from 'common/parser/Types'; import { AttributeData } from 'common/buffer/AttributeData'; @@ -43,6 +43,8 @@ export class MockTerminal implements ITerminal { public onRender!: IEvent<{ start: number, end: number }>; public onResize!: IEvent<{ cols: number, rows: number }>; public markers!: IMarker[]; + public coreMouseService!: ICoreMouseService; + public coreService!: ICoreService; public optionsService!: IOptionsService; public unicodeService!: IUnicodeService; public addMarker(cursorYOffset: number): IMarker { diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 6af9db71..a76b1a22 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight } from 'xterm'; +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes } from 'xterm'; import { ITerminal } from 'browser/Types'; import { Terminal as TerminalCore } from 'browser/Terminal'; import * as Strings from 'browser/LocalizableStrings'; @@ -68,6 +68,27 @@ export class Terminal implements ITerminalApi { this._checkProposedApi(); return this._core.markers; } + public get modes(): IModes { + const m = this._core.coreService.decPrivateModes; + let mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any' = 'none'; + switch (this._core.coreMouseService.activeProtocol) { + case 'X10': mouseTrackingMode = 'x10'; break; + case 'VT200': mouseTrackingMode = 'vt200'; break; + case 'DRAG': mouseTrackingMode = 'drag'; break; + case 'ANY': mouseTrackingMode = 'any'; break; + } + return { + applicationCursorKeysMode: m.applicationCursorKeys, + applicationKeypadMode: m.applicationKeypad, + bracketedPasteMode: m.bracketedPasteMode, + insertMode: this._core.coreService.modes.insertMode, + mouseTrackingMode: mouseTrackingMode, + originMode: m.origin, + reverseWraparoundMode: m.reverseWraparound, + sendFocusMode: m.sendFocus, + wraparoundMode: m.wraparound + }; + } public blur(): void { this._core.blur(); } diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index e61a8e16..96a4b53d 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -47,11 +47,11 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { protected readonly _instantiationService: IInstantiationService; protected readonly _bufferService: IBufferService; protected readonly _logService: ILogService; - protected readonly _coreService: ICoreService; protected readonly _charsetService: ICharsetService; - protected readonly _coreMouseService: ICoreMouseService; protected readonly _dirtyRowService: IDirtyRowService; + public readonly coreMouseService: ICoreMouseService; + public readonly coreService: ICoreService; public readonly unicodeService: IUnicodeService; public readonly optionsService: IOptionsService; @@ -100,10 +100,10 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this._instantiationService.setService(IBufferService, this._bufferService); this._logService = this._instantiationService.createInstance(LogService); this._instantiationService.setService(ILogService, this._logService); - this._coreService = this.register(this._instantiationService.createInstance(CoreService, () => this.scrollToBottom())); - this._instantiationService.setService(ICoreService, this._coreService); - this._coreMouseService = this._instantiationService.createInstance(CoreMouseService); - this._instantiationService.setService(ICoreMouseService, this._coreMouseService); + this.coreService = this.register(this._instantiationService.createInstance(CoreService, () => this.scrollToBottom())); + this._instantiationService.setService(ICoreService, this.coreService); + this.coreMouseService = this._instantiationService.createInstance(CoreMouseService); + this._instantiationService.setService(ICoreMouseService, this.coreMouseService); this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService); this._instantiationService.setService(IDirtyRowService, this._dirtyRowService); this.unicodeService = this._instantiationService.createInstance(UnicodeService); @@ -112,14 +112,14 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this._instantiationService.setService(ICharsetService, this._charsetService); // Register input handler and handle/forward events - this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService); + this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this.coreService, this._dirtyRowService, this._logService, this.optionsService, this.coreMouseService, this.unicodeService); this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed)); this.register(this._inputHandler); // Setup listeners this.register(forwardEvent(this._bufferService.onResize, this._onResize)); - this.register(forwardEvent(this._coreService.onData, this._onData)); - this.register(forwardEvent(this._coreService.onBinary, this._onBinary)); + this.register(forwardEvent(this.coreService.onData, this._onData)); + this.register(forwardEvent(this.coreService.onBinary, this._onBinary)); this.register(this.optionsService.onOptionChange(key => this._updateOptions(key))); this.register(this._bufferService.onScroll(event => { this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL }); @@ -250,8 +250,8 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this._inputHandler.reset(); this._bufferService.reset(); this._charsetService.reset(); - this._coreService.reset(); - this._coreMouseService.reset(); + this.coreService.reset(); + this.coreMouseService.reset(); } protected _updateOptions(key: string): void { diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 36f75dd0..78e2e62d 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -7,10 +7,12 @@ import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IDeleteEvent, IInsertEvent } from 'common/CircularList'; import { IParams } from 'common/parser/Types'; -import { IOptionsService, IUnicodeService } from 'common/services/Services'; +import { ICoreMouseService, ICoreService, IOptionsService, IUnicodeService } from 'common/services/Services'; import { IBufferSet } from 'common/buffer/Types'; export interface ICoreTerminal { + coreMouseService: ICoreMouseService; + coreService: ICoreService; optionsService: IOptionsService; unicodeService: IUnicodeService; buffers: IBufferSet; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 035748fc..210e354d 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -622,6 +622,11 @@ declare module 'xterm' { */ readonly unicode: IUnicodeHandling; + /** + * Gets the terminal modes as set by SM/DECSET. + */ + readonly modes: IModes; + /** * Natural language strings that can be localized. */ @@ -1622,4 +1627,51 @@ declare module 'xterm' { */ activeVersion: string; } + + /** + * Terminal modes as set by SM/DECSET. + */ + export interface IModes { + /** + * Application Cursor Keys (DECCKM): `CSI ? 1 h` + */ + readonly applicationCursorKeysMode: boolean; + /** + * Application Keypad Mode (DECNKM): `CSI ? 6 6 h` + */ + readonly applicationKeypadMode: boolean; + /** + * Bracketed Paste Mode: `CSI ? 2 0 0 4 h` + */ + readonly bracketedPasteMode: boolean; + /** + * Insert Mode (IRM): `CSI 4 h` + */ + readonly insertMode: boolean; + /** + * Mouse Tracking, this can be one of the following: + * - none: This is the default value and can be reset with DECRST + * - x10: Send Mouse X & Y on button press `CSI ? 9 h` + * - vt200: Send Mouse X & Y on button press and release `CSI ? 1 0 0 0 h` + * - drag: Use Cell Motion Mouse Tracking `CSI ? 1 0 0 2 h` + * - any: Use All Motion Mouse Tracking `CSI ? 1 0 0 3 h` + */ + readonly mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any'; + /** + * Origin Mode (DECOM): `CSI ? 6 h` + */ + readonly originMode: boolean; + /** + * Reverse-wraparound Mode: `CSI ? 4 5 h` + */ + readonly reverseWraparoundMode: boolean; + /** + * Send FocusIn/FocusOut events: `CSI ? 1 0 0 3 h` + */ + readonly sendFocusMode: boolean; + /** + * Auto-Wrap Mode (DECAWM): `CSI ? 7 h` + */ + readonly wraparoundMode: boolean + } } From 8c25d57c260f275e3eb1c7e1890a647668d1a7a8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 16 Aug 2021 15:01:30 -0700 Subject: [PATCH 3/9] xterm-headless modes api, tests for headless --- src/headless/public/Terminal.test.ts | 82 + src/headless/public/Terminal.ts | 23 +- typings/xterm-headless.d.ts | 2388 +++++++++++++------------- typings/xterm.d.ts | 2 +- 4 files changed, 1325 insertions(+), 1170 deletions(-) diff --git a/src/headless/public/Terminal.test.ts b/src/headless/public/Terminal.test.ts index 15e1efba..4247aae9 100644 --- a/src/headless/public/Terminal.test.ts +++ b/src/headless/public/Terminal.test.ts @@ -367,6 +367,88 @@ describe('Headless API Tests', function(): void { }); }); + describe('modes', () => { + it('defaults', () => { + deepStrictEqual(term.modes, { + applicationCursorKeysMode: false, + applicationKeypadMode: false, + bracketedPasteMode: false, + insertMode: false, + mouseTrackingMode: 'none', + originMode: false, + reverseWraparoundMode: false, + sendFocusMode: false, + wraparoundMode: true + }); + }); + it('applicationCursorKeysMode', async () => { + await writeSync('\x1b[?1h'); + strictEqual(term.modes.applicationCursorKeysMode, true); + await writeSync('\x1b[?1l'); + strictEqual(term.modes.applicationCursorKeysMode, false); + }); + it('applicationKeypadMode', async () => { + await writeSync('\x1b[?66h'); + strictEqual(term.modes.applicationKeypadMode, true); + await writeSync('\x1b[?66l'); + strictEqual(term.modes.applicationKeypadMode, false); + }); + it('bracketedPasteMode', async () => { + await writeSync('\x1b[?2004h'); + strictEqual(term.modes.bracketedPasteMode, true); + await writeSync('\x1b[?2004l'); + strictEqual(term.modes.bracketedPasteMode, false); + }); + it('insertMode', async () => { + await writeSync('\x1b[4h'); + strictEqual(term.modes.insertMode, true); + await writeSync('\x1b[4l'); + strictEqual(term.modes.insertMode, false); + }); + it('mouseTrackingMode', async () => { + await writeSync('\x1b[?9h'); + strictEqual(term.modes.mouseTrackingMode, 'x10'); + await writeSync('\x1b[?9l'); + strictEqual(term.modes.mouseTrackingMode, 'none'); + await writeSync('\x1b[?1000h'); + strictEqual(term.modes.mouseTrackingMode, 'vt200'); + await writeSync('\x1b[?1000l'); + strictEqual(term.modes.mouseTrackingMode, 'none'); + await writeSync('\x1b[?1002h'); + strictEqual(term.modes.mouseTrackingMode, 'drag'); + await writeSync('\x1b[?1002l'); + strictEqual(term.modes.mouseTrackingMode, 'none'); + await writeSync('\x1b[?1003h'); + strictEqual(term.modes.mouseTrackingMode, 'any'); + await writeSync('\x1b[?1003l'); + strictEqual(term.modes.mouseTrackingMode, 'none'); + }); + it('originMode', async () => { + await writeSync('\x1b[?6h'); + strictEqual(term.modes.originMode, true); + await writeSync('\x1b[?6l'); + strictEqual(term.modes.originMode, false); + }); + it('reverseWraparoundMode', async () => { + await writeSync('\x1b[?45h'); + strictEqual(term.modes.reverseWraparoundMode, true); + await writeSync('\x1b[?45l'); + strictEqual(term.modes.reverseWraparoundMode, false); + }); + it('sendFocusMode', async () => { + await writeSync('\x1b[?1004h'); + strictEqual(term.modes.sendFocusMode, true); + await writeSync('\x1b[?1004l'); + strictEqual(term.modes.sendFocusMode, false); + }); + it('wraparoundMode', async () => { + await writeSync('\x1b[?7h'); + strictEqual(term.modes.wraparoundMode, true); + await writeSync('\x1b[?7l'); + strictEqual(term.modes.wraparoundMode, false); + }); + }); + it('dispose', async () => { term.dispose(); strictEqual((term as any)._core._isDisposed, true); diff --git a/src/headless/public/Terminal.ts b/src/headless/public/Terminal.ts index a1de7fdb..29e83581 100644 --- a/src/headless/public/Terminal.ts +++ b/src/headless/public/Terminal.ts @@ -7,7 +7,7 @@ import { IEvent } from 'common/EventEmitter'; import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi'; import { ParserApi } from 'common/public/ParserApi'; import { UnicodeApi } from 'common/public/UnicodeApi'; -import { IBufferNamespace as IBufferNamespaceApi, IMarker, IParser, ITerminalAddon, ITerminalOptions, IUnicodeHandling, Terminal as ITerminalApi } from 'xterm-headless'; +import { IBufferNamespace as IBufferNamespaceApi, IMarker, IModes, IParser, ITerminalAddon, ITerminalOptions, IUnicodeHandling, Terminal as ITerminalApi } from 'xterm-headless'; import { Terminal as TerminalCore } from 'headless/Terminal'; import { AddonManager } from 'common/public/AddonManager'; @@ -61,6 +61,27 @@ export class Terminal implements ITerminalApi { this._checkProposedApi(); return this._core.markers; } + public get modes(): IModes { + const m = this._core.coreService.decPrivateModes; + let mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any' = 'none'; + switch (this._core.coreMouseService.activeProtocol) { + case 'X10': mouseTrackingMode = 'x10'; break; + case 'VT200': mouseTrackingMode = 'vt200'; break; + case 'DRAG': mouseTrackingMode = 'drag'; break; + case 'ANY': mouseTrackingMode = 'any'; break; + } + return { + applicationCursorKeysMode: m.applicationCursorKeys, + applicationKeypadMode: m.applicationKeypad, + bracketedPasteMode: m.bracketedPasteMode, + insertMode: this._core.coreService.modes.insertMode, + mouseTrackingMode: mouseTrackingMode, + originMode: m.origin, + reverseWraparoundMode: m.reverseWraparound, + sendFocusMode: m.sendFocus, + wraparoundMode: m.wraparound + }; + } public resize(columns: number, rows: number): void { this._verifyIntegers(columns, rows); this._core.resize(columns, rows); diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 03e7567a..b6e1505b 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -8,1246 +8,1298 @@ */ declare module 'xterm-headless' { + /** + * A string representing log level. + */ + export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off'; + + /** + * An object containing start up options for the terminal. + */ + export interface ITerminalOptions { /** - * A string representing log level. + * Whether to allow the use of proposed API. When false, any usage of APIs + * marked as experimental/proposed will throw an error. This defaults to + * true currently, but will change to false in v5.0. */ - export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off'; + allowProposedApi?: boolean; /** - * An object containing start up options for the terminal. + * Whether background should support non-opaque color. It must be set before + * executing the `Terminal.open()` method and can't be changed later without + * executing it again. Note that enabling this can negatively impact + * performance. */ - export interface ITerminalOptions { - /** - * Whether to allow the use of proposed API. When false, any usage of APIs - * marked as experimental/proposed will throw an error. This defaults to - * true currently, but will change to false in v5.0. - */ - allowProposedApi?: boolean; - - /** - * Whether background should support non-opaque color. It must be set before - * executing the `Terminal.open()` method and can't be changed later without - * executing it again. Note that enabling this can negatively impact - * performance. - */ - allowTransparency?: boolean; - - /** - * If enabled, alt + click will move the prompt cursor to position - * underneath the mouse. The default is true. - */ - altClickMovesCursor?: boolean; - - /** - * A data uri of the sound to use for the bell when `bellStyle = 'sound'`. - */ - bellSound?: string; - - /** - * The type of the bell notification the terminal will use. - */ - bellStyle?: 'none' | 'sound'; - - /** - * When enabled the cursor will be set to the beginning of the next line - * with every new line. This is equivalent to sending '\r\n' for each '\n'. - * Normally the termios settings of the underlying PTY deals with the - * translation of '\n' to '\r\n' and this setting should not be used. If you - * deal with data from a non-PTY related source, this settings might be - * useful. - */ - convertEol?: boolean; - - /** - * The number of columns in the terminal. - */ - cols?: number; - - /** - * Whether the cursor blinks. - */ - cursorBlink?: boolean; - - /** - * The style of the cursor. - */ - cursorStyle?: 'block' | 'underline' | 'bar'; - - /** - * The width of the cursor in CSS pixels when `cursorStyle` is set to 'bar'. - */ - cursorWidth?: number; - - /** - * Whether input should be disabled. - */ - disableStdin?: boolean; - - /** - * Whether to draw bold text in bright colors. The default is true. - */ - drawBoldTextInBrightColors?: boolean; - - /** - * The modifier key hold to multiply scroll speed. - */ - fastScrollModifier?: 'alt' | 'ctrl' | 'shift' | undefined; - - /** - * The spacing in whole pixels between characters. - */ - letterSpacing?: number; - - /** - * The line height used to render text. - */ - lineHeight?: number; - - /** - * The duration in milliseconds before link tooltip events fire when - * hovering on a link. - * @deprecated This will be removed when the link matcher API is removed. - */ - linkTooltipHoverDuration?: number; - - /** - * What log level to use, this will log for all levels below and including - * what is set: - * - * 1. debug - * 2. info (default) - * 3. warn - * 4. error - * 5. off - */ - logLevel?: LogLevel; - - /** - * Whether to treat option as the meta key. - */ - macOptionIsMeta?: boolean; - - /** - * Whether holding a modifier key will force normal selection behavior, - * regardless of whether the terminal is in mouse events mode. This will - * also prevent mouse events from being emitted by the terminal. For - * example, this allows you to use xterm.js' regular selection inside tmux - * with mouse mode enabled. - */ - macOptionClickForcesSelection?: boolean; - - /** - * The minimum contrast ratio for text in the terminal, setting this will - * change the foreground color dynamically depending on whether the contrast - * ratio is met. Example values: - * - * - 1: The default, do nothing. - * - 4.5: Minimum for WCAG AA compliance. - * - 7: Minimum for WCAG AAA compliance. - * - 21: White on black or black on white. - */ - minimumContrastRatio?: number; - - /** - * Whether to select the word under the cursor on right click, this is - * standard behavior in a lot of macOS applications. - */ - rightClickSelectsWord?: boolean; - - /** - * The number of rows in the terminal. - */ - rows?: number; - - /** - * Whether screen reader support is enabled. When on this will expose - * supporting elements in the DOM to support NVDA on Windows and VoiceOver - * on macOS. - */ - screenReaderMode?: boolean; - - /** - * The amount of scrollback in the terminal. Scrollback is the amount of - * rows that are retained when lines are scrolled beyond the initial - * viewport. - */ - scrollback?: number; - - /** - * The scrolling speed multiplier used for adjusting normal scrolling speed. - */ - scrollSensitivity?: number; - - /** - * The size of tab stops in the terminal. - */ - tabStopWidth?: number; - - /** - * The color theme of the terminal. - */ - theme?: ITheme; - - /** - * Whether "Windows mode" is enabled. Because Windows backends winpty and - * conpty operate by doing line wrapping on their side, xterm.js does not - * have access to wrapped lines. When Windows mode is enabled the following - * changes will be in effect: - * - * - Reflow is disabled. - * - Lines are assumed to be wrapped if the last character of the line is - * not whitespace. - */ - windowsMode?: boolean; - - /** - * A string containing all characters that are considered word separated by the - * double click to select work logic. - */ - wordSeparator?: string; - - /** - * Enable various window manipulation and report features. - * All features are disabled by default for security reasons. - */ - windowOptions?: IWindowOptions; - } + allowTransparency?: boolean; /** - * Contains colors to theme the terminal with. + * If enabled, alt + click will move the prompt cursor to position + * underneath the mouse. The default is true. */ - export interface ITheme { - /** The default foreground color */ - foreground?: string; - /** The default background color */ - background?: string; - /** The cursor color */ - cursor?: string; - /** The accent color of the cursor (fg color for a block cursor) */ - cursorAccent?: string; - /** The selection background color (can be transparent) */ - selection?: string; - /** ANSI black (eg. `\x1b[30m`) */ - black?: string; - /** ANSI red (eg. `\x1b[31m`) */ - red?: string; - /** ANSI green (eg. `\x1b[32m`) */ - green?: string; - /** ANSI yellow (eg. `\x1b[33m`) */ - yellow?: string; - /** ANSI blue (eg. `\x1b[34m`) */ - blue?: string; - /** ANSI magenta (eg. `\x1b[35m`) */ - magenta?: string; - /** ANSI cyan (eg. `\x1b[36m`) */ - cyan?: string; - /** ANSI white (eg. `\x1b[37m`) */ - white?: string; - /** ANSI bright black (eg. `\x1b[1;30m`) */ - brightBlack?: string; - /** ANSI bright red (eg. `\x1b[1;31m`) */ - brightRed?: string; - /** ANSI bright green (eg. `\x1b[1;32m`) */ - brightGreen?: string; - /** ANSI bright yellow (eg. `\x1b[1;33m`) */ - brightYellow?: string; - /** ANSI bright blue (eg. `\x1b[1;34m`) */ - brightBlue?: string; - /** ANSI bright magenta (eg. `\x1b[1;35m`) */ - brightMagenta?: string; - /** ANSI bright cyan (eg. `\x1b[1;36m`) */ - brightCyan?: string; - /** ANSI bright white (eg. `\x1b[1;37m`) */ - brightWhite?: string; - } + altClickMovesCursor?: boolean; /** - * An object that can be disposed via a dispose function. + * A data uri of the sound to use for the bell when `bellStyle = 'sound'`. */ - export interface IDisposable { - dispose(): void; - } + bellSound?: string; /** - * An event that can be listened to. + * The type of the bell notification the terminal will use. + */ + bellStyle?: 'none' | 'sound'; + + /** + * When enabled the cursor will be set to the beginning of the next line + * with every new line. This is equivalent to sending '\r\n' for each '\n'. + * Normally the termios settings of the underlying PTY deals with the + * translation of '\n' to '\r\n' and this setting should not be used. If you + * deal with data from a non-PTY related source, this settings might be + * useful. + */ + convertEol?: boolean; + + /** + * The number of columns in the terminal. + */ + cols?: number; + + /** + * Whether the cursor blinks. + */ + cursorBlink?: boolean; + + /** + * The style of the cursor. + */ + cursorStyle?: 'block' | 'underline' | 'bar'; + + /** + * The width of the cursor in CSS pixels when `cursorStyle` is set to 'bar'. + */ + cursorWidth?: number; + + /** + * Whether input should be disabled. + */ + disableStdin?: boolean; + + /** + * Whether to draw bold text in bright colors. The default is true. + */ + drawBoldTextInBrightColors?: boolean; + + /** + * The modifier key hold to multiply scroll speed. + */ + fastScrollModifier?: 'alt' | 'ctrl' | 'shift' | undefined; + + /** + * The spacing in whole pixels between characters. + */ + letterSpacing?: number; + + /** + * The line height used to render text. + */ + lineHeight?: number; + + /** + * The duration in milliseconds before link tooltip events fire when + * hovering on a link. + * @deprecated This will be removed when the link matcher API is removed. + */ + linkTooltipHoverDuration?: number; + + /** + * What log level to use, this will log for all levels below and including + * what is set: + * + * 1. debug + * 2. info (default) + * 3. warn + * 4. error + * 5. off + */ + logLevel?: LogLevel; + + /** + * Whether to treat option as the meta key. + */ + macOptionIsMeta?: boolean; + + /** + * Whether holding a modifier key will force normal selection behavior, + * regardless of whether the terminal is in mouse events mode. This will + * also prevent mouse events from being emitted by the terminal. For + * example, this allows you to use xterm.js' regular selection inside tmux + * with mouse mode enabled. + */ + macOptionClickForcesSelection?: boolean; + + /** + * The minimum contrast ratio for text in the terminal, setting this will + * change the foreground color dynamically depending on whether the contrast + * ratio is met. Example values: + * + * - 1: The default, do nothing. + * - 4.5: Minimum for WCAG AA compliance. + * - 7: Minimum for WCAG AAA compliance. + * - 21: White on black or black on white. + */ + minimumContrastRatio?: number; + + /** + * Whether to select the word under the cursor on right click, this is + * standard behavior in a lot of macOS applications. + */ + rightClickSelectsWord?: boolean; + + /** + * The number of rows in the terminal. + */ + rows?: number; + + /** + * Whether screen reader support is enabled. When on this will expose + * supporting elements in the DOM to support NVDA on Windows and VoiceOver + * on macOS. + */ + screenReaderMode?: boolean; + + /** + * The amount of scrollback in the terminal. Scrollback is the amount of + * rows that are retained when lines are scrolled beyond the initial + * viewport. + */ + scrollback?: number; + + /** + * The scrolling speed multiplier used for adjusting normal scrolling speed. + */ + scrollSensitivity?: number; + + /** + * The size of tab stops in the terminal. + */ + tabStopWidth?: number; + + /** + * The color theme of the terminal. + */ + theme?: ITheme; + + /** + * Whether "Windows mode" is enabled. Because Windows backends winpty and + * conpty operate by doing line wrapping on their side, xterm.js does not + * have access to wrapped lines. When Windows mode is enabled the following + * changes will be in effect: + * + * - Reflow is disabled. + * - Lines are assumed to be wrapped if the last character of the line is + * not whitespace. + */ + windowsMode?: boolean; + + /** + * A string containing all characters that are considered word separated by the + * double click to select work logic. + */ + wordSeparator?: string; + + /** + * Enable various window manipulation and report features. + * All features are disabled by default for security reasons. + */ + windowOptions?: IWindowOptions; + } + + /** + * Contains colors to theme the terminal with. + */ + export interface ITheme { + /** The default foreground color */ + foreground?: string; + /** The default background color */ + background?: string; + /** The cursor color */ + cursor?: string; + /** The accent color of the cursor (fg color for a block cursor) */ + cursorAccent?: string; + /** The selection background color (can be transparent) */ + selection?: string; + /** ANSI black (eg. `\x1b[30m`) */ + black?: string; + /** ANSI red (eg. `\x1b[31m`) */ + red?: string; + /** ANSI green (eg. `\x1b[32m`) */ + green?: string; + /** ANSI yellow (eg. `\x1b[33m`) */ + yellow?: string; + /** ANSI blue (eg. `\x1b[34m`) */ + blue?: string; + /** ANSI magenta (eg. `\x1b[35m`) */ + magenta?: string; + /** ANSI cyan (eg. `\x1b[36m`) */ + cyan?: string; + /** ANSI white (eg. `\x1b[37m`) */ + white?: string; + /** ANSI bright black (eg. `\x1b[1;30m`) */ + brightBlack?: string; + /** ANSI bright red (eg. `\x1b[1;31m`) */ + brightRed?: string; + /** ANSI bright green (eg. `\x1b[1;32m`) */ + brightGreen?: string; + /** ANSI bright yellow (eg. `\x1b[1;33m`) */ + brightYellow?: string; + /** ANSI bright blue (eg. `\x1b[1;34m`) */ + brightBlue?: string; + /** ANSI bright magenta (eg. `\x1b[1;35m`) */ + brightMagenta?: string; + /** ANSI bright cyan (eg. `\x1b[1;36m`) */ + brightCyan?: string; + /** ANSI bright white (eg. `\x1b[1;37m`) */ + brightWhite?: string; + } + + /** + * An object that can be disposed via a dispose function. + */ + export interface IDisposable { + dispose(): void; + } + + /** + * An event that can be listened to. + * @returns an `IDisposable` to stop listening. + */ + export interface IEvent { + (listener: (arg1: T, arg2: U) => any): IDisposable; + } + + /** + * Represents a specific line in the terminal that is tracked when scrollback + * is trimmed and lines are added or removed. This is a single line that may + * be part of a larger wrapped line. + */ + export interface IMarker extends IDisposable { + /** + * A unique identifier for this marker. + */ + readonly id: number; + + /** + * Whether this marker is disposed. + */ + readonly isDisposed: boolean; + + /** + * The actual line index in the buffer at this point in time. This is set to + * -1 if the marker has been disposed. + */ + readonly line: number; + + /** + * Event listener to get notified when the marker gets disposed. Automatic disposal + * might happen for a marker, that got invalidated by scrolling out or removal of + * a line from the buffer. + */ + onDispose: IEvent; + } + + /** + * The set of localizable strings. + */ + export interface ILocalizableStrings { + /** + * The aria label for the underlying input textarea for the terminal. + */ + promptLabel: string; + + /** + * Announcement for when line reading is suppressed due to too many lines + * being printed to the terminal when `screenReaderMode` is enabled. + */ + tooMuchOutput: string; + } + + /** + * Enable various window manipulation and report features (CSI Ps ; Ps ; Ps t). + * + * Most settings have no default implementation, as they heavily rely on + * the embedding environment. + * + * To implement a feature, create a custom CSI hook like this: + * ```ts + * term.parser.addCsiHandler({final: 't'}, params => { + * const ps = params[0]; + * switch (ps) { + * case XY: + * ... // your implementation for option XY + * return true; // signal Ps=XY was handled + * } + * return false; // any Ps that was not handled + * }); + * ``` + * + * Note on security: + * Most features are meant to deal with some information of the host machine + * where the terminal runs on. This is seen as a security risk possibly leaking + * sensitive data of the host to the program in the terminal. Therefore all options + * (even those without a default implementation) are guarded by the boolean flag + * and disabled by default. + */ + export interface IWindowOptions { + /** + * Ps=1 De-iconify window. + * No default implementation. + */ + restoreWin?: boolean; + /** + * Ps=2 Iconify window. + * No default implementation. + */ + minimizeWin?: boolean; + /** + * Ps=3 ; x ; y + * Move window to [x, y]. + * No default implementation. + */ + setWinPosition?: boolean; + /** + * Ps = 4 ; height ; width + * Resize the window to given `height` and `width` in pixels. + * Omitted parameters should reuse the current height or width. + * Zero parameters should use the display's height or width. + * No default implementation. + */ + setWinSizePixels?: boolean; + /** + * Ps=5 Raise the window to the front of the stacking order. + * No default implementation. + */ + raiseWin?: boolean; + /** + * Ps=6 Lower the xterm window to the bottom of the stacking order. + * No default implementation. + */ + lowerWin?: boolean; + /** Ps=7 Refresh the window. */ + refreshWin?: boolean; + /** + * Ps = 8 ; height ; width + * Resize the text area to given height and width in characters. + * Omitted parameters should reuse the current height or width. + * Zero parameters use the display's height or width. + * No default implementation. + */ + setWinSizeChars?: boolean; + /** + * Ps=9 ; 0 Restore maximized window. + * Ps=9 ; 1 Maximize window (i.e., resize to screen size). + * Ps=9 ; 2 Maximize window vertically. + * Ps=9 ; 3 Maximize window horizontally. + * No default implementation. + */ + maximizeWin?: boolean; + /** + * Ps=10 ; 0 Undo full-screen mode. + * Ps=10 ; 1 Change to full-screen. + * Ps=10 ; 2 Toggle full-screen. + * No default implementation. + */ + fullscreenWin?: boolean; + /** Ps=11 Report xterm window state. + * If the xterm window is non-iconified, it returns "CSI 1 t". + * If the xterm window is iconified, it returns "CSI 2 t". + * No default implementation. + */ + getWinState?: boolean; + /** + * Ps=13 Report xterm window position. Result is "CSI 3 ; x ; y t". + * Ps=13 ; 2 Report xterm text-area position. Result is "CSI 3 ; x ; y t". + * No default implementation. + */ + getWinPosition?: boolean; + /** + * Ps=14 Report xterm text area size in pixels. Result is "CSI 4 ; height ; width t". + * Ps=14 ; 2 Report xterm window size in pixels. Result is "CSI 4 ; height ; width t". + * Has a default implementation. + */ + getWinSizePixels?: boolean; + /** + * Ps=15 Report size of the screen in pixels. Result is "CSI 5 ; height ; width t". + * No default implementation. + */ + getScreenSizePixels?: boolean; + /** + * Ps=16 Report xterm character cell size in pixels. Result is "CSI 6 ; height ; width t". + * Has a default implementation. + */ + getCellSizePixels?: boolean; + /** + * Ps=18 Report the size of the text area in characters. Result is "CSI 8 ; height ; width t". + * Has a default implementation. + */ + getWinSizeChars?: boolean; + /** + * Ps=19 Report the size of the screen in characters. Result is "CSI 9 ; height ; width t". + * No default implementation. + */ + getScreenSizeChars?: boolean; + /** + * Ps=20 Report xterm window's icon label. Result is "OSC L label ST". + * No default implementation. + */ + getIconTitle?: boolean; + /** + * Ps=21 Report xterm window's title. Result is "OSC l label ST". + * No default implementation. + */ + getWinTitle?: boolean; + /** + * Ps=22 ; 0 Save xterm icon and window title on stack. + * Ps=22 ; 1 Save xterm icon title on stack. + * Ps=22 ; 2 Save xterm window title on stack. + * All variants have a default implementation. + */ + pushTitle?: boolean; + /** + * Ps=23 ; 0 Restore xterm icon and window title from stack. + * Ps=23 ; 1 Restore xterm icon title from stack. + * Ps=23 ; 2 Restore xterm window title from stack. + * All variants have a default implementation. + */ + popTitle?: boolean; + /** + * Ps>=24 Resize to Ps lines (DECSLPP). + * DECSLPP is not implemented. This settings is also used to + * enable / disable DECCOLM (earlier variant of DECSLPP). + */ + setWinLines?: boolean; + } + + /** + * The class that represents an xterm.js terminal. + */ + export class Terminal implements IDisposable { + /** + * The number of rows in the terminal's viewport. Use + * `ITerminalOptions.rows` to set this in the constructor and + * `Terminal.resize` for when the terminal exists. + */ + readonly rows: number; + + /** + * The number of columns in the terminal's viewport. Use + * `ITerminalOptions.cols` to set this in the constructor and + * `Terminal.resize` for when the terminal exists. + */ + readonly cols: number; + + /** + * (EXPERIMENTAL) The terminal's current buffer, this might be either the + * normal buffer or the alt buffer depending on what's running in the + * terminal. + */ + readonly buffer: IBufferNamespace; + + /** + * (EXPERIMENTAL) Get all markers registered against the buffer. If the alt + * buffer is active this will always return []. + */ + readonly markers: ReadonlyArray; + + /** + * (EXPERIMENTAL) Get the parser interface to register + * custom escape sequence handlers. + */ + readonly parser: IParser; + + /** + * (EXPERIMENTAL) Get the Unicode handling interface + * to register and switch Unicode version. + */ + readonly unicode: IUnicodeHandling; + + /** + * Gets the terminal modes as set by SM/DECSET. + */ + readonly modes: IModes; + + /** + * Natural language strings that can be localized. + */ + static strings: ILocalizableStrings; + + /** + * Creates a new `Terminal` object. + * + * @param options An object containing a set of options. + */ + constructor(options?: ITerminalOptions); + + /** + * Adds an event listener for when the bell is triggered. * @returns an `IDisposable` to stop listening. */ - export interface IEvent { - (listener: (arg1: T, arg2: U) => any): IDisposable; - } + onBell: IEvent; /** - * Represents a specific line in the terminal that is tracked when scrollback - * is trimmed and lines are added or removed. This is a single line that may - * be part of a larger wrapped line. + * Adds an event listener for when a binary event fires. This is used to + * enable non UTF-8 conformant binary messages to be sent to the backend. + * Currently this is only used for a certain type of mouse reports that + * happen to be not UTF-8 compatible. + * The event value is a JS string, pass it to the underlying pty as + * binary data, e.g. `pty.write(Buffer.from(data, 'binary'))`. + * @returns an `IDisposable` to stop listening. */ - export interface IMarker extends IDisposable { - /** - * A unique identifier for this marker. - */ - readonly id: number; - - /** - * Whether this marker is disposed. - */ - readonly isDisposed: boolean; - - /** - * The actual line index in the buffer at this point in time. This is set to - * -1 if the marker has been disposed. - */ - readonly line: number; - - /** - * Event listener to get notified when the marker gets disposed. Automatic disposal - * might happen for a marker, that got invalidated by scrolling out or removal of - * a line from the buffer. - */ - onDispose: IEvent; - } + onBinary: IEvent; /** - * The set of localizable strings. + * Adds an event listener for the cursor moves. + * @returns an `IDisposable` to stop listening. */ - export interface ILocalizableStrings { - /** - * The aria label for the underlying input textarea for the terminal. - */ - promptLabel: string; - - /** - * Announcement for when line reading is suppressed due to too many lines - * being printed to the terminal when `screenReaderMode` is enabled. - */ - tooMuchOutput: string; - } + onCursorMove: IEvent; /** - * Enable various window manipulation and report features (CSI Ps ; Ps ; Ps t). - * - * Most settings have no default implementation, as they heavily rely on - * the embedding environment. - * - * To implement a feature, create a custom CSI hook like this: - * ```ts - * term.parser.addCsiHandler({final: 't'}, params => { - * const ps = params[0]; - * switch (ps) { - * case XY: - * ... // your implementation for option XY - * return true; // signal Ps=XY was handled - * } - * return false; // any Ps that was not handled - * }); - * ``` - * - * Note on security: - * Most features are meant to deal with some information of the host machine - * where the terminal runs on. This is seen as a security risk possibly leaking - * sensitive data of the host to the program in the terminal. Therefore all options - * (even those without a default implementation) are guarded by the boolean flag - * and disabled by default. + * Adds an event listener for when a data event fires. This happens for + * example when the user types or pastes into the terminal. The event value + * is whatever `string` results, in a typical setup, this should be passed + * on to the backing pty. + * @returns an `IDisposable` to stop listening. */ - export interface IWindowOptions { - /** - * Ps=1 De-iconify window. - * No default implementation. - */ - restoreWin?: boolean; - /** - * Ps=2 Iconify window. - * No default implementation. - */ - minimizeWin?: boolean; - /** - * Ps=3 ; x ; y - * Move window to [x, y]. - * No default implementation. - */ - setWinPosition?: boolean; - /** - * Ps = 4 ; height ; width - * Resize the window to given `height` and `width` in pixels. - * Omitted parameters should reuse the current height or width. - * Zero parameters should use the display's height or width. - * No default implementation. - */ - setWinSizePixels?: boolean; - /** - * Ps=5 Raise the window to the front of the stacking order. - * No default implementation. - */ - raiseWin?: boolean; - /** - * Ps=6 Lower the xterm window to the bottom of the stacking order. - * No default implementation. - */ - lowerWin?: boolean; - /** Ps=7 Refresh the window. */ - refreshWin?: boolean; - /** - * Ps = 8 ; height ; width - * Resize the text area to given height and width in characters. - * Omitted parameters should reuse the current height or width. - * Zero parameters use the display's height or width. - * No default implementation. - */ - setWinSizeChars?: boolean; - /** - * Ps=9 ; 0 Restore maximized window. - * Ps=9 ; 1 Maximize window (i.e., resize to screen size). - * Ps=9 ; 2 Maximize window vertically. - * Ps=9 ; 3 Maximize window horizontally. - * No default implementation. - */ - maximizeWin?: boolean; - /** - * Ps=10 ; 0 Undo full-screen mode. - * Ps=10 ; 1 Change to full-screen. - * Ps=10 ; 2 Toggle full-screen. - * No default implementation. - */ - fullscreenWin?: boolean; - /** Ps=11 Report xterm window state. - * If the xterm window is non-iconified, it returns "CSI 1 t". - * If the xterm window is iconified, it returns "CSI 2 t". - * No default implementation. - */ - getWinState?: boolean; - /** - * Ps=13 Report xterm window position. Result is "CSI 3 ; x ; y t". - * Ps=13 ; 2 Report xterm text-area position. Result is "CSI 3 ; x ; y t". - * No default implementation. - */ - getWinPosition?: boolean; - /** - * Ps=14 Report xterm text area size in pixels. Result is "CSI 4 ; height ; width t". - * Ps=14 ; 2 Report xterm window size in pixels. Result is "CSI 4 ; height ; width t". - * Has a default implementation. - */ - getWinSizePixels?: boolean; - /** - * Ps=15 Report size of the screen in pixels. Result is "CSI 5 ; height ; width t". - * No default implementation. - */ - getScreenSizePixels?: boolean; - /** - * Ps=16 Report xterm character cell size in pixels. Result is "CSI 6 ; height ; width t". - * Has a default implementation. - */ - getCellSizePixels?: boolean; - /** - * Ps=18 Report the size of the text area in characters. Result is "CSI 8 ; height ; width t". - * Has a default implementation. - */ - getWinSizeChars?: boolean; - /** - * Ps=19 Report the size of the screen in characters. Result is "CSI 9 ; height ; width t". - * No default implementation. - */ - getScreenSizeChars?: boolean; - /** - * Ps=20 Report xterm window's icon label. Result is "OSC L label ST". - * No default implementation. - */ - getIconTitle?: boolean; - /** - * Ps=21 Report xterm window's title. Result is "OSC l label ST". - * No default implementation. - */ - getWinTitle?: boolean; - /** - * Ps=22 ; 0 Save xterm icon and window title on stack. - * Ps=22 ; 1 Save xterm icon title on stack. - * Ps=22 ; 2 Save xterm window title on stack. - * All variants have a default implementation. - */ - pushTitle?: boolean; - /** - * Ps=23 ; 0 Restore xterm icon and window title from stack. - * Ps=23 ; 1 Restore xterm icon title from stack. - * Ps=23 ; 2 Restore xterm window title from stack. - * All variants have a default implementation. - */ - popTitle?: boolean; - /** - * Ps>=24 Resize to Ps lines (DECSLPP). - * DECSLPP is not implemented. This settings is also used to - * enable / disable DECCOLM (earlier variant of DECSLPP). - */ - setWinLines?: boolean; - } + onData: IEvent; /** - * The class that represents an xterm.js terminal. + * Adds an event listener for when a line feed is added. + * @returns an `IDisposable` to stop listening. */ - export class Terminal implements IDisposable { - /** - * The number of rows in the terminal's viewport. Use - * `ITerminalOptions.rows` to set this in the constructor and - * `Terminal.resize` for when the terminal exists. - */ - readonly rows: number; + onLineFeed: IEvent; - /** - * The number of columns in the terminal's viewport. Use - * `ITerminalOptions.cols` to set this in the constructor and - * `Terminal.resize` for when the terminal exists. - */ - readonly cols: number; + /** + * Adds an event listener for when the terminal is resized. The event value + * contains the new size. + * @returns an `IDisposable` to stop listening. + */ + onResize: IEvent<{ cols: number, rows: number }>; - /** - * (EXPERIMENTAL) The terminal's current buffer, this might be either the - * normal buffer or the alt buffer depending on what's running in the - * terminal. - */ - readonly buffer: IBufferNamespace; + /** + * Adds an event listener for when a scroll occurs. The event value is the + * new position of the viewport. + * @returns an `IDisposable` to stop listening. + */ + onScroll: IEvent; - /** - * (EXPERIMENTAL) Get all markers registered against the buffer. If the alt - * buffer is active this will always return []. - */ - readonly markers: ReadonlyArray; + /** + * Adds an event listener for when an OSC 0 or OSC 2 title change occurs. + * The event value is the new title. + * @returns an `IDisposable` to stop listening. + */ + onTitleChange: IEvent; - /** - * (EXPERIMENTAL) Get the parser interface to register - * custom escape sequence handlers. - */ - readonly parser: IParser; + /** + * Resizes the terminal. It's best practice to debounce calls to resize, + * this will help ensure that the pty can respond to the resize event + * before another one occurs. + * @param x The number of columns to resize to. + * @param y The number of rows to resize to. + */ + resize(columns: number, rows: number): void; - /** - * (EXPERIMENTAL) Get the Unicode handling interface - * to register and switch Unicode version. - */ - readonly unicode: IUnicodeHandling; + /** + * (EXPERIMENTAL) Adds a marker to the normal buffer and returns it. If the + * alt buffer is active, undefined is returned. + * @param cursorYOffset The y position offset of the marker from the cursor. + * @returns The new marker or undefined. + */ + registerMarker(cursorYOffset: number): IMarker | undefined; - /** - * Natural language strings that can be localized. - */ - static strings: ILocalizableStrings; + /** + * @deprecated use `registerMarker` instead. + */ + addMarker(cursorYOffset: number): IMarker | undefined; - /** - * Creates a new `Terminal` object. - * - * @param options An object containing a set of options. - */ - constructor(options?: ITerminalOptions); - - /** - * Adds an event listener for when the bell is triggered. - * @returns an `IDisposable` to stop listening. - */ - onBell: IEvent; - - /** - * Adds an event listener for when a binary event fires. This is used to - * enable non UTF-8 conformant binary messages to be sent to the backend. - * Currently this is only used for a certain type of mouse reports that - * happen to be not UTF-8 compatible. - * The event value is a JS string, pass it to the underlying pty as - * binary data, e.g. `pty.write(Buffer.from(data, 'binary'))`. - * @returns an `IDisposable` to stop listening. - */ - onBinary: IEvent; - - /** - * Adds an event listener for the cursor moves. - * @returns an `IDisposable` to stop listening. - */ - onCursorMove: IEvent; - - /** - * Adds an event listener for when a data event fires. This happens for - * example when the user types or pastes into the terminal. The event value - * is whatever `string` results, in a typical setup, this should be passed - * on to the backing pty. - * @returns an `IDisposable` to stop listening. - */ - onData: IEvent; - - /** - * Adds an event listener for when a line feed is added. - * @returns an `IDisposable` to stop listening. - */ - onLineFeed: IEvent; - - /** - * Adds an event listener for when the terminal is resized. The event value - * contains the new size. - * @returns an `IDisposable` to stop listening. - */ - onResize: IEvent<{ cols: number, rows: number }>; - - /** - * Adds an event listener for when a scroll occurs. The event value is the - * new position of the viewport. - * @returns an `IDisposable` to stop listening. - */ - onScroll: IEvent; - - /** - * Adds an event listener for when an OSC 0 or OSC 2 title change occurs. - * The event value is the new title. - * @returns an `IDisposable` to stop listening. - */ - onTitleChange: IEvent; - - /** - * Resizes the terminal. It's best practice to debounce calls to resize, - * this will help ensure that the pty can respond to the resize event - * before another one occurs. - * @param x The number of columns to resize to. - * @param y The number of rows to resize to. - */ - resize(columns: number, rows: number): void; - - /** - * (EXPERIMENTAL) Adds a marker to the normal buffer and returns it. If the - * alt buffer is active, undefined is returned. - * @param cursorYOffset The y position offset of the marker from the cursor. - * @returns The new marker or undefined. - */ - registerMarker(cursorYOffset: number): IMarker | undefined; - - /** - * @deprecated use `registerMarker` instead. - */ - addMarker(cursorYOffset: number): IMarker | undefined; - - /* - * Disposes of the terminal, detaching it from the DOM and removing any - * active listeners. - */ - dispose(): void; - - /** - * Scroll the display of the terminal - * @param amount The number of lines to scroll down (negative scroll up). - */ - scrollLines(amount: number): void; - - /** - * Scroll the display of the terminal by a number of pages. - * @param pageCount The number of pages to scroll (negative scrolls up). - */ - scrollPages(pageCount: number): void; - - /** - * Scrolls the display of the terminal to the top. - */ - scrollToTop(): void; - - /** - * Scrolls the display of the terminal to the bottom. - */ - scrollToBottom(): void; - - /** - * Scrolls to a line within the buffer. - * @param line The 0-based line index to scroll to. - */ - scrollToLine(line: number): void; - - /** - * Clear the entire buffer, making the prompt line the new first line. - */ - clear(): void; - - /** - * Write data to the terminal. - * @param data The data to write to the terminal. This can either be raw - * bytes given as Uint8Array from the pty or a string. Raw bytes will always - * be treated as UTF-8 encoded, string data as UTF-16. - * @param callback Optional callback that fires when the data was processed - * by the parser. - */ - write(data: string | Uint8Array, callback?: () => void): void; - - /** - * Writes data to the terminal, followed by a break line character (\n). - * @param data The data to write to the terminal. This can either be raw - * bytes given as Uint8Array from the pty or a string. Raw bytes will always - * be treated as UTF-8 encoded, string data as UTF-16. - * @param callback Optional callback that fires when the data was processed - * by the parser. - */ - writeln(data: string | Uint8Array, callback?: () => void): void; - - /** - * Write UTF8 data to the terminal. - * @param data The data to write to the terminal. - * @param callback Optional callback when data was processed. - * @deprecated use `write` instead - */ - writeUtf8(data: Uint8Array, callback?: () => void): void; - - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell' | 'windowsMode'): boolean; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: string): any; - - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'fontFamily' | 'termName' | 'bellSound' | 'wordSeparator', value: string): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. + /* + * Disposes of the terminal, detaching it from the DOM and removing any + * active listeners. */ - setOption(key: 'fontWeight' | 'fontWeightBold', value: null | 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'logLevel', value: LogLevel): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'bellStyle', value: null | 'none' | 'visual' | 'sound' | 'both'): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'cursorStyle', value: null | 'block' | 'underline' | 'bar'): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'visualBell' | 'windowsMode', value: boolean): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'theme', value: ITheme): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'cols' | 'rows', value: number): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: string, value: any): void; - - /** - * Perform a full reset (RIS, aka '\x1bc'). - */ - reset(): void; - - /** - * Loads an addon into this instance of xterm.js. - * @param addon The addon to load. - */ - loadAddon(addon: ITerminalAddon): void; - } + dispose(): void; /** - * An addon that can provide additional functionality to the terminal. + * Scroll the display of the terminal + * @param amount The number of lines to scroll down (negative scroll up). */ - export interface ITerminalAddon extends IDisposable { - /** - * This is called when the addon is activated. - */ - activate(terminal: Terminal): void; - } + scrollLines(amount: number): void; /** - * An object representing a selection within the terminal. + * Scroll the display of the terminal by a number of pages. + * @param pageCount The number of pages to scroll (negative scrolls up). */ - interface ISelectionPosition { - /** - * The start column of the selection. - */ - startColumn: number; - - /** - * The start row of the selection. - */ - startRow: number; - - /** - * The end column of the selection. - */ - endColumn: number; - - /** - * The end row of the selection. - */ - endRow: number; - } + scrollPages(pageCount: number): void; /** - * An object representing a range within the viewport of the terminal. + * Scrolls the display of the terminal to the top. */ - export interface IViewportRange { - /** - * The start of the range. - */ - start: IViewportRangePosition; - - /** - * The end of the range. - */ - end: IViewportRangePosition; - } + scrollToTop(): void; /** - * An object representing a cell position within the viewport of the terminal. + * Scrolls the display of the terminal to the bottom. */ - interface IViewportRangePosition { - /** - * The x position of the cell. This is a 0-based index that refers to the - * space in between columns, not the column itself. Index 0 refers to the - * left side of the viewport, index `Terminal.cols` refers to the right side - * of the viewport. This can be thought of as how a cursor is positioned in - * a text editor. - */ - x: number; - - /** - * The y position of the cell. This is a 0-based index that refers to a - * specific row. - */ - y: number; - } + scrollToBottom(): void; /** - * A range within a buffer. + * Scrolls to a line within the buffer. + * @param line The 0-based line index to scroll to. */ - interface IBufferRange { - /** - * The start position of the range. - */ - start: IBufferCellPosition; - - /** - * The end position of the range. - */ - end: IBufferCellPosition; - } + scrollToLine(line: number): void; /** - * A position within a buffer. + * Clear the entire buffer, making the prompt line the new first line. */ - interface IBufferCellPosition { - /** - * The x position within the buffer. - */ - x: number; - - /** - * The y position within the buffer. - */ - y: number; - } + clear(): void; /** - * Represents a terminal buffer. + * Write data to the terminal. + * @param data The data to write to the terminal. This can either be raw + * bytes given as Uint8Array from the pty or a string. Raw bytes will always + * be treated as UTF-8 encoded, string data as UTF-16. + * @param callback Optional callback that fires when the data was processed + * by the parser. */ - interface IBuffer { - /** - * The type of the buffer. - */ - readonly type: 'normal' | 'alternate'; - - /** - * The y position of the cursor. This ranges between `0` (when the - * cursor is at baseY) and `Terminal.rows - 1` (when the cursor is on the - * last row). - */ - readonly cursorY: number; - - /** - * The x position of the cursor. This ranges between `0` (left side) and - * `Terminal.cols` (after last cell of the row). - */ - readonly cursorX: number; - - /** - * The line within the buffer where the top of the viewport is. - */ - readonly viewportY: number; - - /** - * The line within the buffer where the top of the bottom page is (when - * fully scrolled down). - */ - readonly baseY: number; - - /** - * The amount of lines in the buffer. - */ - readonly length: number; - - /** - * Gets a line from the buffer, or undefined if the line index does not - * exist. - * - * Note that the result of this function should be used immediately after - * calling as when the terminal updates it could lead to unexpected - * behavior. - * - * @param y The line index to get. - */ - getLine(y: number): IBufferLine | undefined; - - /** - * Creates an empty cell object suitable as a cell reference in - * `line.getCell(x, cell)`. Use this to avoid costly recreation of - * cell objects when dealing with tons of cells. - */ - getNullCell(): IBufferCell; - } + write(data: string | Uint8Array, callback?: () => void): void; /** - * Represents the terminal's set of buffers. + * Writes data to the terminal, followed by a break line character (\n). + * @param data The data to write to the terminal. This can either be raw + * bytes given as Uint8Array from the pty or a string. Raw bytes will always + * be treated as UTF-8 encoded, string data as UTF-16. + * @param callback Optional callback that fires when the data was processed + * by the parser. */ - interface IBufferNamespace { - /** - * The active buffer, this will either be the normal or alternate buffers. - */ - readonly active: IBuffer; - - /** - * The normal buffer. - */ - readonly normal: IBuffer; - - /** - * The alternate buffer, this becomes the active buffer when an application - * enters this mode via DECSET (`CSI ? 4 7 h`) - */ - readonly alternate: IBuffer; - - /** - * Adds an event listener for when the active buffer changes. - * @returns an `IDisposable` to stop listening. - */ - onBufferChange: IEvent; - } + writeln(data: string | Uint8Array, callback?: () => void): void; /** - * Represents a line in the terminal's buffer. + * Write UTF8 data to the terminal. + * @param data The data to write to the terminal. + * @param callback Optional callback when data was processed. + * @deprecated use `write` instead */ - interface IBufferLine { - /** - * Whether the line is wrapped from the previous line. - */ - readonly isWrapped: boolean; - - /** - * The length of the line, all call to getCell beyond the length will result - * in `undefined`. - */ - readonly length: number; - - /** - * Gets a cell from the line, or undefined if the line index does not exist. - * - * Note that the result of this function should be used immediately after - * calling as when the terminal updates it could lead to unexpected - * behavior. - * - * @param x The character index to get. - * @param cell Optional cell object to load data into for performance - * reasons. This is mainly useful when every cell in the buffer is being - * looped over to avoid creating new objects for every cell. - */ - getCell(x: number, cell?: IBufferCell): IBufferCell | undefined; - - /** - * Gets the line as a string. Note that this is gets only the string for the - * line, not taking isWrapped into account. - * - * @param trimRight Whether to trim any whitespace at the right of the line. - * @param startColumn The column to start from (inclusive). - * @param endColumn The column to end at (exclusive). - */ - translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string; - } + writeUtf8(data: Uint8Array, callback?: () => void): void; /** - * Represents a single cell in the terminal's buffer. + * Retrieves an option's value from the terminal. + * @param key The option key. */ - interface IBufferCell { - /** - * The width of the character. Some examples: - * - * - `1` for most cells. - * - `2` for wide character like CJK glyphs. - * - `0` for cells immediately following cells with a width of `2`. - */ - getWidth(): number; - - /** - * The character(s) within the cell. Examples of what this can contain: - * - * - A normal width character - * - A wide character (eg. CJK) - * - An emoji - */ - getChars(): string; - - /** - * Gets the UTF32 codepoint of single characters, if content is a combined - * string it returns the codepoint of the last character in the string. - */ - getCode(): number; - - /** - * Gets the number representation of the foreground color mode, this can be - * used to perform quick comparisons of 2 cells to see if they're the same. - * Use `isFgRGB`, `isFgPalette` and `isFgDefault` to check what color mode - * a cell is. - */ - getFgColorMode(): number; - - /** - * Gets the number representation of the background color mode, this can be - * used to perform quick comparisons of 2 cells to see if they're the same. - * Use `isBgRGB`, `isBgPalette` and `isBgDefault` to check what color mode - * a cell is. - */ - getBgColorMode(): number; - - /** - * Gets a cell's foreground color number, this differs depending on what the - * color mode of the cell is: - * - * - Default: This should be 0, representing the default foreground color - * (CSI 39 m). - * - Palette: This is a number from 0 to 255 of ANSI colors (CSI 3(0-7) m, - * CSI 9(0-7) m, CSI 38 ; 5 ; 0-255 m). - * - RGB: A hex value representing a 'true color': 0xRRGGBB. - * (CSI 3 8 ; 2 ; Pi ; Pr ; Pg ; Pb) - */ - getFgColor(): number; - - /** - * Gets a cell's background color number, this differs depending on what the - * color mode of the cell is: - * - * - Default: This should be 0, representing the default background color - * (CSI 49 m). - * - Palette: This is a number from 0 to 255 of ANSI colors - * (CSI 4(0-7) m, CSI 10(0-7) m, CSI 48 ; 5 ; 0-255 m). - * - RGB: A hex value representing a 'true color': 0xRRGGBB - * (CSI 4 8 ; 2 ; Pi ; Pr ; Pg ; Pb) - */ - getBgColor(): number; - - /** Whether the cell has the bold attribute (CSI 1 m). */ - isBold(): number; - /** Whether the cell has the inverse attribute (CSI 3 m). */ - isItalic(): number; - /** Whether the cell has the inverse attribute (CSI 2 m). */ - isDim(): number; - /** Whether the cell has the underline attribute (CSI 4 m). */ - isUnderline(): number; - /** Whether the cell has the inverse attribute (CSI 5 m). */ - isBlink(): number; - /** Whether the cell has the inverse attribute (CSI 7 m). */ - isInverse(): number; - /** Whether the cell has the inverse attribute (CSI 8 m). */ - isInvisible(): number; - - /** Whether the cell is using the RGB foreground color mode. */ - isFgRGB(): boolean; - /** Whether the cell is using the RGB background color mode. */ - isBgRGB(): boolean; - /** Whether the cell is using the palette foreground color mode. */ - isFgPalette(): boolean; - /** Whether the cell is using the palette background color mode. */ - isBgPalette(): boolean; - /** Whether the cell is using the default foreground color mode. */ - isFgDefault(): boolean; - /** Whether the cell is using the default background color mode. */ - isBgDefault(): boolean; - - /** Whether the cell has the default attribute (no color or style). */ - isAttributeDefault(): boolean; - } + getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; + /** + * Retrieves an option's value from the terminal. + * @param key The option key. + */ + getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell' | 'windowsMode'): boolean; + /** + * Retrieves an option's value from the terminal. + * @param key The option key. + */ + getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; + /** + * Retrieves an option's value from the terminal. + * @param key The option key. + */ + getOption(key: string): any; /** - * Data type to register a CSI, DCS or ESC callback in the parser - * in the form: - * ESC I..I F - * CSI Prefix P..P I..I F - * DCS Prefix P..P I..I F data_bytes ST - * - * with these rules/restrictions: - * - prefix can only be used with CSI and DCS - * - only one leading prefix byte is recognized by the parser - * before any other parameter bytes (P..P) - * - intermediate bytes are recognized up to 2 - * - * For custom sequences make sure to read ECMA-48 and the resources at - * vt100.net to not clash with existing sequences or reserved address space. - * General recommendations: - * - use private address space (see ECMA-48) - * - use max one intermediate byte (technically not limited by the spec, - * in practice there are no sequences with more than one intermediate byte, - * thus parsers might get confused with more intermediates) - * - test against other common emulators to check whether they escape/ignore - * the sequence correctly - * - * Notes: OSC command registration is handled differently (see addOscHandler) - * APC, PM or SOS is currently not supported. + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. */ - export interface IFunctionIdentifier { - /** - * Optional prefix byte, must be in range \x3c .. \x3f. - * Usable in CSI and DCS. - */ - prefix?: string; - /** - * Optional intermediate bytes, must be in range \x20 .. \x2f. - * Usable in CSI, DCS and ESC. - */ - intermediates?: string; - /** - * Final byte, must be in range \x40 .. \x7e for CSI and DCS, - * \x30 .. \x7e for ESC. - */ - final: string; - } + setOption(key: 'fontFamily' | 'termName' | 'bellSound' | 'wordSeparator', value: string): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'fontWeight' | 'fontWeightBold', value: null | 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'logLevel', value: LogLevel): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'bellStyle', value: null | 'none' | 'visual' | 'sound' | 'both'): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'cursorStyle', value: null | 'block' | 'underline' | 'bar'): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'visualBell' | 'windowsMode', value: boolean): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'theme', value: ITheme): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: 'cols' | 'rows', value: number): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: string, value: any): void; /** - * Allows hooking into the parser for custom handling of escape sequences. + * Perform a full reset (RIS, aka '\x1bc'). */ - export interface IParser { - /** - * Adds a handler for CSI escape sequences. - * @param id Specifies the function identifier under which the callback - * gets registered, e.g. {final: 'm'} for SGR. - * @param callback The function to handle the sequence. The callback is - * called with the numerical params. If the sequence has subparams the - * array will contain subarrays with their numercial values. - * Return true if the sequence was handled; false if we should try - * a previous handler (set by addCsiHandler or setCsiHandler). - * The most recently added handler is tried first. - * @return An IDisposable you can call to remove this handler. - */ - registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable; - - /** - * Adds a handler for DCS escape sequences. - * @param id Specifies the function identifier under which the callback - * gets registered, e.g. {intermediates: '$' final: 'q'} for DECRQSS. - * @param callback The function to handle the sequence. Note that the - * function will only be called once if the sequence finished sucessfully. - * There is currently no way to intercept smaller data chunks, data chunks - * will be stored up until the sequence is finished. Since DCS sequences - * are not limited by the amount of data this might impose a problem for - * big payloads. Currently xterm.js limits DCS payload to 10 MB - * which should give enough room for most use cases. - * The function gets the payload and numerical parameters as arguments. - * Return true if the sequence was handled; false if we should try - * a previous handler (set by addDcsHandler or setDcsHandler). - * The most recently added handler is tried first. - * @return An IDisposable you can call to remove this handler. - */ - registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable; - - /** - * Adds a handler for ESC escape sequences. - * @param id Specifies the function identifier under which the callback - * gets registered, e.g. {intermediates: '%' final: 'G'} for - * default charset selection. - * @param callback The function to handle the sequence. - * Return true if the sequence was handled; false if we should try - * a previous handler (set by addEscHandler or setEscHandler). - * The most recently added handler is tried first. - * @return An IDisposable you can call to remove this handler. - */ - registerEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable; - - /** - * Adds a handler for OSC escape sequences. - * @param ident The number (first parameter) of the sequence. - * @param callback The function to handle the sequence. Note that the - * function will only be called once if the sequence finished sucessfully. - * There is currently no way to intercept smaller data chunks, data chunks - * will be stored up until the sequence is finished. Since OSC sequences - * are not limited by the amount of data this might impose a problem for - * big payloads. Currently xterm.js limits OSC payload to 10 MB - * which should give enough room for most use cases. - * The callback is called with OSC data string. - * Return true if the sequence was handled; false if we should try - * a previous handler (set by addOscHandler or setOscHandler). - * The most recently added handler is tried first. - * @return An IDisposable you can call to remove this handler. - */ - registerOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; - } + reset(): void; /** - * (EXPERIMENTAL) Unicode version provider. - * Used to register custom Unicode versions with `Terminal.unicode.register`. + * Loads an addon into this instance of xterm.js. + * @param addon The addon to load. */ - export interface IUnicodeVersionProvider { - /** - * String indicating the Unicode version provided. - */ - readonly version: string; - - /** - * Unicode version dependent wcwidth implementation. - */ - wcwidth(codepoint: number): 0 | 1 | 2; - } - - /** - * (EXPERIMENTAL) Unicode handling interface. - */ - export interface IUnicodeHandling { - /** - * Register a custom Unicode version provider. - */ - register(provider: IUnicodeVersionProvider): void; - - /** - * Registered Unicode versions. - */ - readonly versions: ReadonlyArray; - - /** - * Getter/setter for active Unicode version. - */ - activeVersion: string; - } + loadAddon(addon: ITerminalAddon): void; } + + /** + * An addon that can provide additional functionality to the terminal. + */ + export interface ITerminalAddon extends IDisposable { + /** + * This is called when the addon is activated. + */ + activate(terminal: Terminal): void; + } + + /** + * An object representing a selection within the terminal. + */ + interface ISelectionPosition { + /** + * The start column of the selection. + */ + startColumn: number; + + /** + * The start row of the selection. + */ + startRow: number; + + /** + * The end column of the selection. + */ + endColumn: number; + + /** + * The end row of the selection. + */ + endRow: number; + } + + /** + * An object representing a range within the viewport of the terminal. + */ + export interface IViewportRange { + /** + * The start of the range. + */ + start: IViewportRangePosition; + + /** + * The end of the range. + */ + end: IViewportRangePosition; + } + + /** + * An object representing a cell position within the viewport of the terminal. + */ + interface IViewportRangePosition { + /** + * The x position of the cell. This is a 0-based index that refers to the + * space in between columns, not the column itself. Index 0 refers to the + * left side of the viewport, index `Terminal.cols` refers to the right side + * of the viewport. This can be thought of as how a cursor is positioned in + * a text editor. + */ + x: number; + + /** + * The y position of the cell. This is a 0-based index that refers to a + * specific row. + */ + y: number; + } + + /** + * A range within a buffer. + */ + interface IBufferRange { + /** + * The start position of the range. + */ + start: IBufferCellPosition; + + /** + * The end position of the range. + */ + end: IBufferCellPosition; + } + + /** + * A position within a buffer. + */ + interface IBufferCellPosition { + /** + * The x position within the buffer. + */ + x: number; + + /** + * The y position within the buffer. + */ + y: number; + } + + /** + * Represents a terminal buffer. + */ + interface IBuffer { + /** + * The type of the buffer. + */ + readonly type: 'normal' | 'alternate'; + + /** + * The y position of the cursor. This ranges between `0` (when the + * cursor is at baseY) and `Terminal.rows - 1` (when the cursor is on the + * last row). + */ + readonly cursorY: number; + + /** + * The x position of the cursor. This ranges between `0` (left side) and + * `Terminal.cols` (after last cell of the row). + */ + readonly cursorX: number; + + /** + * The line within the buffer where the top of the viewport is. + */ + readonly viewportY: number; + + /** + * The line within the buffer where the top of the bottom page is (when + * fully scrolled down). + */ + readonly baseY: number; + + /** + * The amount of lines in the buffer. + */ + readonly length: number; + + /** + * Gets a line from the buffer, or undefined if the line index does not + * exist. + * + * Note that the result of this function should be used immediately after + * calling as when the terminal updates it could lead to unexpected + * behavior. + * + * @param y The line index to get. + */ + getLine(y: number): IBufferLine | undefined; + + /** + * Creates an empty cell object suitable as a cell reference in + * `line.getCell(x, cell)`. Use this to avoid costly recreation of + * cell objects when dealing with tons of cells. + */ + getNullCell(): IBufferCell; + } + + /** + * Represents the terminal's set of buffers. + */ + interface IBufferNamespace { + /** + * The active buffer, this will either be the normal or alternate buffers. + */ + readonly active: IBuffer; + + /** + * The normal buffer. + */ + readonly normal: IBuffer; + + /** + * The alternate buffer, this becomes the active buffer when an application + * enters this mode via DECSET (`CSI ? 4 7 h`) + */ + readonly alternate: IBuffer; + + /** + * Adds an event listener for when the active buffer changes. + * @returns an `IDisposable` to stop listening. + */ + onBufferChange: IEvent; + } + + /** + * Represents a line in the terminal's buffer. + */ + interface IBufferLine { + /** + * Whether the line is wrapped from the previous line. + */ + readonly isWrapped: boolean; + + /** + * The length of the line, all call to getCell beyond the length will result + * in `undefined`. + */ + readonly length: number; + + /** + * Gets a cell from the line, or undefined if the line index does not exist. + * + * Note that the result of this function should be used immediately after + * calling as when the terminal updates it could lead to unexpected + * behavior. + * + * @param x The character index to get. + * @param cell Optional cell object to load data into for performance + * reasons. This is mainly useful when every cell in the buffer is being + * looped over to avoid creating new objects for every cell. + */ + getCell(x: number, cell?: IBufferCell): IBufferCell | undefined; + + /** + * Gets the line as a string. Note that this is gets only the string for the + * line, not taking isWrapped into account. + * + * @param trimRight Whether to trim any whitespace at the right of the line. + * @param startColumn The column to start from (inclusive). + * @param endColumn The column to end at (exclusive). + */ + translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string; + } + + /** + * Represents a single cell in the terminal's buffer. + */ + interface IBufferCell { + /** + * The width of the character. Some examples: + * + * - `1` for most cells. + * - `2` for wide character like CJK glyphs. + * - `0` for cells immediately following cells with a width of `2`. + */ + getWidth(): number; + + /** + * The character(s) within the cell. Examples of what this can contain: + * + * - A normal width character + * - A wide character (eg. CJK) + * - An emoji + */ + getChars(): string; + + /** + * Gets the UTF32 codepoint of single characters, if content is a combined + * string it returns the codepoint of the last character in the string. + */ + getCode(): number; + + /** + * Gets the number representation of the foreground color mode, this can be + * used to perform quick comparisons of 2 cells to see if they're the same. + * Use `isFgRGB`, `isFgPalette` and `isFgDefault` to check what color mode + * a cell is. + */ + getFgColorMode(): number; + + /** + * Gets the number representation of the background color mode, this can be + * used to perform quick comparisons of 2 cells to see if they're the same. + * Use `isBgRGB`, `isBgPalette` and `isBgDefault` to check what color mode + * a cell is. + */ + getBgColorMode(): number; + + /** + * Gets a cell's foreground color number, this differs depending on what the + * color mode of the cell is: + * + * - Default: This should be 0, representing the default foreground color + * (CSI 39 m). + * - Palette: This is a number from 0 to 255 of ANSI colors (CSI 3(0-7) m, + * CSI 9(0-7) m, CSI 38 ; 5 ; 0-255 m). + * - RGB: A hex value representing a 'true color': 0xRRGGBB. + * (CSI 3 8 ; 2 ; Pi ; Pr ; Pg ; Pb) + */ + getFgColor(): number; + + /** + * Gets a cell's background color number, this differs depending on what the + * color mode of the cell is: + * + * - Default: This should be 0, representing the default background color + * (CSI 49 m). + * - Palette: This is a number from 0 to 255 of ANSI colors + * (CSI 4(0-7) m, CSI 10(0-7) m, CSI 48 ; 5 ; 0-255 m). + * - RGB: A hex value representing a 'true color': 0xRRGGBB + * (CSI 4 8 ; 2 ; Pi ; Pr ; Pg ; Pb) + */ + getBgColor(): number; + + /** Whether the cell has the bold attribute (CSI 1 m). */ + isBold(): number; + /** Whether the cell has the inverse attribute (CSI 3 m). */ + isItalic(): number; + /** Whether the cell has the inverse attribute (CSI 2 m). */ + isDim(): number; + /** Whether the cell has the underline attribute (CSI 4 m). */ + isUnderline(): number; + /** Whether the cell has the inverse attribute (CSI 5 m). */ + isBlink(): number; + /** Whether the cell has the inverse attribute (CSI 7 m). */ + isInverse(): number; + /** Whether the cell has the inverse attribute (CSI 8 m). */ + isInvisible(): number; + + /** Whether the cell is using the RGB foreground color mode. */ + isFgRGB(): boolean; + /** Whether the cell is using the RGB background color mode. */ + isBgRGB(): boolean; + /** Whether the cell is using the palette foreground color mode. */ + isFgPalette(): boolean; + /** Whether the cell is using the palette background color mode. */ + isBgPalette(): boolean; + /** Whether the cell is using the default foreground color mode. */ + isFgDefault(): boolean; + /** Whether the cell is using the default background color mode. */ + isBgDefault(): boolean; + + /** Whether the cell has the default attribute (no color or style). */ + isAttributeDefault(): boolean; + } + + /** + * Data type to register a CSI, DCS or ESC callback in the parser + * in the form: + * ESC I..I F + * CSI Prefix P..P I..I F + * DCS Prefix P..P I..I F data_bytes ST + * + * with these rules/restrictions: + * - prefix can only be used with CSI and DCS + * - only one leading prefix byte is recognized by the parser + * before any other parameter bytes (P..P) + * - intermediate bytes are recognized up to 2 + * + * For custom sequences make sure to read ECMA-48 and the resources at + * vt100.net to not clash with existing sequences or reserved address space. + * General recommendations: + * - use private address space (see ECMA-48) + * - use max one intermediate byte (technically not limited by the spec, + * in practice there are no sequences with more than one intermediate byte, + * thus parsers might get confused with more intermediates) + * - test against other common emulators to check whether they escape/ignore + * the sequence correctly + * + * Notes: OSC command registration is handled differently (see addOscHandler) + * APC, PM or SOS is currently not supported. + */ + export interface IFunctionIdentifier { + /** + * Optional prefix byte, must be in range \x3c .. \x3f. + * Usable in CSI and DCS. + */ + prefix?: string; + /** + * Optional intermediate bytes, must be in range \x20 .. \x2f. + * Usable in CSI, DCS and ESC. + */ + intermediates?: string; + /** + * Final byte, must be in range \x40 .. \x7e for CSI and DCS, + * \x30 .. \x7e for ESC. + */ + final: string; + } + + /** + * Allows hooking into the parser for custom handling of escape sequences. + */ + export interface IParser { + /** + * Adds a handler for CSI escape sequences. + * @param id Specifies the function identifier under which the callback + * gets registered, e.g. {final: 'm'} for SGR. + * @param callback The function to handle the sequence. The callback is + * called with the numerical params. If the sequence has subparams the + * array will contain subarrays with their numercial values. + * Return true if the sequence was handled; false if we should try + * a previous handler (set by addCsiHandler or setCsiHandler). + * The most recently added handler is tried first. + * @return An IDisposable you can call to remove this handler. + */ + registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable; + + /** + * Adds a handler for DCS escape sequences. + * @param id Specifies the function identifier under which the callback + * gets registered, e.g. {intermediates: '$' final: 'q'} for DECRQSS. + * @param callback The function to handle the sequence. Note that the + * function will only be called once if the sequence finished sucessfully. + * There is currently no way to intercept smaller data chunks, data chunks + * will be stored up until the sequence is finished. Since DCS sequences + * are not limited by the amount of data this might impose a problem for + * big payloads. Currently xterm.js limits DCS payload to 10 MB + * which should give enough room for most use cases. + * The function gets the payload and numerical parameters as arguments. + * Return true if the sequence was handled; false if we should try + * a previous handler (set by addDcsHandler or setDcsHandler). + * The most recently added handler is tried first. + * @return An IDisposable you can call to remove this handler. + */ + registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable; + + /** + * Adds a handler for ESC escape sequences. + * @param id Specifies the function identifier under which the callback + * gets registered, e.g. {intermediates: '%' final: 'G'} for + * default charset selection. + * @param callback The function to handle the sequence. + * Return true if the sequence was handled; false if we should try + * a previous handler (set by addEscHandler or setEscHandler). + * The most recently added handler is tried first. + * @return An IDisposable you can call to remove this handler. + */ + registerEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable; + + /** + * Adds a handler for OSC escape sequences. + * @param ident The number (first parameter) of the sequence. + * @param callback The function to handle the sequence. Note that the + * function will only be called once if the sequence finished sucessfully. + * There is currently no way to intercept smaller data chunks, data chunks + * will be stored up until the sequence is finished. Since OSC sequences + * are not limited by the amount of data this might impose a problem for + * big payloads. Currently xterm.js limits OSC payload to 10 MB + * which should give enough room for most use cases. + * The callback is called with OSC data string. + * Return true if the sequence was handled; false if we should try + * a previous handler (set by addOscHandler or setOscHandler). + * The most recently added handler is tried first. + * @return An IDisposable you can call to remove this handler. + */ + registerOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; + } + + /** + * (EXPERIMENTAL) Unicode version provider. + * Used to register custom Unicode versions with `Terminal.unicode.register`. + */ + export interface IUnicodeVersionProvider { + /** + * String indicating the Unicode version provided. + */ + readonly version: string; + + /** + * Unicode version dependent wcwidth implementation. + */ + wcwidth(codepoint: number): 0 | 1 | 2; + } + + /** + * (EXPERIMENTAL) Unicode handling interface. + */ + export interface IUnicodeHandling { + /** + * Register a custom Unicode version provider. + */ + register(provider: IUnicodeVersionProvider): void; + + /** + * Registered Unicode versions. + */ + readonly versions: ReadonlyArray; + + /** + * Getter/setter for active Unicode version. + */ + activeVersion: string; + } + + /** + * Terminal modes as set by SM/DECSET. + */ + export interface IModes { + /** + * Application Cursor Keys (DECCKM): `CSI ? 1 h` + */ + readonly applicationCursorKeysMode: boolean; + /** + * Application Keypad Mode (DECNKM): `CSI ? 6 6 h` + */ + readonly applicationKeypadMode: boolean; + /** + * Bracketed Paste Mode: `CSI ? 2 0 0 4 h` + */ + readonly bracketedPasteMode: boolean; + /** + * Insert Mode (IRM): `CSI 4 h` + */ + readonly insertMode: boolean; + /** + * Mouse Tracking, this can be one of the following: + * - none: This is the default value and can be reset with DECRST + * - x10: Send Mouse X & Y on button press `CSI ? 9 h` + * - vt200: Send Mouse X & Y on button press and release `CSI ? 1 0 0 0 h` + * - drag: Use Cell Motion Mouse Tracking `CSI ? 1 0 0 2 h` + * - any: Use All Motion Mouse Tracking `CSI ? 1 0 0 3 h` + */ + readonly mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any'; + /** + * Origin Mode (DECOM): `CSI ? 6 h` + */ + readonly originMode: boolean; + /** + * Reverse-wraparound Mode: `CSI ? 4 5 h` + */ + readonly reverseWraparoundMode: boolean; + /** + * Send FocusIn/FocusOut events: `CSI ? 1 0 0 4 h` + */ + readonly sendFocusMode: boolean; + /** + * Auto-Wrap Mode (DECAWM): `CSI ? 7 h` + */ + readonly wraparoundMode: boolean + } +} diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 210e354d..3828b415 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1666,7 +1666,7 @@ declare module 'xterm' { */ readonly reverseWraparoundMode: boolean; /** - * Send FocusIn/FocusOut events: `CSI ? 1 0 0 3 h` + * Send FocusIn/FocusOut events: `CSI ? 1 0 0 4 h` */ readonly sendFocusMode: boolean; /** From bef865e1967637b0c07486b23cfc82ded14e27a2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 16 Aug 2021 15:06:29 -0700 Subject: [PATCH 4/9] Add xterm api tests --- test/api/Terminal.api.ts | 92 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 75399b73..1599f3a2 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -562,6 +562,98 @@ describe('API Integration Tests', function(): void { }); }); + describe('modes', () => { + it('defaults', async () => { + await openTerminal(page); + assert.deepStrictEqual(await page.evaluate(`window.term.modes`), { + applicationCursorKeysMode: false, + applicationKeypadMode: false, + bracketedPasteMode: false, + insertMode: false, + mouseTrackingMode: 'none', + originMode: false, + reverseWraparoundMode: false, + sendFocusMode: false, + wraparoundMode: true + }); + }); + it('applicationCursorKeysMode', async () => { + await openTerminal(page); + await writeSync(page, '\\x1b[?1h'); + assert.strictEqual(await page.evaluate(`window.term.modes.applicationCursorKeysMode`), true); + await writeSync(page, '\\x1b[?1l'); + assert.strictEqual(await page.evaluate(`window.term.modes.applicationCursorKeysMode`), false); + }); + it('applicationKeypadMode', async () => { + await openTerminal(page); + await writeSync(page, '\\x1b[?66h'); + assert.strictEqual(await page.evaluate(`window.term.modes.applicationKeypadMode`), true); + await writeSync(page, '\\x1b[?66l'); + assert.strictEqual(await page.evaluate(`window.term.modes.applicationKeypadMode`), false); + }); + it('bracketedPasteMode', async () => { + await openTerminal(page); + await writeSync(page, '\\x1b[?2004h'); + assert.strictEqual(await page.evaluate(`window.term.modes.bracketedPasteMode`), true); + await writeSync(page, '\\x1b[?2004l'); + assert.strictEqual(await page.evaluate(`window.term.modes.bracketedPasteMode`), false); + }); + it('insertMode', async () => { + await openTerminal(page); + await writeSync(page, '\\x1b[4h'); + assert.strictEqual(await page.evaluate(`window.term.modes.insertMode`), true); + await writeSync(page, '\\x1b[4l'); + assert.strictEqual(await page.evaluate(`window.term.modes.insertMode`), false); + }); + it('mouseTrackingMode', async () => { + await openTerminal(page); + await writeSync(page, '\\x1b[?9h'); + assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'x10'); + await writeSync(page, '\\x1b[?9l'); + assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'none'); + await writeSync(page, '\\x1b[?1000h'); + assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'vt200'); + await writeSync(page, '\\x1b[?1000l'); + assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'none'); + await writeSync(page, '\\x1b[?1002h'); + assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'drag'); + await writeSync(page, '\\x1b[?1002l'); + assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'none'); + await writeSync(page, '\\x1b[?1003h'); + assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'any'); + await writeSync(page, '\\x1b[?1003l'); + assert.strictEqual(await page.evaluate(`window.term.modes.mouseTrackingMode`), 'none'); + }); + it('originMode', async () => { + await openTerminal(page); + await writeSync(page, '\\x1b[?6h'); + assert.strictEqual(await page.evaluate(`window.term.modes.originMode`), true); + await writeSync(page, '\\x1b[?6l'); + assert.strictEqual(await page.evaluate(`window.term.modes.originMode`), false); + }); + it('reverseWraparoundMode', async () => { + await openTerminal(page); + await writeSync(page, '\\x1b[?45h'); + assert.strictEqual(await page.evaluate(`window.term.modes.reverseWraparoundMode`), true); + await writeSync(page, '\\x1b[?45l'); + assert.strictEqual(await page.evaluate(`window.term.modes.reverseWraparoundMode`), false); + }); + it('sendFocusMode', async () => { + await openTerminal(page); + await writeSync(page, '\\x1b[?1004h'); + assert.strictEqual(await page.evaluate(`window.term.modes.sendFocusMode`), true); + await writeSync(page, '\\x1b[?1004l'); + assert.strictEqual(await page.evaluate(`window.term.modes.sendFocusMode`), false); + }); + it('wraparoundMode', async () => { + await openTerminal(page); + await writeSync(page, '\\x1b[?7h'); + assert.strictEqual(await page.evaluate(`window.term.modes.wraparoundMode`), true); + await writeSync(page, '\\x1b[?7l'); + assert.strictEqual(await page.evaluate(`window.term.modes.wraparoundMode`), false); + }); + }); + it('dispose', async () => { await page.evaluate(` window.term = new Terminal(); From 6c834aecb4bc1e08e9710cf6cf54c90c23a69f1d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 16 Aug 2021 15:31:34 -0700 Subject: [PATCH 5/9] Serialize modes Fixes #3417 --- .../src/SerializeAddon.ts | 32 ++++++++++++ .../test/SerializeAddon.api.ts | 52 +++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index 5cd834b8..72780765 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -403,6 +403,35 @@ export class SerializeAddon implements ITerminalAddon { return handler.serialize(maxRows - correctRows, maxRows); } + private _serializeModes(terminal: Terminal): string { + let content = ''; + const modes = terminal.modes; + + // Default: false + if (modes.applicationCursorKeysMode) content += '\x1b[?1h'; + if (modes.applicationKeypadMode) content += '\x1b[?66h'; + if (modes.bracketedPasteMode) content += '\x1b[?2004h'; + if (modes.insertMode) content += '\x1b[4h'; + if (modes.originMode) content += '\x1b[?6h'; + if (modes.reverseWraparoundMode) content += '\x1b[?45h'; + if (modes.sendFocusMode) content += '\x1b[?1004h'; + + // Default: true + if (modes.wraparoundMode === false) content += '\x1b[?7l'; + + // Default: 'none' + if (modes.mouseTrackingMode !== 'none') { + switch (modes.mouseTrackingMode) { + case 'x10': content += '\x1b[?9h'; break; + case 'vt200': content += '\x1b[?1000h'; break; + case 'drag': content += '\x1b[?1002h'; break; + case 'any': content += '\x1b[?1003h'; break; + } + } + + return content; + } + public serialize(scrollback?: number): string { // TODO: Add combinedData support if (!this._terminal) { @@ -418,6 +447,9 @@ export class SerializeAddon implements ITerminalAddon { content += `\u001b[?1049h\u001b[H${alternativeScreenContent}`; } + // Modes + content += this._serializeModes(this._terminal); + return content; } diff --git a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts index c8b7302f..87f77f59 100644 --- a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts +++ b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts @@ -30,6 +30,12 @@ const testNormalScreenEqual = async (page: any, str: string): Promise => { assert.equal(JSON.stringify(originalBuffer), JSON.stringify(newBuffer)); }; +async function testSerializeEquals(writeContent: string, expectedSerialized: string): Promise { + await writeRawSync(page, writeContent); + const result = await page.evaluate(`serializeAddon.serialize();`) as string; + assert.strictEqual(result, expectedSerialized); +} + describe('SerializeAddon', () => { before(async function(): Promise { const browserType = getBrowserType(); @@ -479,6 +485,52 @@ describe('SerializeAddon', () => { await testNormalScreenEqual(page, lines.join('')); }); + + describe('handle modes', () => { + it('applicationCursorKeysMode', async () => { + await testSerializeEquals('test\u001b[?1h', 'test\u001b[?1h'); + await testSerializeEquals('\u001b[?1l', 'test'); + }); + it('applicationKeypadMode', async () => { + await testSerializeEquals('test\u001b[?66h', 'test\u001b[?66h'); + await testSerializeEquals('\u001b[?66l', 'test'); + }); + it('bracketedPasteMode', async () => { + await testSerializeEquals('test\u001b[?2004h', 'test\u001b[?2004h'); + await testSerializeEquals('\u001b[?2004l', 'test'); + }); + it('insertMode', async () => { + await testSerializeEquals('test\u001b[4h', 'test\u001b[4h'); + await testSerializeEquals('\u001b[4l', 'test'); + }); + it('mouseTrackingMode', async () => { + await testSerializeEquals('test\u001b[?9h', 'test\u001b[?9h'); + await testSerializeEquals('\u001b[?9l', 'test'); + await testSerializeEquals('\u001b[?1000h', 'test\u001b[?1000h'); + await testSerializeEquals('\u001b[?1000l', 'test'); + await testSerializeEquals('\u001b[?1002h', 'test\u001b[?1002h'); + await testSerializeEquals('\u001b[?1002l', 'test'); + await testSerializeEquals('\u001b[?1003h', 'test\u001b[?1003h'); + await testSerializeEquals('\u001b[?1003l', 'test'); + }); + it('originMode', async () => { + // origin mode moves cursor to (0,0) + await testSerializeEquals('test\u001b[?6h', 'test\u001b[4D\u001b[?6h'); + await testSerializeEquals('\u001b[?6l', 'test\u001b[4D'); + }); + it('reverseWraparoundMode', async () => { + await testSerializeEquals('test\u001b[?45h', 'test\u001b[?45h'); + await testSerializeEquals('\u001b[?45l', 'test'); + }); + it('sendFocusMode', async () => { + await testSerializeEquals('test\u001b[?1004h', 'test\u001b[?1004h'); + await testSerializeEquals('\u001b[?1004l', 'test'); + }); + it('wraparoundMode', async () => { + await testSerializeEquals('test\u001b[?7l', 'test\u001b[?7l'); + await testSerializeEquals('\u001b[?7h', 'test'); + }); + }); }); function newArray(initial: T | ((index: number) => T), count: number): T[] { From c1b4f4edb15f9b9a55dabf373156417dfcea8cd8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 Aug 2021 04:41:44 -0700 Subject: [PATCH 6/9] Fix webgl access of core service --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 +- .../xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 9b75d1de..29e97d6d 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -60,7 +60,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers = [ new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core), - new CursorRenderLayer(this._core.screenElement!, 3, this._colors, this._onRequestRedraw) + new CursorRenderLayer(this._core.screenElement!, 3, this._colors, this._core, this._onRequestRedraw) ]; this.dimensions = { scaledCharWidth: 0, diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index 880896e0..912c23c9 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -7,7 +7,7 @@ import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ITerminal } from 'browser/Types'; import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; import { IEventEmitter } from 'common/EventEmitter'; @@ -34,6 +34,7 @@ export class CursorRenderLayer extends BaseRenderLayer { container: HTMLElement, zIndex: number, colors: IColorSet, + private readonly _terminal: ITerminal, private _onRequestRefreshRowsEvent: IEventEmitter ) { super(container, 'cursor', zIndex, true, colors); @@ -120,7 +121,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _render(terminal: Terminal, triggeredByAnimationFrame: boolean): void { // Don't draw the cursor if it's hidden // TODO: Need to expose API for this - if (!(terminal as any)._core._coreService.isCursorInitialized || (terminal as any)._core._coreService.isCursorHidden) { + if (!this._terminal.coreService.isCursorInitialized || this._terminal.coreService.isCursorHidden) { this._clearCursor(); return; } From 280ce85b748cabd299fb2e1f848063979ba78ece Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 Aug 2021 04:50:29 -0700 Subject: [PATCH 7/9] Remove role=document from terminal element Part of microsoft/vscode#98918 --- src/browser/Terminal.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 004f2314..8072d6cb 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -408,7 +408,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this.element.classList.add('terminal'); this.element.classList.add('xterm'); this.element.setAttribute('tabindex', '0'); - this.element.setAttribute('role', 'document'); parent.appendChild(this.element); // Performance: Use a document fragment to build the terminal From 7c0a28eb59304f919f02ef8bd94efd2b172f0f25 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 Aug 2021 04:54:44 -0700 Subject: [PATCH 8/9] Call out xterm-headless in main readme Fixes #3412 --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 479c8113..720e703c 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,10 @@ We also partially support *Internet Explorer 11*, meaning xterm.js should work f Xterm.js works seamlessly in [Electron](https://electronjs.org/) apps and may even work on earlier versions of the browsers. These are the versions we strive to keep working. +### Node.js Support + +We also publish [`xterm-headless`](https://www.npmjs.com/package/xterm-headless) which is a stripped down version of xterm.js that runs in Node.js. An example use case for this is to keep track of a terminal's state where the process is running and using the serialize addon so it can get all state restored upon reconnection. + ## API The full API for xterm.js is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API. From 7a2621f6abbb58d80e77d213182374bbc4b3199a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 Aug 2021 05:48:52 -0700 Subject: [PATCH 9/9] Make serialize and unicode11 compatible with node Fixes #3410 Fixes #3411 --- addons/xterm-addon-serialize/src/SerializeAddon.ts | 1 + addons/xterm-addon-serialize/webpack.config.js | 3 ++- addons/xterm-addon-unicode11/src/Unicode11Addon.ts | 1 + addons/xterm-addon-unicode11/webpack.config.js | 3 ++- headless/package.json | 9 --------- 5 files changed, 6 insertions(+), 11 deletions(-) delete mode 100644 headless/package.json diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index 0caaaa03..0cf474e6 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -7,6 +7,7 @@ import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm'; + function constrain(value: number, low: number, high: number): number { return Math.max(low, Math.min(value, high)); } diff --git a/addons/xterm-addon-serialize/webpack.config.js b/addons/xterm-addon-serialize/webpack.config.js index 4cabbad9..7c0ccd01 100644 --- a/addons/xterm-addon-serialize/webpack.config.js +++ b/addons/xterm-addon-serialize/webpack.config.js @@ -25,7 +25,8 @@ module.exports = { filename: mainFile, path: path.resolve('./lib'), library: addonName, - libraryTarget: 'umd' + libraryTarget: 'umd', + globalObject: 'this' }, mode: 'production' }; diff --git a/addons/xterm-addon-unicode11/src/Unicode11Addon.ts b/addons/xterm-addon-unicode11/src/Unicode11Addon.ts index cb8c9c56..d4ddf77e 100644 --- a/addons/xterm-addon-unicode11/src/Unicode11Addon.ts +++ b/addons/xterm-addon-unicode11/src/Unicode11Addon.ts @@ -8,6 +8,7 @@ import { Terminal, ITerminalAddon } from 'xterm'; import { UnicodeV11 } from './UnicodeV11'; + export class Unicode11Addon implements ITerminalAddon { public activate(terminal: Terminal): void { terminal.unicode.register(new UnicodeV11()); diff --git a/addons/xterm-addon-unicode11/webpack.config.js b/addons/xterm-addon-unicode11/webpack.config.js index 66a83a2f..10c0adc3 100644 --- a/addons/xterm-addon-unicode11/webpack.config.js +++ b/addons/xterm-addon-unicode11/webpack.config.js @@ -32,7 +32,8 @@ module.exports = { filename: mainFile, path: path.resolve('./lib'), library: addonName, - libraryTarget: 'umd' + libraryTarget: 'umd', + globalObject: 'this' }, mode: 'production' }; diff --git a/headless/package.json b/headless/package.json deleted file mode 100644 index d53761e7..00000000 --- a/headless/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "xterm-headless", - "description": "A headless terminal component that runs in Node.js", - "version": "4.13.0-alpha3", - "main": "lib-headless/xterm-headless.js", - "types": "typings/xterm-headless.d.ts", - "repository": "https://github.com/xtermjs/xterm.js", - "license": "MIT" -} \ No newline at end of file