diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index daf7f6fc..7165edb3 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ITerminalOptions } from '../../../src/Types'; +import { ITerminalOptions } from '../../../src/common/Types'; import { ITheme } from 'xterm'; import { assert } from 'chai'; import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils'; diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts deleted file mode 100644 index 84d4dae5..00000000 --- a/src/InputHandler.test.ts +++ /dev/null @@ -1,1497 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert, expect } from 'chai'; -import { InputHandler } from './InputHandler'; -import { MockInputHandlingTerminal, TestTerminal } from './TestUtils.test'; -import { Terminal } from './Terminal'; -import { IBufferLine, IAttributeData } from 'common/Types'; -import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; -import { CellData } from 'common/buffer/CellData'; -import { Attributes } from 'common/buffer/Constants'; -import { AttributeData } from 'common/buffer/AttributeData'; -import { Params } from 'common/parser/Params'; -import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService, MockCharsetService, MockUnicodeService } from 'common/TestUtils.test'; -import { IBufferService } from 'common/services/Services'; -import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; -import { clone } from 'common/Clone'; - -function getCursor(term: TestTerminal): number[] { - return [ - term.buffer.x, - term.buffer.y - ]; -} - -function getLines(term: TestTerminal, limit: number = term.rows): string[] { - const res: string[] = []; - for (let i = 0; i < limit; ++i) { - res.push(term.buffer.lines.get(i).translateToString(true)); - } - return res; -} - -class TestInputHandler extends InputHandler { - public get curAttrData(): IAttributeData { return (this as any)._curAttrData; } -} - -describe('InputHandler', () => { - describe('save and restore cursor', () => { - const terminal = new MockInputHandlingTerminal(); - const bufferService = new MockBufferService(80, 30); - bufferService.buffer.x = 1; - bufferService.buffer.y = 2; - bufferService.buffer.ybase = 0; - const inputHandler = new TestInputHandler(terminal, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - inputHandler.curAttrData.fg = 3; - // Save cursor position - inputHandler.saveCursor(); - assert.equal(bufferService.buffer.x, 1); - assert.equal(bufferService.buffer.y, 2); - assert.equal(inputHandler.curAttrData.fg, 3); - // Change cursor position - bufferService.buffer.x = 10; - bufferService.buffer.y = 20; - inputHandler.curAttrData.fg = 30; - // Restore cursor position - inputHandler.restoreCursor(); - assert.equal(bufferService.buffer.x, 1); - assert.equal(bufferService.buffer.y, 2); - assert.equal(inputHandler.curAttrData.fg, 3); - }); - describe('setCursorStyle', () => { - it('should call Terminal.setOption with correct params', () => { - const optionsService = new MockOptionsService(); - const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService(), {} as any); - - inputHandler.setCursorStyle(Params.fromArray([0])); - assert.equal(optionsService.options['cursorStyle'], 'block'); - assert.equal(optionsService.options['cursorBlink'], true); - - optionsService.options = clone(DEFAULT_OPTIONS); - inputHandler.setCursorStyle(Params.fromArray([1])); - assert.equal(optionsService.options['cursorStyle'], 'block'); - assert.equal(optionsService.options['cursorBlink'], true); - - optionsService.options = clone(DEFAULT_OPTIONS); - inputHandler.setCursorStyle(Params.fromArray([2])); - assert.equal(optionsService.options['cursorStyle'], 'block'); - assert.equal(optionsService.options['cursorBlink'], false); - - optionsService.options = clone(DEFAULT_OPTIONS); - inputHandler.setCursorStyle(Params.fromArray([3])); - assert.equal(optionsService.options['cursorStyle'], 'underline'); - assert.equal(optionsService.options['cursorBlink'], true); - - optionsService.options = clone(DEFAULT_OPTIONS); - inputHandler.setCursorStyle(Params.fromArray([4])); - assert.equal(optionsService.options['cursorStyle'], 'underline'); - assert.equal(optionsService.options['cursorBlink'], false); - - optionsService.options = clone(DEFAULT_OPTIONS); - inputHandler.setCursorStyle(Params.fromArray([5])); - assert.equal(optionsService.options['cursorStyle'], 'bar'); - assert.equal(optionsService.options['cursorBlink'], true); - - optionsService.options = clone(DEFAULT_OPTIONS); - inputHandler.setCursorStyle(Params.fromArray([6])); - assert.equal(optionsService.options['cursorStyle'], 'bar'); - assert.equal(optionsService.options['cursorBlink'], false); - }); - }); - describe('setMode', () => { - it('should toggle Terminal.bracketedPasteMode', () => { - const terminal = new MockInputHandlingTerminal(); - terminal.bracketedPasteMode = false; - const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - // Set bracketed paste mode - inputHandler.setModePrivate(Params.fromArray([2004])); - assert.equal(terminal.bracketedPasteMode, true); - // Reset bracketed paste mode - inputHandler.resetModePrivate(Params.fromArray([2004])); - assert.equal(terminal.bracketedPasteMode, false); - }); - }); - describe('regression tests', function(): void { - function termContent(bufferService: IBufferService, trim: boolean): string[] { - const result = []; - for (let i = 0; i < bufferService.rows; ++i) result.push(bufferService.buffer.lines.get(i).translateToString(trim)); - return result; - } - - it('insertChars', function(): void { - const term = new Terminal(); - const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - - // insert some data in first and second line - inputHandler.parse(Array(bufferService.cols - 9).join('a')); - inputHandler.parse('1234567890'); - inputHandler.parse(Array(bufferService.cols - 9).join('a')); - inputHandler.parse('1234567890'); - const line1: IBufferLine = bufferService.buffer.lines.get(0); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '1234567890'); - - // insert one char from params = [0] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.insertChars(Params.fromArray([0])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' 123456789'); - - // insert one char from params = [1] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.insertChars(Params.fromArray([1])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' 12345678'); - - // insert two chars from params = [2] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.insertChars(Params.fromArray([2])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' 123456'); - - // insert 10 chars from params = [10] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.insertChars(Params.fromArray([10])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' '); - expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a')); - }); - it('deleteChars', function(): void { - const term = new Terminal(); - const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - - // insert some data in first and second line - inputHandler.parse(Array(bufferService.cols - 9).join('a')); - inputHandler.parse('1234567890'); - inputHandler.parse(Array(bufferService.cols - 9).join('a')); - inputHandler.parse('1234567890'); - const line1: IBufferLine = bufferService.buffer.lines.get(0); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '1234567890'); - - // delete one char from params = [0] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.deleteChars(Params.fromArray([0])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '234567890 '); - expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a') + '234567890'); - - // insert one char from params = [1] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.deleteChars(Params.fromArray([1])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '34567890 '); - expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a') + '34567890'); - - // insert two chars from params = [2] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.deleteChars(Params.fromArray([2])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '567890 '); - expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a') + '567890'); - - // insert 10 chars from params = [10] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.deleteChars(Params.fromArray([10])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' '); - expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a')); - }); - it('eraseInLine', function(): void { - const term = new Terminal(); - const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - - // fill 6 lines to test 3 different states - inputHandler.parse(Array(bufferService.cols + 1).join('a')); - inputHandler.parse(Array(bufferService.cols + 1).join('a')); - inputHandler.parse(Array(bufferService.cols + 1).join('a')); - - // params[0] - right erase - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.eraseInLine(Params.fromArray([0])); - expect(bufferService.buffer.lines.get(0).translateToString(false)).equals(Array(71).join('a') + ' '); - - // params[1] - left erase - bufferService.buffer.y = 1; - bufferService.buffer.x = 70; - inputHandler.eraseInLine(Params.fromArray([1])); - expect(bufferService.buffer.lines.get(1).translateToString(false)).equals(Array(71).join(' ') + ' aaaaaaaaa'); - - // params[1] - left erase - bufferService.buffer.y = 2; - bufferService.buffer.x = 70; - inputHandler.eraseInLine(Params.fromArray([2])); - expect(bufferService.buffer.lines.get(2).translateToString(false)).equals(Array(bufferService.cols + 1).join(' ')); - - }); - it('eraseInDisplay', function(): void { - const term = new Terminal({cols: 80, rows: 7}); - const bufferService = new MockBufferService(80, 7); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - - // fill display with a's - for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); - - // params [0] - right and below erase - bufferService.buffer.y = 5; - bufferService.buffer.x = 40; - inputHandler.eraseInDisplay(Params.fromArray([0])); - expect(termContent(bufferService, false)).eql([ - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(40 + 1).join('a') + Array(bufferService.cols - 40 + 1).join(' '), - Array(bufferService.cols + 1).join(' ') - ]); - expect(termContent(bufferService, true)).eql([ - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(40 + 1).join('a'), - '' - ]); - - // reset - bufferService.buffer.y = 0; - bufferService.buffer.x = 0; - for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); - - // params [1] - left and above - bufferService.buffer.y = 5; - bufferService.buffer.x = 40; - inputHandler.eraseInDisplay(Params.fromArray([1])); - expect(termContent(bufferService, false)).eql([ - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(41 + 1).join(' ') + Array(bufferService.cols - 41 + 1).join('a'), - Array(bufferService.cols + 1).join('a') - ]); - expect(termContent(bufferService, true)).eql([ - '', - '', - '', - '', - '', - Array(41 + 1).join(' ') + Array(bufferService.cols - 41 + 1).join('a'), - Array(bufferService.cols + 1).join('a') - ]); - - // reset - bufferService.buffer.y = 0; - bufferService.buffer.x = 0; - for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); - - // params [2] - whole screen - bufferService.buffer.y = 5; - bufferService.buffer.x = 40; - inputHandler.eraseInDisplay(Params.fromArray([2])); - expect(termContent(bufferService, false)).eql([ - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' ') - ]); - expect(termContent(bufferService, true)).eql([ - '', - '', - '', - '', - '', - '', - '' - ]); - - // reset and add a wrapped line - bufferService.buffer.y = 0; - bufferService.buffer.x = 0; - inputHandler.parse(Array(bufferService.cols + 1).join('a')); // line 0 - inputHandler.parse(Array(bufferService.cols + 10).join('a')); // line 1 and 2 - for (let i = 3; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); - - // params[1] left and above with wrap - // confirm precondition that line 2 is wrapped - expect(bufferService.buffer.lines.get(2).isWrapped).true; - bufferService.buffer.y = 2; - bufferService.buffer.x = 40; - inputHandler.eraseInDisplay(Params.fromArray([1])); - expect(bufferService.buffer.lines.get(2).isWrapped).false; - - // reset and add a wrapped line - bufferService.buffer.y = 0; - bufferService.buffer.x = 0; - inputHandler.parse(Array(bufferService.cols + 1).join('a')); // line 0 - inputHandler.parse(Array(bufferService.cols + 10).join('a')); // line 1 and 2 - for (let i = 3; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); - - // params[1] left and above with wrap - // confirm precondition that line 2 is wrapped - expect(bufferService.buffer.lines.get(2).isWrapped).true; - bufferService.buffer.y = 1; - bufferService.buffer.x = 90; // Cursor is beyond last column - inputHandler.eraseInDisplay(Params.fromArray([1])); - expect(bufferService.buffer.lines.get(2).isWrapped).false; - }); - }); - it('convertEol setting', function(): void { - // not converting - const termNotConverting = new Terminal({cols: 15, rows: 10}); - (termNotConverting as any)._inputHandler.parse('Hello\nWorld'); - expect(termNotConverting.buffer.lines.get(0).translateToString(false)).equals('Hello '); - expect(termNotConverting.buffer.lines.get(1).translateToString(false)).equals(' World '); - expect(termNotConverting.buffer.lines.get(0).translateToString(true)).equals('Hello'); - expect(termNotConverting.buffer.lines.get(1).translateToString(true)).equals(' World'); - - // converting - const termConverting = new Terminal({cols: 15, rows: 10, convertEol: true}); - (termConverting as any)._inputHandler.parse('Hello\nWorld'); - expect(termConverting.buffer.lines.get(0).translateToString(false)).equals('Hello '); - expect(termConverting.buffer.lines.get(1).translateToString(false)).equals('World '); - expect(termConverting.buffer.lines.get(0).translateToString(true)).equals('Hello'); - expect(termConverting.buffer.lines.get(1).translateToString(true)).equals('World'); - }); - describe('print', () => { - it('should not cause an infinite loop (regression test)', () => { - const term = new Terminal(); - const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - const container = new Uint32Array(10); - container[0] = 0x200B; - inputHandler.print(container, 0, 1); - }); - }); - - describe('alt screen', () => { - let term: Terminal; - let bufferService: IBufferService; - let handler: InputHandler; - - beforeEach(() => { - term = new Terminal(); - bufferService = new MockBufferService(80, 30); - handler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - }); - it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { - handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); - expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal(''); - expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); - // Text color of 'TEST' should be red - expect((bufferService.buffer.lines.get(1).loadCell(4, new CellData()).getFgColor())).to.equal(1); - }); - it('should handle DECSET/DECRST 1047 (alt screen buffer)', () => { - handler.parse('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST'); - expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal(''); - expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); - // Text color of 'TEST' should be red - expect((bufferService.buffer.lines.get(1).loadCell(4, new CellData()).getFgColor())).to.equal(1); - }); - it('should handle DECSET/DECRST 1048 (alt screen cursor)', () => { - handler.parse('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST'); - expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); - expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal('JUNK'); - // Text color of 'TEST' should be default - expect(bufferService.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); - // Text color of 'JUNK' should be red - expect((bufferService.buffer.lines.get(1).loadCell(0, new CellData()).getFgColor())).to.equal(1); - }); - it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => { - handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST'); - expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); - expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(''); - // Text color of 'TEST' should be default - expect(bufferService.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); - }); - it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', () => { - handler.parse('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST'); - expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); - // Text color of 'TEST' should be default - expect(bufferService.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); - handler.parse('\x1b[?1049h\x1b[uTEST'); - expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal('TEST'); - // Text color of 'TEST' should be red - expect((bufferService.buffer.lines.get(1).loadCell(0, new CellData()).getFgColor())).to.equal(1); - }); - it('should handle DECSET/DECRST 1049 - clears alt buffer with erase attributes', () => { - handler.parse('\x1b[42m\x1b[?1049h'); - // Buffer should be filled with green background - expect(bufferService.buffer.lines.get(20).loadCell(10, new CellData()).getBgColor()).to.equal(2); - }); - }); - - describe('text attributes', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal(); - }); - it('bold', () => { - term.writeSync('\x1b[1m'); - assert.equal(!!term.curAttrData.isBold(), true); - term.writeSync('\x1b[22m'); - assert.equal(!!term.curAttrData.isBold(), false); - }); - it('dim', () => { - term.writeSync('\x1b[2m'); - assert.equal(!!term.curAttrData.isDim(), true); - term.writeSync('\x1b[22m'); - assert.equal(!!term.curAttrData.isDim(), false); - }); - it('italic', () => { - term.writeSync('\x1b[3m'); - assert.equal(!!term.curAttrData.isItalic(), true); - term.writeSync('\x1b[23m'); - assert.equal(!!term.curAttrData.isItalic(), false); - }); - it('underline', () => { - term.writeSync('\x1b[4m'); - assert.equal(!!term.curAttrData.isUnderline(), true); - term.writeSync('\x1b[24m'); - assert.equal(!!term.curAttrData.isUnderline(), false); - }); - it('blink', () => { - term.writeSync('\x1b[5m'); - assert.equal(!!term.curAttrData.isBlink(), true); - term.writeSync('\x1b[25m'); - assert.equal(!!term.curAttrData.isBlink(), false); - }); - it('inverse', () => { - term.writeSync('\x1b[7m'); - assert.equal(!!term.curAttrData.isInverse(), true); - term.writeSync('\x1b[27m'); - assert.equal(!!term.curAttrData.isInverse(), false); - }); - it('invisible', () => { - term.writeSync('\x1b[8m'); - assert.equal(!!term.curAttrData.isInvisible(), true); - term.writeSync('\x1b[28m'); - assert.equal(!!term.curAttrData.isInvisible(), false); - }); - it('colormode palette 16', () => { - assert.equal(term.curAttrData.getFgColorMode(), 0); // DEFAULT - assert.equal(term.curAttrData.getBgColorMode(), 0); // DEFAULT - // lower 8 colors - for (let i = 0; i < 8; ++i) { - term.writeSync(`\x1b[${i + 30};${i + 40}m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getFgColor(), i); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getBgColor(), i); - } - // reset to DEFAULT - term.writeSync(`\x1b[39;49m`); - assert.equal(term.curAttrData.getFgColorMode(), 0); - assert.equal(term.curAttrData.getBgColorMode(), 0); - }); - it('colormode palette 256', () => { - assert.equal(term.curAttrData.getFgColorMode(), 0); // DEFAULT - assert.equal(term.curAttrData.getBgColorMode(), 0); // DEFAULT - // lower 8 colors - for (let i = 0; i < 256; ++i) { - term.writeSync(`\x1b[38;5;${i};48;5;${i}m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getFgColor(), i); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getBgColor(), i); - } - // reset to DEFAULT - term.writeSync(`\x1b[39;49m`); - assert.equal(term.curAttrData.getFgColorMode(), 0); - assert.equal(term.curAttrData.getFgColor(), -1); - assert.equal(term.curAttrData.getBgColorMode(), 0); - assert.equal(term.curAttrData.getBgColor(), -1); - }); - it('colormode RGB', () => { - assert.equal(term.curAttrData.getFgColorMode(), 0); // DEFAULT - assert.equal(term.curAttrData.getBgColorMode(), 0); // DEFAULT - term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_RGB); - assert.equal(term.curAttrData.getFgColor(), 1 << 16 | 2 << 8 | 3); - assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getFgColor()), [1, 2, 3]); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_RGB); - assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getBgColor()), [4, 5, 6]); - // reset to DEFAULT - term.writeSync(`\x1b[39;49m`); - assert.equal(term.curAttrData.getFgColorMode(), 0); - assert.equal(term.curAttrData.getFgColor(), -1); - assert.equal(term.curAttrData.getBgColorMode(), 0); - assert.equal(term.curAttrData.getBgColor(), -1); - }); - it('colormode transition RGB to 256', () => { - // enter RGB for FG and BG - term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`); - // enter 256 for FG and BG - term.writeSync(`\x1b[38;5;255;48;5;255m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getFgColor(), 255); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getBgColor(), 255); - }); - it('colormode transition RGB to 16', () => { - // enter RGB for FG and BG - term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`); - // enter 16 for FG and BG - term.writeSync(`\x1b[37;47m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getFgColor(), 7); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getBgColor(), 7); - }); - it('colormode transition 16 to 256', () => { - // enter 16 for FG and BG - term.writeSync(`\x1b[37;47m`); - // enter 256 for FG and BG - term.writeSync(`\x1b[38;5;255;48;5;255m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getFgColor(), 255); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getBgColor(), 255); - }); - it('colormode transition 256 to 16', () => { - // enter 256 for FG and BG - term.writeSync(`\x1b[38;5;255;48;5;255m`); - // enter 16 for FG and BG - term.writeSync(`\x1b[37;47m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getFgColor(), 7); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getBgColor(), 7); - }); - it('should zero missing RGB values', () => { - term.writeSync(`\x1b[38;2;1;2;3m`); - term.writeSync(`\x1b[38;2;5m`); - assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getFgColor()), [5, 0, 0]); - }); - }); - describe('colon notation', () => { - let termColon: TestTerminal; - let termSemicolon: TestTerminal; - beforeEach(() => { - termColon = new TestTerminal(); - termSemicolon = new TestTerminal(); - }); - describe('should equal to semicolon', () => { - it('CSI 38:2::50:100:150 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;100;150m'); - termColon.writeSync('\x1b[38:2::50:100:150m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38:2::50:100: m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;100;m'); - termColon.writeSync('\x1b[38:2::50:100:m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38:2::50:: m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;;m'); - termColon.writeSync('\x1b[38:2::50::m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 0 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38:2:::: m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;;;m'); - termColon.writeSync('\x1b[38:2::::m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38;2::50:100:150 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;100;150m'); - termColon.writeSync('\x1b[38;2::50:100:150m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38;2;50:100:150 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;100;150m'); - termColon.writeSync('\x1b[38;2;50:100:150m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38;2;50;100:150 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;100;150m'); - termColon.writeSync('\x1b[38;2;50;100:150m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38:5:50 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;5;50m'); - termColon.writeSync('\x1b[38:5:50m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFF, 50); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38:5: m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;5;m'); - termColon.writeSync('\x1b[38:5:m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFF, 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38;5:50 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;5;50m'); - termColon.writeSync('\x1b[38;5:50m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFF, 50); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - }); - describe('should fill early sequence end with default of 0', () => { - it('CSI 38:2 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2m'); - termColon.writeSync('\x1b[38:2m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38:5 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;5m'); - termColon.writeSync('\x1b[38:5m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFF, 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - }); - describe('should not interfere with leading/following SGR attrs', () => { - it('CSI 1 ; 38:2::50:100:150 ; 4 m', () => { - termSemicolon.writeSync('\x1b[1;38;2;50;100;150;4m'); - termColon.writeSync('\x1b[1;38:2::50:100:150;4m'); - assert.equal(!!termSemicolon.curAttrData.isBold(), true); - assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 1 ; 38:2::50:100: ; 4 m', () => { - termSemicolon.writeSync('\x1b[1;38;2;50;100;;4m'); - termColon.writeSync('\x1b[1;38:2::50:100:;4m'); - assert.equal(!!termSemicolon.curAttrData.isBold(), true); - assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 1 ; 38:2::50:100 ; 4 m', () => { - termSemicolon.writeSync('\x1b[1;38;2;50;100;;4m'); - termColon.writeSync('\x1b[1;38:2::50:100;4m'); - assert.equal(!!termSemicolon.curAttrData.isBold(), true); - assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 1 ; 38:2:: ; 4 m', () => { - termSemicolon.writeSync('\x1b[1;38;2;;;;4m'); - termColon.writeSync('\x1b[1;38:2::;4m'); - assert.equal(!!termSemicolon.curAttrData.isBold(), true); - assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 1 ; 38;2:: ; 4 m', () => { - termSemicolon.writeSync('\x1b[1;38;2;;;;4m'); - termColon.writeSync('\x1b[1;38;2::;4m'); - assert.equal(!!termSemicolon.curAttrData.isBold(), true); - assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - }); - }); - describe('cursor positioning', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal({cols: 10, rows: 10}); - }); - it('cursor forward (CUF)', () => { - term.writeSync('\x1b[C'); - assert.deepEqual(getCursor(term), [1, 0]); - term.writeSync('\x1b[1C'); - assert.deepEqual(getCursor(term), [2, 0]); - term.writeSync('\x1b[4C'); - assert.deepEqual(getCursor(term), [6, 0]); - term.writeSync('\x1b[100C'); - assert.deepEqual(getCursor(term), [9, 0]); - // should not change y - term.buffer.x = 8; - term.buffer.y = 4; - term.writeSync('\x1b[C'); - assert.deepEqual(getCursor(term), [9, 4]); - }); - it('cursor backward (CUB)', () => { - term.writeSync('\x1b[D'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1D'); - assert.deepEqual(getCursor(term), [0, 0]); - // place cursor at end of first line - term.writeSync('\x1b[100C'); - term.writeSync('\x1b[D'); - assert.deepEqual(getCursor(term), [8, 0]); - term.writeSync('\x1b[1D'); - assert.deepEqual(getCursor(term), [7, 0]); - term.writeSync('\x1b[4D'); - assert.deepEqual(getCursor(term), [3, 0]); - term.writeSync('\x1b[100D'); - assert.deepEqual(getCursor(term), [0, 0]); - // should not change y - term.buffer.x = 4; - term.buffer.y = 4; - term.writeSync('\x1b[D'); - assert.deepEqual(getCursor(term), [3, 4]); - }); - it('cursor down (CUD)', () => { - term.writeSync('\x1b[B'); - assert.deepEqual(getCursor(term), [0, 1]); - term.writeSync('\x1b[1B'); - assert.deepEqual(getCursor(term), [0, 2]); - term.writeSync('\x1b[4B'); - assert.deepEqual(getCursor(term), [0, 6]); - term.writeSync('\x1b[100B'); - assert.deepEqual(getCursor(term), [0, 9]); - // should not change x - term.buffer.x = 8; - term.buffer.y = 0; - term.writeSync('\x1b[B'); - assert.deepEqual(getCursor(term), [8, 1]); - }); - it('cursor up (CUU)', () => { - term.writeSync('\x1b[A'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1A'); - assert.deepEqual(getCursor(term), [0, 0]); - // place cursor at beginning of last row - term.writeSync('\x1b[100B'); - term.writeSync('\x1b[A'); - assert.deepEqual(getCursor(term), [0, 8]); - term.writeSync('\x1b[1A'); - assert.deepEqual(getCursor(term), [0, 7]); - term.writeSync('\x1b[4A'); - assert.deepEqual(getCursor(term), [0, 3]); - term.writeSync('\x1b[100A'); - assert.deepEqual(getCursor(term), [0, 0]); - // should not change x - term.buffer.x = 8; - term.buffer.y = 9; - term.writeSync('\x1b[A'); - assert.deepEqual(getCursor(term), [8, 8]); - }); - it('cursor next line (CNL)', () => { - term.writeSync('\x1b[E'); - assert.deepEqual(getCursor(term), [0, 1]); - term.writeSync('\x1b[1E'); - assert.deepEqual(getCursor(term), [0, 2]); - term.writeSync('\x1b[4E'); - assert.deepEqual(getCursor(term), [0, 6]); - term.writeSync('\x1b[100E'); - assert.deepEqual(getCursor(term), [0, 9]); - // should reset x to zero - term.buffer.x = 8; - term.buffer.y = 0; - term.writeSync('\x1b[E'); - assert.deepEqual(getCursor(term), [0, 1]); - }); - it('cursor previous line (CPL)', () => { - term.writeSync('\x1b[F'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1F'); - assert.deepEqual(getCursor(term), [0, 0]); - // place cursor at beginning of last row - term.writeSync('\x1b[100E'); - term.writeSync('\x1b[F'); - assert.deepEqual(getCursor(term), [0, 8]); - term.writeSync('\x1b[1F'); - assert.deepEqual(getCursor(term), [0, 7]); - term.writeSync('\x1b[4F'); - assert.deepEqual(getCursor(term), [0, 3]); - term.writeSync('\x1b[100F'); - assert.deepEqual(getCursor(term), [0, 0]); - // should reset x to zero - term.buffer.x = 8; - term.buffer.y = 9; - term.writeSync('\x1b[F'); - assert.deepEqual(getCursor(term), [0, 8]); - }); - it('cursor character absolute (CHA)', () => { - term.writeSync('\x1b[G'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1G'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[2G'); - assert.deepEqual(getCursor(term), [1, 0]); - term.writeSync('\x1b[5G'); - assert.deepEqual(getCursor(term), [4, 0]); - term.writeSync('\x1b[100G'); - assert.deepEqual(getCursor(term), [9, 0]); - }); - it('cursor position (CUP)', () => { - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[H'); - assert.deepEqual(getCursor(term), [0, 0]); - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[1H'); - assert.deepEqual(getCursor(term), [0, 0]); - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[1;1H'); - assert.deepEqual(getCursor(term), [0, 0]); - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[8H'); - assert.deepEqual(getCursor(term), [0, 7]); - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[;8H'); - assert.deepEqual(getCursor(term), [7, 0]); - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[100;100H'); - assert.deepEqual(getCursor(term), [9, 9]); - }); - it('horizontal position absolute (HPA)', () => { - term.writeSync('\x1b[`'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1`'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[2`'); - assert.deepEqual(getCursor(term), [1, 0]); - term.writeSync('\x1b[5`'); - assert.deepEqual(getCursor(term), [4, 0]); - term.writeSync('\x1b[100`'); - assert.deepEqual(getCursor(term), [9, 0]); - }); - it('horizontal position relative (HPR)', () => { - term.writeSync('\x1b[a'); - assert.deepEqual(getCursor(term), [1, 0]); - term.writeSync('\x1b[1a'); - assert.deepEqual(getCursor(term), [2, 0]); - term.writeSync('\x1b[4a'); - assert.deepEqual(getCursor(term), [6, 0]); - term.writeSync('\x1b[100a'); - assert.deepEqual(getCursor(term), [9, 0]); - // should not change y - term.buffer.x = 8; - term.buffer.y = 4; - term.writeSync('\x1b[a'); - assert.deepEqual(getCursor(term), [9, 4]); - }); - it('vertical position absolute (VPA)', () => { - term.writeSync('\x1b[d'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1d'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[2d'); - assert.deepEqual(getCursor(term), [0, 1]); - term.writeSync('\x1b[5d'); - assert.deepEqual(getCursor(term), [0, 4]); - term.writeSync('\x1b[100d'); - assert.deepEqual(getCursor(term), [0, 9]); - // should not change x - term.buffer.x = 8; - term.buffer.y = 4; - term.writeSync('\x1b[d'); - assert.deepEqual(getCursor(term), [8, 0]); - }); - it('vertical position relative (VPR)', () => { - term.writeSync('\x1b[e'); - assert.deepEqual(getCursor(term), [0, 1]); - term.writeSync('\x1b[1e'); - assert.deepEqual(getCursor(term), [0, 2]); - term.writeSync('\x1b[4e'); - assert.deepEqual(getCursor(term), [0, 6]); - term.writeSync('\x1b[100e'); - assert.deepEqual(getCursor(term), [0, 9]); - // should not change x - term.buffer.x = 8; - term.buffer.y = 4; - term.writeSync('\x1b[e'); - assert.deepEqual(getCursor(term), [8, 5]); - }); - describe('should clamp cursor into addressible range', () => { - it('CUF', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[C'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[C'); - assert.deepEqual(getCursor(term), [1, 0]); - }); - it('CUB', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[D'); - assert.deepEqual(getCursor(term), [8, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[D'); - assert.deepEqual(getCursor(term), [0, 0]); - }); - it('CUD', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[B'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[B'); - assert.deepEqual(getCursor(term), [0, 1]); - }); - it('CUU', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[A'); - assert.deepEqual(getCursor(term), [9, 8]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[A'); - assert.deepEqual(getCursor(term), [0, 0]); - }); - it('CNL', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[E'); - assert.deepEqual(getCursor(term), [0, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[E'); - assert.deepEqual(getCursor(term), [0, 1]); - }); - it('CPL', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[F'); - assert.deepEqual(getCursor(term), [0, 8]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[F'); - assert.deepEqual(getCursor(term), [0, 0]); - }); - it('CHA', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[5G'); - assert.deepEqual(getCursor(term), [4, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[5G'); - assert.deepEqual(getCursor(term), [4, 0]); - }); - it('CUP', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[5;5H'); - assert.deepEqual(getCursor(term), [4, 4]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[5;5H'); - assert.deepEqual(getCursor(term), [4, 4]); - }); - it('HPA', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[5`'); - assert.deepEqual(getCursor(term), [4, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[5`'); - assert.deepEqual(getCursor(term), [4, 0]); - }); - it('HPR', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[a'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[a'); - assert.deepEqual(getCursor(term), [1, 0]); - }); - it('VPA', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[5d'); - assert.deepEqual(getCursor(term), [9, 4]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[5d'); - assert.deepEqual(getCursor(term), [0, 4]); - }); - it('VPR', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[e'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[e'); - assert.deepEqual(getCursor(term), [0, 1]); - }); - it('DCH', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[P'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[P'); - assert.deepEqual(getCursor(term), [0, 0]); - }); - it('DCH - should delete last cell', () => { - term.writeSync('0123456789\x1b[P'); - assert.equal(term.buffer.lines.get(0).translateToString(false), '012345678 '); - }); - it('ECH', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[X'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[X'); - assert.deepEqual(getCursor(term), [0, 0]); - }); - it('ECH - should delete last cell', () => { - term.writeSync('0123456789\x1b[X'); - assert.equal(term.buffer.lines.get(0).translateToString(false), '012345678 '); - }); - it('ICH', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[@'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[@'); - assert.deepEqual(getCursor(term), [0, 0]); - }); - it('ICH - should delete last cell', () => { - term.writeSync('0123456789\x1b[@'); - assert.equal(term.buffer.lines.get(0).translateToString(false), '012345678 '); - }); - }); - }); - describe('DECSTBM - scroll margins', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal({cols: 10, rows: 10}); - }); - it('should default to whole viewport', () => { - term.writeSync('\x1b[r'); - assert.equal(term.buffer.scrollTop, 0); - assert.equal(term.buffer.scrollBottom, 9); - term.writeSync('\x1b[3;7r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 6); - term.writeSync('\x1b[0;0r'); - assert.equal(term.buffer.scrollTop, 0); - assert.equal(term.buffer.scrollBottom, 9); - }); - it('should clamp bottom', () => { - term.writeSync('\x1b[3;1000r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 9); - }); - it('should only apply for top < bottom', () => { - term.writeSync('\x1b[7;2r'); - assert.equal(term.buffer.scrollTop, 0); - assert.equal(term.buffer.scrollBottom, 9); - }); - it('should home cursor', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[2;7r'); - assert.deepEqual(getCursor(term), [0, 0]); - }); - }); - describe('scroll margins', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal({cols: 10, rows: 10}); - }); - it('scrollUp', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Sm'); - assert.deepEqual(getLines(term), ['m', '3', '', '', '4', '5', '6', '7', '8', '9']); - }); - it('scrollDown', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Tm'); - assert.deepEqual(getLines(term), ['m', '', '', '1', '4', '5', '6', '7', '8', '9']); - }); - it('insertLines - out of margins', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 5); - term.writeSync('\x1b[2Lm'); - assert.deepEqual(getLines(term), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']); - term.writeSync('\x1b[2H\x1b[2Ln'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']); - // skip below scrollbottom - term.writeSync('\x1b[7H\x1b[2Lo'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']); - term.writeSync('\x1b[8H\x1b[2Lp'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']); - term.writeSync('\x1b[100H\x1b[2Lq'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']); - }); - it('insertLines - within margins', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 5); - term.writeSync('\x1b[3H\x1b[2Lm'); - assert.deepEqual(getLines(term), ['0', '1', 'm', '', '2', '3', '6', '7', '8', '9']); - term.writeSync('\x1b[6H\x1b[2Ln'); - assert.deepEqual(getLines(term), ['0', '1', 'm', '', '2', 'n', '6', '7', '8', '9']); - }); - it('deleteLines - out of margins', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 5); - term.writeSync('\x1b[2Mm'); - assert.deepEqual(getLines(term), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']); - term.writeSync('\x1b[2H\x1b[2Mn'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']); - // skip below scrollbottom - term.writeSync('\x1b[7H\x1b[2Mo'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']); - term.writeSync('\x1b[8H\x1b[2Mp'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']); - term.writeSync('\x1b[100H\x1b[2Mq'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']); - }); - it('deleteLines - within margins', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 5); - term.writeSync('\x1b[6H\x1b[2Mm'); - assert.deepEqual(getLines(term), ['0', '1', '2', '3', '4', 'm', '6', '7', '8', '9']); - term.writeSync('\x1b[3H\x1b[2Mn'); - assert.deepEqual(getLines(term), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']); - }); - }); - describe('SL/SR/DECIC/DECDC', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal({cols: 5, rows: 5, scrollback: 1}); - }); - it('SL (scrollLeft)', () => { - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[ @'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '2345', '2345', '2345', '2345', '2345']); - term.writeSync('\x1b[0 @'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '345', '345', '345', '345', '345']); - term.writeSync('\x1b[2 @'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '5', '5', '5', '5', '5']); - }); - it('SR (scrollRight)', () => { - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[ A'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']); - term.writeSync('\x1b[0 A'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']); - term.writeSync('\x1b[2 A'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']); - }); - it('insertColumns (DECIC)', () => { - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[\'}'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); - term.reset(); - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[1\'}'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); - term.reset(); - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[2\'}'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']); - }); - it('deleteColumns (DECDC)', () => { - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[\'~'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']); - term.reset(); - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[1\'~'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']); - term.reset(); - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[2\'~'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '125', '125', '125', '125', '125']); - }); - }); - it('should parse big chunks in smaller subchunks', () => { - // max single chunk size is hardcoded as 131072 - const calls: any[] = []; - const term = new TestTerminal({cols: 10, rows: 10}); - (term as any)._inputHandler._parser.parse = (data: Uint32Array, length: number) => { - calls.push([data.length, length]); - }; - term.writeSync('12345'); - term.writeSync('a'.repeat(10000)); - term.writeSync('a'.repeat(200000)); - term.writeSync('a'.repeat(300000)); - assert.deepEqual(calls, [ - [4096, 5], - [10000, 10000], - [131072, 131072], [131072, 200000 - 131072], - [131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072] - ]); - }); - describe('windowOptions', () => { - it('all should be disabled by default and not report', () => { - const term = new TestTerminal({cols: 10, rows: 10}); - const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b[14t'); - term.writeSync('\x1b[16t'); - term.writeSync('\x1b[18t'); - term.writeSync('\x1b[20t'); - term.writeSync('\x1b[21t'); - assert.deepEqual(stack, []); - }); - it('14 - GetWinSizePixels', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinSizePixels: true}}); - const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b[14t'); - // does not report in test terminal due to missing renderer - assert.deepEqual(stack, []); - }); - it('16 - GetCellSizePixels', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getCellSizePixels: true}}); - const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b[16t'); - // does not report in test terminal due to missing renderer - assert.deepEqual(stack, []); - }); - it('18 - GetWinSizeChars', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinSizeChars: true}}); - const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b[18t'); - assert.deepEqual(stack, ['\x1b[8;10;10t']); - term.resize(50, 20); - term.writeSync('\x1b[18t'); - assert.deepEqual(stack, ['\x1b[8;10;10t', '\x1b[8;20;50t']); - }); - it('22/23 - PushTitle/PopTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); - const stack: string[] = []; - term.onTitleChange(data => stack.push(data)); - term.writeSync('\x1b]0;1\x07'); - term.writeSync('\x1b[22t'); - term.writeSync('\x1b]0;2\x07'); - term.writeSync('\x1b[22t'); - term.writeSync('\x1b]0;3\x07'); - term.writeSync('\x1b[22t'); - assert.deepEqual((term as any)._inputHandler._windowTitleStack, ['1', '2', '3']); - assert.deepEqual((term as any)._inputHandler._iconNameStack, ['1', '2', '3']); - assert.deepEqual(stack, ['1', '2', '3']); - term.writeSync('\x1b[23t'); - term.writeSync('\x1b[23t'); - term.writeSync('\x1b[23t'); - term.writeSync('\x1b[23t'); // one more to test "overflow" - assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); - assert.deepEqual((term as any)._inputHandler._iconNameStack, []); - assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); - }); - it('22/23 - PushTitle/PopTitle with ;1', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); - const stack: string[] = []; - term.onTitleChange(data => stack.push(data)); - term.writeSync('\x1b]0;1\x07'); - term.writeSync('\x1b[22;1t'); - term.writeSync('\x1b]0;2\x07'); - term.writeSync('\x1b[22;1t'); - term.writeSync('\x1b]0;3\x07'); - term.writeSync('\x1b[22;1t'); - assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); - assert.deepEqual((term as any)._inputHandler._iconNameStack, ['1', '2', '3']); - assert.deepEqual(stack, ['1', '2', '3']); - term.writeSync('\x1b[23;1t'); - term.writeSync('\x1b[23;1t'); - term.writeSync('\x1b[23;1t'); - term.writeSync('\x1b[23;1t'); // one more to test "overflow" - assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); - assert.deepEqual((term as any)._inputHandler._iconNameStack, []); - assert.deepEqual(stack, ['1', '2', '3']); - }); - it('22/23 - PushTitle/PopTitle with ;2', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); - const stack: string[] = []; - term.onTitleChange(data => stack.push(data)); - term.writeSync('\x1b]0;1\x07'); - term.writeSync('\x1b[22;2t'); - term.writeSync('\x1b]0;2\x07'); - term.writeSync('\x1b[22;2t'); - term.writeSync('\x1b]0;3\x07'); - term.writeSync('\x1b[22;2t'); - assert.deepEqual((term as any)._inputHandler._windowTitleStack, ['1', '2', '3']); - assert.deepEqual((term as any)._inputHandler._iconNameStack, []); - assert.deepEqual(stack, ['1', '2', '3']); - term.writeSync('\x1b[23;2t'); - term.writeSync('\x1b[23;2t'); - term.writeSync('\x1b[23;2t'); - term.writeSync('\x1b[23;2t'); // one more to test "overflow" - assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); - assert.deepEqual((term as any)._inputHandler._iconNameStack, []); - assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); - }); - it('DECCOLM - should only work with "SetWinLines" (24) enabled', () => { - // disabled - const term = new TestTerminal({cols: 10, rows: 10}); - term.writeSync('\x1b[?3l'); - assert.equal((term as any)._bufferService.cols, 10); - term.writeSync('\x1b[?3h'); - assert.equal((term as any)._bufferService.cols, 10); - // enabled - const term2 = new TestTerminal({cols: 10, rows: 10, windowOptions: {setWinLines: true}}); - term2.writeSync('\x1b[?3l'); - assert.equal((term2 as any)._bufferService.cols, 80); - term2.writeSync('\x1b[?3h'); - assert.equal((term2 as any)._bufferService.cols, 132); - }); - }); - describe('should correctly reset cells taken by wide chars', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal({cols: 10, rows: 5, scrollback: 1}); - term.writeSync('¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥'); - }); - it('print', () => { - term.writeSync('\x1b[H#'); - assert.deepEqual(getLines(term), ['# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[1;6H######'); - assert.deepEqual(getLines(term), ['# ¥ #####', '# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '##¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[3;9H#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥#', '¥¥¥¥¥', '']); - term.writeSync('#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '¥¥¥¥¥', '']); - term.writeSync('#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥¥', '']); - term.writeSync('\x1b[4;10H#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥ #', '']); - }); - it('EL', () => { - term.writeSync('\x1b[1;6H\x1b[K#'); - assert.deepEqual(getLines(term), ['¥¥ #', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[2;5H\x1b[1K'); - assert.deepEqual(getLines(term), ['¥¥ #', ' ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[3;6H\x1b[1K'); - assert.deepEqual(getLines(term), ['¥¥ #', ' ¥¥', ' ¥¥', '¥¥¥¥¥', '']); - }); - it('ICH', () => { - term.writeSync('\x1b[1;6H\x1b[@'); - assert.deepEqual(getLines(term), ['¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[2;4H\x1b[2@'); - assert.deepEqual(getLines(term), ['¥¥ ¥', '¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[3;4H\x1b[3@'); - assert.deepEqual(getLines(term), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[4;4H\x1b[4@'); - assert.deepEqual(getLines(term), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥ ¥', '']); - }); - it('DCH', () => { - term.writeSync('\x1b[1;6H\x1b[P'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[2;6H\x1b[2P'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[3;6H\x1b[3P'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); - }); - it('ECH', () => { - term.writeSync('\x1b[1;6H\x1b[X'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[2;6H\x1b[2X'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[3;6H\x1b[3X'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); - }); - }); - describe('DECSTR', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal({cols: 10, rows: 5, scrollback: 1}); - term.writeSync('01234567890123'); - }); - it('should reset IRM', () => { - term.writeSync('\x1b[4h'); - assert.equal(term.insertMode, true); - term.writeSync('\x1b[!p'); - assert.equal(term.insertMode, false); - }); - it('should reset cursor visibility', () => { - term.writeSync('\x1b[?25l'); - assert.equal((term as any)._coreService.isCursorHidden, true); - term.writeSync('\x1b[!p'); - assert.equal((term as any)._coreService.isCursorHidden, false); - }); - it('should reset scroll margins', () => { - term.writeSync('\x1b[2;4r'); - assert.equal((term as any)._bufferService.buffer.scrollTop, 1); - assert.equal((term as any)._bufferService.buffer.scrollBottom, 3); - term.writeSync('\x1b[!p'); - assert.equal((term as any)._bufferService.buffer.scrollTop, 0); - assert.equal((term as any)._bufferService.buffer.scrollBottom, term.rows - 1); - }); - it('should reset text attributes', () => { - term.writeSync('\x1b[1;2;32;43m'); - assert.equal(!!term.curAttrData.isBold(), true); - term.writeSync('\x1b[!p'); - assert.equal(!!term.curAttrData.isBold(), false); - assert.equal(term.curAttrData.fg, 0); - assert.equal(term.curAttrData.bg, 0); - }); - it('should reset DECSC data', () => { - term.writeSync('\x1b7'); - assert.equal((term as any)._bufferService.buffer.savedX, 4); - assert.equal((term as any)._bufferService.buffer.savedY, 1); - term.writeSync('\x1b[!p'); - assert.equal((term as any)._bufferService.buffer.savedX, 0); - assert.equal((term as any)._bufferService.buffer.savedY, 0); - }); - it('should reset DECOM', () => { - term.writeSync('\x1b[?6h'); - assert.equal((term as any)._coreService.decPrivateModes.origin, true); - term.writeSync('\x1b[!p'); - assert.equal((term as any)._coreService.decPrivateModes.origin, false); - }); - }); -}); diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 4d590699..6488aa43 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -121,7 +121,7 @@ describe('Terminal', () => { assert.equal(e, 'title'); done(); }); - term.handleTitle('title'); + term.write('\x1b]2;title\x07'); }); }); @@ -140,7 +140,6 @@ describe('Terminal', () => { }; beforeEach(() => { - term.showCursor = () => { }; term.clearSelection = () => { }; }); @@ -531,7 +530,6 @@ describe('Terminal', () => { let evKeyPress: any; beforeEach(() => { - term.showCursor = () => { }; term.clearSelection = () => { }; // term.compositionHelper = { // isComposing: false, @@ -991,7 +989,7 @@ describe('Terminal', () => { term.writeSync(Array(9).join('0123456789').slice(-80)); term.buffer.x = 10; term.buffer.y = 0; - term.insertMode = true; + term.write('\x1b[4h'); term.writeSync('abcde'); expect(term.buffer.lines.get(0).length).eql(term.cols); expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a'); @@ -1003,7 +1001,7 @@ describe('Terminal', () => { term.writeSync(Array(9).join('0123456789').slice(-80)); term.buffer.x = 10; term.buffer.y = 0; - term.insertMode = true; + term.write('\x1b[4h'); term.writeSync('¥¥¥'); expect(term.buffer.lines.get(0).length).eql(term.cols); expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('¥'); @@ -1016,7 +1014,7 @@ describe('Terminal', () => { term.writeSync(Array(41).join('¥')); term.buffer.x = 10; term.buffer.y = 0; - term.insertMode = true; + term.write('\x1b[4h'); term.writeSync('a'); expect(term.buffer.lines.get(0).length).eql(term.cols); expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a'); @@ -1403,6 +1401,90 @@ describe('Terminal', () => { assert.equal(windowsModeTerminal.buffer.lines.get(2).isWrapped, false); }); }); + it('convertEol setting', function(): void { + // not converting + const termNotConverting = new TestTerminal({cols: 15, rows: 10}); + termNotConverting.writeSync('Hello\nWorld'); + expect(termNotConverting.buffer.lines.get(0).translateToString(false)).equals('Hello '); + expect(termNotConverting.buffer.lines.get(1).translateToString(false)).equals(' World '); + expect(termNotConverting.buffer.lines.get(0).translateToString(true)).equals('Hello'); + expect(termNotConverting.buffer.lines.get(1).translateToString(true)).equals(' World'); + + // converting + const termConverting = new TestTerminal({cols: 15, rows: 10, convertEol: true}); + termConverting.writeSync('Hello\nWorld'); + expect(termConverting.buffer.lines.get(0).translateToString(false)).equals('Hello '); + expect(termConverting.buffer.lines.get(1).translateToString(false)).equals('World '); + expect(termConverting.buffer.lines.get(0).translateToString(true)).equals('Hello'); + expect(termConverting.buffer.lines.get(1).translateToString(true)).equals('World'); + }); + describe('Terminal InputHandler integration', () => { + function getLines(term: TestTerminal, limit: number = term.rows): string[] { + const res: string[] = []; + for (let i = 0; i < limit; ++i) { + res.push(term.buffer.lines.get(i).translateToString(true)); + } + return res; + } + + // This suite cannot live in InputHandler unless Terminal.scroll moved into IBufferService + describe('SL/SR/DECIC/DECDC', () => { + let term: TestTerminal; + beforeEach(() => { + term = new TestTerminal({cols: 5, rows: 5, scrollback: 1}); + }); + it('SL (scrollLeft)', () => { + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[ @'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '2345', '2345', '2345', '2345', '2345']); + term.writeSync('\x1b[0 @'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '345', '345', '345', '345', '345']); + term.writeSync('\x1b[2 @'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '5', '5', '5', '5', '5']); + }); + it('SR (scrollRight)', () => { + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[ A'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']); + term.writeSync('\x1b[0 A'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']); + term.writeSync('\x1b[2 A'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']); + }); + it('insertColumns (DECIC)', () => { + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[3;3H'); + term.writeSync('\x1b[\'}'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); + term.reset(); + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[3;3H'); + term.writeSync('\x1b[1\'}'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); + term.reset(); + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[3;3H'); + term.writeSync('\x1b[2\'}'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']); + }); + it('deleteColumns (DECDC)', () => { + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[3;3H'); + term.writeSync('\x1b[\'~'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']); + term.reset(); + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[3;3H'); + term.writeSync('\x1b[1\'~'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']); + term.reset(); + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[3;3H'); + term.writeSync('\x1b[2\'~'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '125', '125', '125', '125', '125']); + }); + }); + }); }); class TestLinkifier extends Linkifier { diff --git a/src/Terminal.ts b/src/Terminal.ts index e3a3801d..61c997bd 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -21,13 +21,13 @@ * http://linux.die.net/man/7/urxvt */ -import { IInputHandlingTerminal, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, CustomKeyEventHandler } from './Types'; +import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler } from './Types'; import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard'; import { C0 } from 'common/data/EscapeSequences'; -import { InputHandler } from './InputHandler'; +import { InputHandler, WindowsOptionsReportType } from './common/InputHandler'; import { Renderer } from 'browser/renderer/Renderer'; import { Linkifier } from 'browser/Linkifier'; import { SelectionService } from 'browser/services/SelectionService'; @@ -39,39 +39,28 @@ import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; -import { IKeyboardEvent, KeyboardResultType, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; -import { updateWindowsModeWrappedState } from 'common/WindowsMode'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; -import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService, ICharsetService, IUnicodeService } from 'common/services/Services'; -import { OptionsService } from 'common/services/OptionsService'; import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; -import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; -import { Disposable } from 'common/Lifecycle'; -import { IBufferSet, IBuffer } from 'common/buffer/Types'; +import { IBuffer } from 'common/buffer/Types'; import { MouseService } from 'browser/services/MouseService'; import { IParams, IFunctionIdentifier } from 'common/parser/Types'; -import { CoreService } from 'common/services/CoreService'; -import { LogService } from 'common/services/LogService'; import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; -import { DirtyRowService } from 'common/services/DirtyRowService'; -import { InstantiationService } from 'common/services/InstantiationService'; -import { CoreMouseService } from 'common/services/CoreMouseService'; import { WriteBuffer } from 'common/input/WriteBuffer'; import { Linkifier2 } from 'browser/Linkifier2'; import { CoreBrowserService } from 'browser/services/CoreBrowserService'; -import { UnicodeService } from 'common/services/UnicodeService'; -import { CharsetService } from 'common/services/CharsetService'; +import { CoreTerminal } from 'common/CoreTerminal'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; -export class Terminal extends Disposable implements ITerminal, IDisposable, IInputHandlingTerminal { +export class Terminal extends CoreTerminal implements ITerminal { public textarea: HTMLTextAreaElement; public element: HTMLElement; public screenElement: HTMLElement; @@ -91,17 +80,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _customKeyEventHandler: CustomKeyEventHandler; - // common services - private _bufferService: IBufferService; - private _coreService: ICoreService; - private _charsetService: ICharsetService; - private _coreMouseService: ICoreMouseService; - private _dirtyRowService: IDirtyRowService; - private _instantiationService: IInstantiationService; - private _logService: ILogService; - public optionsService: IOptionsService; - public unicodeService: IUnicodeService; - // browser services private _charSizeService: ICharSizeService; private _mouseService: IMouseService; @@ -109,14 +87,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _selectionService: ISelectionService; private _soundService: ISoundService; - // modes - public insertMode: boolean; - public bracketedPasteMode: boolean; - - // mouse properties - public mouseEvents: CoreMouseEventType = CoreMouseEventType.NONE; - public sendFocus: boolean; - // write buffer private _writeBuffer: WriteBuffer; @@ -139,28 +109,16 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _accessibilityManager: AccessibilityManager; private _colorManager: ColorManager; private _theme: ITheme; - private _windowsMode: IDisposable | undefined; // bufferline to clone/copy from for new blank lines private _blankLine: IBufferLine = null; - public get cols(): number { return this._bufferService.cols; } - public get rows(): number { return this._bufferService.rows; } - private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } - private _onData = new EventEmitter(); - public get onData(): IEvent { return this._onData.event; } - private _onBinary = new EventEmitter(); - public get onBinary(): IEvent { return this._onBinary.event; } private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; } - private _onLineFeed = new EventEmitter(); - public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onRender = new EventEmitter<{ start: number, end: number }>(); public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } - private _onResize = new EventEmitter<{ cols: number, rows: number }>(); - public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } private _onScroll = new EventEmitter(); public get onScroll(): IEvent { return this._onScroll.event; } private _onSelectionChange = new EventEmitter(); @@ -172,10 +130,10 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp public get onFocus(): IEvent { return this._onFocus.event; } private _onBlur = new EventEmitter(); public get onBlur(): IEvent { return this._onBlur.event; } - public onA11yCharEmitter = new EventEmitter(); - public get onA11yChar(): IEvent { return this.onA11yCharEmitter.event; } - public onA11yTabEmitter = new EventEmitter(); - public get onA11yTab(): IEvent { return this.onA11yTabEmitter.event; } + private _onA11yCharEmitter = new EventEmitter(); + public get onA11yChar(): IEvent { return this._onA11yCharEmitter.event; } + private _onA11yTabEmitter = new EventEmitter(); + public get onA11yTab(): IEvent { return this._onA11yTabEmitter.event; } /** * Creates a new `Terminal` object. @@ -192,32 +150,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp constructor( options: ITerminalOptions = {} ) { - super(); + super(options); - // Setup and initialize common services - this._instantiationService = new InstantiationService(); - this.optionsService = new OptionsService(options); - this._instantiationService.setService(IOptionsService, this.optionsService); - this._bufferService = this._instantiationService.createInstance(BufferService); - this._instantiationService.setService(IBufferService, this._bufferService); - this._logService = this._instantiationService.createInstance(LogService); - this._instantiationService.setService(ILogService, this._logService); - this._coreService = this._instantiationService.createInstance(CoreService, () => this.scrollToBottom()); - this._instantiationService.setService(ICoreService, this._coreService); - this._coreService.onData(e => this._onData.fire(e)); - this._coreService.onBinary(e => this._onBinary.fire(e)); - 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); - this._instantiationService.setService(IUnicodeService, this.unicodeService); - this._charsetService = this._instantiationService.createInstance(CharsetService); - this._instantiationService.setService(ICharsetService, this._charsetService); - - this._setupOptionsListeners(); this._setup(); + // Setup listeners + this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)); + this._writeBuffer = new WriteBuffer(data => this._inputHandler.parse(data)); } @@ -226,62 +165,42 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp return; } super.dispose(); - this._windowsMode?.dispose(); - this._windowsMode = undefined; this._renderService?.dispose(); this._customKeyEventHandler = null; this.write = () => { }; this.element?.parentNode?.removeChild(this.element); } - private _setup(): void { - this._customKeyEventHandler = null; - - // modes - this.insertMode = false; - this.bracketedPasteMode = false; - - this._userScrolling = false; - + protected _setup(): void { if (this._inputHandler) { this._inputHandler.reset(); } else { // Register input handler and refire/handle events - this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService, this._instantiationService); - this._inputHandler.onRequestBell(() => this.bell()); - this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)); - this._inputHandler.onRequestReset(() => this.reset()); - this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); - this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); + this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService); + this.register(this._inputHandler.onRequestBell(() => this.bell())); + this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); + this.register(this._inputHandler.onRequestReset(() => this.reset())); + this.register(this._inputHandler.onRequestScroll((eraseAttr, isWrapped) => this.scroll(eraseAttr, isWrapped || undefined))); + this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); + this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); + this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed)); + this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange)); + this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter)); + this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter)); this.register(this._inputHandler); } + super._setup(); + + this._customKeyEventHandler = null; + + this._userScrolling = false; if (!this.linkifier) { this.linkifier = this._instantiationService.createInstance(Linkifier); } if (!this.linkifier2) { this.linkifier2 = this._instantiationService.createInstance(Linkifier2); } - - if (this.options.windowsMode) { - this._enableWindowsMode(); - } - } - - private _enableWindowsMode(): void { - if (!this._windowsMode) { - const disposables: IDisposable[] = []; - disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService))); - disposables.push(this.addCsiHandler({ final: 'H' }, () => { - updateWindowsModeWrappedState(this._bufferService); - return false; - })); - this._windowsMode = { - dispose: () => { - disposables.forEach(d => d.dispose()); - } - }; - } } /** @@ -291,10 +210,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp return this.buffers.active; } - public get buffers(): IBufferSet { - return this._bufferService.buffers; - } - /** * Focus the terminal. Delegates focus handling to the terminal's DOM element. */ @@ -304,80 +219,71 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } } - private _setupOptionsListeners(): void { + protected _updateOptions(key: string): void { + super._updateOptions(key); + // TODO: These listeners should be owned by individual components - this.optionsService.onOptionChange(key => { - switch (key) { - case 'fontFamily': - case 'fontSize': - // When the font changes the size of the cells may change which requires a renderer clear - this._renderService?.clear(); - this._charSizeService?.measure(); - break; - case 'cursorBlink': - case 'cursorStyle': - // The DOM renderer needs a row refresh to update the cursor styles - this.refresh(this.buffer.y, this.buffer.y); - break; - case 'drawBoldTextInBrightColors': - case 'letterSpacing': - case 'lineHeight': - case 'fontWeight': - case 'fontWeightBold': - case 'minimumContrastRatio': - // When the font changes the size of the cells may change which requires a renderer clear - if (this._renderService) { - this._renderService.clear(); - this._renderService.onResize(this.cols, this.rows); - this.refresh(0, this.rows - 1); + switch (key) { + case 'fontFamily': + case 'fontSize': + // When the font changes the size of the cells may change which requires a renderer clear + this._renderService?.clear(); + this._charSizeService?.measure(); + break; + case 'cursorBlink': + case 'cursorStyle': + // The DOM renderer needs a row refresh to update the cursor styles + this.refresh(this.buffer.y, this.buffer.y); + break; + case 'drawBoldTextInBrightColors': + case 'letterSpacing': + case 'lineHeight': + case 'fontWeight': + case 'fontWeightBold': + case 'minimumContrastRatio': + // When the font changes the size of the cells may change which requires a renderer clear + if (this._renderService) { + this._renderService.clear(); + this._renderService.onResize(this.cols, this.rows); + this.refresh(0, this.rows - 1); + } + break; + case 'rendererType': + if (this._renderService) { + this._renderService.setRenderer(this._createRenderer()); + this._renderService.onResize(this.cols, this.rows); + } + break; + case 'scrollback': + this.viewport?.syncScrollArea(); + break; + case 'screenReaderMode': + if (this.optionsService.options.screenReaderMode) { + if (!this._accessibilityManager && this._renderService) { + this._accessibilityManager = new AccessibilityManager(this, this._renderService); } - break; - case 'rendererType': - if (this._renderService) { - this._renderService.setRenderer(this._createRenderer()); - this._renderService.onResize(this.cols, this.rows); - } - break; - case 'scrollback': - this.buffers.resize(this.cols, this.rows); - this.viewport?.syncScrollArea(); - break; - case 'screenReaderMode': - if (this.optionsService.options.screenReaderMode) { - if (!this._accessibilityManager && this._renderService) { - this._accessibilityManager = new AccessibilityManager(this, this._renderService); - } - } else { - this._accessibilityManager?.dispose(); - this._accessibilityManager = null; - } - break; - case 'tabStopWidth': this.buffers.setupTabStops(); break; - case 'theme': - this._setTheme(this.optionsService.options.theme); - break; - case 'windowsMode': - if (this.optionsService.options.windowsMode) { - this._enableWindowsMode(); - } else { - this._windowsMode?.dispose(); - this._windowsMode = undefined; - } - break; - } - }); + } else { + this._accessibilityManager?.dispose(); + this._accessibilityManager = null; + } + break; + case 'tabStopWidth': this.buffers.setupTabStops(); break; + case 'theme': + this._setTheme(this.optionsService.options.theme); + break; + } } /** * Binds the desired focus behavior on a given terminal object. */ private _onTextAreaFocus(ev: KeyboardEvent): void { - if (this.sendFocus) { + if (this._coreService.decPrivateModes.sendFocus) { this._coreService.triggerDataEvent(C0.ESC + '[I'); } this.updateCursorStyle(ev); this.element.classList.add('focus'); - this.showCursor(); + this._showCursor(); this._onFocus.fire(); } @@ -397,7 +303,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // screen readers reading it out. this.textarea.value = ''; this.refresh(this.buffer.y, this.buffer.y); - if (this.sendFocus) { + if (this._coreService.decPrivateModes.sendFocus) { this._coreService.triggerDataEvent(C0.ESC + '[O'); } this.element.classList.remove('focus'); @@ -419,7 +325,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } copyHandler(event, this._selectionService); })); - const pasteHandlerWrapper = (event: ClipboardEvent): void => handlePasteEvent(event, this.textarea, this.bracketedPasteMode, 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)); @@ -557,6 +463,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._viewportScrollArea ); this.viewport.onThemeChange(this._colorManager.colors); + this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport.syncScrollArea())); this.register(this.viewport); this.register(this.onCursorMove(() => this._renderService.onCursorMove())); @@ -596,7 +503,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp 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.mouseEvents) { + if (this._coreMouseService.areMouseEventsActive) { this._selectionService.disable(); this.element.classList.add('enable-mouse-events'); } else { @@ -771,7 +678,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp }; this._coreMouseService.onProtocolChange(events => { // apply global changes on events - this.mouseEvents = events; if (events) { if (this.optionsService.options.logLevel === 'debug') { this._logService.debug('Binding to mouse events:', this._coreMouseService.explainEvents(events)); @@ -829,7 +735,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // 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.mouseEvents || this._selectionService.shouldForceSelection(ev)) { + if (!this._coreMouseService.areMouseEventsActive || this._selectionService.shouldForceSelection(ev)) { return; } @@ -883,13 +789,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp })); this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => { - if (this.mouseEvents) return; + if (this._coreMouseService.areMouseEventsActive) return; this.viewport.onTouchStart(ev); return this.cancel(ev); })); this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => { - if (this.mouseEvents) return; + if (this._coreMouseService.areMouseEventsActive) return; if (!this.viewport.onTouchMove(ev)) { return this.cancel(ev); } @@ -930,7 +836,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp /** * Display the cursor element */ - public showCursor(): void { + private _showCursor(): void { if (!this._coreService.isCursorInitialized) { this._coreService.isCursorInitialized = true; this.refresh(this.buffer.y, this.buffer.y); @@ -1064,7 +970,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } public paste(data: string): void { - paste(data, this.textarea, this.bracketedPasteMode, this._coreService); + paste(data, this.textarea, this._coreService); } /** @@ -1265,7 +1171,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } this._onKey.fire({ key: result.key, domEvent: event }); - this.showCursor(); + this._showCursor(); this._coreService.triggerDataEvent(result.key, true); // Cancel events when not in screen reader mode so events don't get bubbled up and handled by @@ -1342,7 +1248,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp key = String.fromCharCode(key); this._onKey.fire({ key, domEvent: ev }); - this.showCursor(); + this._showCursor(); this._coreService.triggerDataEvent(key, true); return true; @@ -1373,10 +1279,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp * @param y The number of rows to resize to. */ public resize(x: number, y: number): void { - if (isNaN(x) || isNaN(y)) { - return; - } - if (x === this.cols && y === this.rows) { // Check if we still need to measure the char size (fixes #785). if (this._charSizeService && !this._charSizeService.hasValidSize) { @@ -1385,22 +1287,15 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp return; } - if (x < MINIMUM_COLS) x = MINIMUM_COLS; - if (y < MINIMUM_ROWS) y = MINIMUM_ROWS; - - this.buffers.resize(x, y); - - this._bufferService.resize(x, y); - this.buffers.setupTabStops(this.cols); + super.resize(x, y); + } + private _afterResize(x: number, y: number): void { this._charSizeService?.measure(); // Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an // invalid location this.viewport?.syncScrollArea(true); - - this.refresh(0, this.rows - 1); - this._onResize.fire({ cols: x, rows: y }); } /** @@ -1423,44 +1318,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._onScroll.fire(this.buffer.ydisp); } - /** - * Evaluate if the current terminal is the given argument. - * @param term The terminal name to evaluate - */ - public is(term: string): boolean { - return (this.options.termName + '').indexOf(term) === 0; - } - - /** - * Emit the data event and populate the given data. - * @param data The data to populate in the event. - */ - // public handler(data: string): void { - // // Prevents all events to pty process if stdin is disabled - // if (this.options.disableStdin) { - // return; - // } - - // // Clear the selection if the selection manager is available and has an active selection - // if (this.selectionService && this.selectionService.hasSelection) { - // this.selectionService.clearSelection(); - // } - - // // Input is being sent to the terminal, the terminal should focus the prompt. - // if (this.buffer.ybase !== this.buffer.ydisp) { - // this.scrollToBottom(); - // } - // this._onData.fire(data); - // } - - /** - * Emit the 'title' event and populate the given title. - * @param title The title to populate in the event. - */ - public handleTitle(title: string): void { - this._onTitleChange.fire(title); - } - /** * Reset terminal. * Note: Calling this directly from JS is synchronous but does not clear @@ -1495,6 +1352,25 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.viewport?.syncScrollArea(); } + private _reportWindowsOptions(type: WindowsOptionsReportType): void { + if (!this._renderService) { + return; + } + + switch (type) { + 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`); + 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`); + break; + } + } + // TODO: Remove cancel function and cancelEvents option public cancel(ev: Event, force?: boolean): boolean { if (!this.options.cancelEvents && !force) { diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index cac0a43d..32b2633f 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -4,9 +4,9 @@ */ import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRedrawEvent } from 'browser/renderer/Types'; -import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions } from './Types'; +import { ICompositionHelper, ITerminal, IBrowser } from './Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; -import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset } from 'common/Types'; +import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition, ILinkProvider } from 'xterm'; @@ -14,7 +14,6 @@ import { Terminal } from './Terminal'; import { AttributeData } from 'common/buffer/AttributeData'; import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier, IViewport, ILinkifier2 } from 'browser/Types'; import { IOptionsService, IUnicodeService } from 'common/services/Services'; -import { EventEmitter } from 'common/EventEmitter'; import { IParams, IFunctionIdentifier } from 'common/parser/Types'; import { ISelectionService } from 'browser/services/Services'; @@ -190,9 +189,6 @@ export class MockTerminal implements ITerminal { public reset(): void { throw new Error('Method not implemented.'); } - public showCursor(): void { - throw new Error('Method not implemented.'); - } public refresh(start: number, end: number): void { throw new Error('Method not implemented.'); } @@ -200,35 +196,6 @@ export class MockTerminal implements ITerminal { public deregisterCharacterJoiner(joinerId: number): void { } } -export class MockInputHandlingTerminal implements IInputHandlingTerminal { - public onA11yCharEmitter: EventEmitter; - public onA11yTabEmitter: EventEmitter; - public insertMode: boolean; - public bracketedPasteMode: boolean; - public sendFocus: boolean; - public buffers: IBufferSet; - public buffer: IBuffer = new MockBuffer(); - public viewport: IViewport; - public scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void { - throw new Error('Method not implemented.'); - } - public is(term: string): boolean { - throw new Error('Method not implemented.'); - } - public resize(x: number, y: number): void { - throw new Error('Method not implemented.'); - } - public showCursor(): void { - throw new Error('Method not implemented.'); - } - public handler(data: string): void { - throw new Error('Method not implemented.'); - } - public handleTitle(title: string): void { - throw new Error('Method not implemented.'); - } -} - export class MockBuffer implements IBuffer { public markers: IMarker[]; public addMarker(y: number): IMarker { diff --git a/src/Types.d.ts b/src/Types.d.ts index cb0ba844..05c89e15 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -3,10 +3,10 @@ * @license MIT */ -import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm'; -import { ICharset, IAttributeData, CharData, CoreMouseEventType } from 'common/Types'; -import { IEvent, IEventEmitter } from 'common/EventEmitter'; -import { IColorSet, ILinkifier, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; +import { IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm'; +import { IAttributeData, CharData, ITerminalOptions } from 'common/Types'; +import { IEvent } from 'common/EventEmitter'; +import { ILinkifier, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; import { IOptionsService, IUnicodeService } from 'common/services/Services'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IParams, IFunctionIdentifier } from 'common/parser/Types'; @@ -15,30 +15,6 @@ export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; export type LineData = CharData[]; -/** - * This interface encapsulates everything needed from the Terminal by the - * InputHandler. This cleanly separates the large amount of methods needed by - * InputHandler cleanly from the ITerminal interface. - */ -export interface IInputHandlingTerminal { - insertMode: boolean; - bracketedPasteMode: boolean; - sendFocus: boolean; - - buffers: IBufferSet; - buffer: IBuffer; - viewport: IViewport; - - onA11yCharEmitter: IEventEmitter; - onA11yTabEmitter: IEventEmitter; - - scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void; - is(term: string): boolean; - resize(x: number, y: number): void; - showCursor(): void; - handleTitle(title: string): void; -} - export interface ICompositionHelper { compositionstart(): void; compositionupdate(ev: CompositionEvent): void; @@ -47,95 +23,12 @@ export interface ICompositionHelper { keydown(ev: KeyboardEvent): boolean; } -/** - * Calls the parser and handles actions generated by the parser. - */ -export interface IInputHandler { - parse(data: string | Uint8Array): void; - print(data: Uint32Array, start: number, end: number): void; - - /** C0 BEL */ bell(): void; - /** C0 LF */ lineFeed(): void; - /** C0 CR */ carriageReturn(): void; - /** C0 BS */ backspace(): void; - /** C0 HT */ tab(): void; - /** C0 SO */ shiftOut(): void; - /** C0 SI */ shiftIn(): void; - - /** CSI @ */ insertChars(params: IParams): void; - /** CSI SP @ */ scrollLeft(params: IParams): void; - /** CSI A */ cursorUp(params: IParams): void; - /** CSI SP A */ scrollRight(params: IParams): void; - /** CSI B */ cursorDown(params: IParams): void; - /** CSI C */ cursorForward(params: IParams): void; - /** CSI D */ cursorBackward(params: IParams): void; - /** CSI E */ cursorNextLine(params: IParams): void; - /** CSI F */ cursorPrecedingLine(params: IParams): void; - /** CSI G */ cursorCharAbsolute(params: IParams): void; - /** CSI H */ cursorPosition(params: IParams): void; - /** CSI I */ cursorForwardTab(params: IParams): void; - /** CSI J */ eraseInDisplay(params: IParams): void; - /** CSI K */ eraseInLine(params: IParams): void; - /** CSI L */ insertLines(params: IParams): void; - /** CSI M */ deleteLines(params: IParams): void; - /** CSI P */ deleteChars(params: IParams): void; - /** CSI S */ scrollUp(params: IParams): void; - /** CSI T */ scrollDown(params: IParams, collect?: string): void; - /** CSI X */ eraseChars(params: IParams): void; - /** CSI Z */ cursorBackwardTab(params: IParams): void; - /** CSI ` */ charPosAbsolute(params: IParams): void; - /** CSI a */ hPositionRelative(params: IParams): void; - /** CSI b */ repeatPrecedingCharacter(params: IParams): void; - /** CSI c */ sendDeviceAttributesPrimary(params: IParams): void; - /** CSI > c */ sendDeviceAttributesSecondary(params: IParams): void; - /** CSI d */ linePosAbsolute(params: IParams): void; - /** CSI e */ vPositionRelative(params: IParams): void; - /** CSI f */ hVPosition(params: IParams): void; - /** CSI g */ tabClear(params: IParams): void; - /** CSI h */ setMode(params: IParams, collect?: string): void; - /** CSI l */ resetMode(params: IParams, collect?: string): void; - /** CSI m */ charAttributes(params: IParams): void; - /** CSI n */ deviceStatus(params: IParams, collect?: string): void; - /** CSI p */ softReset(params: IParams, collect?: string): void; - /** CSI q */ setCursorStyle(params: IParams, collect?: string): void; - /** CSI r */ setScrollRegion(params: IParams, collect?: string): void; - /** CSI s */ saveCursor(params: IParams): void; - /** CSI u */ restoreCursor(params: IParams): void; - /** CSI ' } */ insertColumns(params: IParams): void; - /** CSI ' ~ */ deleteColumns(params: IParams): void; - /** OSC 0 - OSC 2 */ setTitle(data: string): void; - /** ESC E */ nextLine(): void; - /** ESC = */ keypadApplicationMode(): void; - /** ESC > */ keypadNumericMode(): void; - /** ESC % G - ESC % @ */ selectDefaultCharset(): void; - /** ESC ( C - ESC ) C - ESC * C - ESC + C - ESC - C - ESC . C - ESC / C */ selectCharset(collectAndFlag: string): void; - /** ESC D */ index(): void; - /** ESC H */ tabSet(): void; - /** ESC M */ reverseIndex(): void; - /** ESC c */ fullReset(): void; - /** ESC n - ESC o - ESC | - ESC } - ESC ~ */ setgLevel(level: number): void; - /** ESC # 8 */ screenAlignmentPattern(): void; -} - export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor { screenElement: HTMLElement; browser: IBrowser; buffer: IBuffer; buffers: IBufferSet; viewport: IViewport; - bracketedPasteMode: boolean; optionsService: IOptionsService; // TODO: We should remove options once components adopt optionsService options: ITerminalOptions; @@ -148,7 +41,6 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc scrollLines(disp: number, suppressScrollEvent?: boolean): void; cancel(ev: Event, force?: boolean): boolean | void; - showCursor(): void; } // Portions of the public API that are required by the internal Terminal @@ -216,14 +108,6 @@ export interface ILinkifierAccessor { linkifier2: ILinkifier2; } -// TODO: The options that are not in the public API should be reviewed -export interface ITerminalOptions extends IPublicTerminalOptions { - [key: string]: any; - cancelEvents?: boolean; - convertEol?: boolean; - termName?: string; -} - export interface IBrowser { isNode: boolean; userAgent: string; diff --git a/src/browser/Clipboard.ts b/src/browser/Clipboard.ts index a7c48bfb..5fe1e8fc 100644 --- a/src/browser/Clipboard.ts +++ b/src/browser/Clipboard.ts @@ -42,17 +42,17 @@ export function copyHandler(ev: ClipboardEvent, selectionService: ISelectionServ * @param ev The original paste event to be handled * @param term The terminal on which to apply the handled paste event */ -export function handlePasteEvent(ev: ClipboardEvent, textarea: HTMLTextAreaElement, bracketedPasteMode: boolean, coreService: ICoreService): void { +export function handlePasteEvent(ev: ClipboardEvent, textarea: HTMLTextAreaElement, coreService: ICoreService): void { ev.stopPropagation(); if (ev.clipboardData) { const text = ev.clipboardData.getData('text/plain'); - paste(text, textarea, bracketedPasteMode, coreService); + paste(text, textarea, coreService); } } -export function paste(text: string, textarea: HTMLTextAreaElement, bracketedPasteMode: boolean, coreService: ICoreService): void { +export function paste(text: string, textarea: HTMLTextAreaElement, coreService: ICoreService): void { text = prepareTextForTerminal(text); - text = bracketTextForPaste(text, bracketedPasteMode); + text = bracketTextForPaste(text, coreService.decPrivateModes.bracketedPasteMode); coreService.triggerDataEvent(text, true); textarea.value = ''; } diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 96d1eae0..1869ab87 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -9,7 +9,7 @@ import { IRenderDimensions, IRenderer, CharacterJoinerHandler } from 'browser/re import { IColorSet } from 'browser/Types'; export class MockCharSizeService implements ICharSizeService { - public serviceBrand: any; + public serviceBrand: undefined; public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } public onCharSizeChange: IEvent = new EventEmitter().event; constructor(public width: number, public height: number) {} @@ -17,7 +17,7 @@ export class MockCharSizeService implements ICharSizeService { } export class MockMouseService implements IMouseService { - public serviceBrand: any; + public serviceBrand: undefined; public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined { throw new Error('Not implemented'); } @@ -28,7 +28,7 @@ export class MockMouseService implements IMouseService { } export class MockRenderService implements IRenderService { - public serviceBrand: any; + public serviceBrand: undefined; public onDimensionsChange: IEvent = new EventEmitter().event; public onRenderedBufferChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; public onRefreshRequest: IEvent<{ start: number, end: number}, void> = new EventEmitter<{ start: number, end: number }>().event; diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index 7623109c..79696a28 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -8,7 +8,7 @@ import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharSizeService } from 'browser/services/Services'; export class CharSizeService implements ICharSizeService { - public serviceBrand: any; + public serviceBrand: undefined; public width: number = 0; public height: number = 0; diff --git a/src/browser/services/CoreBrowserService.ts b/src/browser/services/CoreBrowserService.ts index e3207d7b..985253d9 100644 --- a/src/browser/services/CoreBrowserService.ts +++ b/src/browser/services/CoreBrowserService.ts @@ -6,7 +6,7 @@ import { ICoreBrowserService } from './Services'; export class CoreBrowserService implements ICoreBrowserService { - public serviceBrand: any; + public serviceBrand: undefined; constructor( private _textarea: HTMLTextAreaElement diff --git a/src/browser/services/MouseService.ts b/src/browser/services/MouseService.ts index 39df6383..348ba64e 100644 --- a/src/browser/services/MouseService.ts +++ b/src/browser/services/MouseService.ts @@ -7,7 +7,7 @@ import { ICharSizeService, IRenderService, IMouseService } from './Services'; import { getCoords, getRawByteCoords } from 'browser/input/Mouse'; export class MouseService implements IMouseService { - public serviceBrand: any; + public serviceBrand: undefined; constructor( @IRenderService private readonly _renderService: IRenderService, diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 7c8a13cc..a0ca25d2 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -10,7 +10,7 @@ import { Disposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet } from 'browser/Types'; -import { IOptionsService } from 'common/services/Services'; +import { IOptionsService, IBufferService, ICoreService } from 'common/services/Services'; import { ICharSizeService, IRenderService } from 'browser/services/Services'; interface ISelectionState { @@ -20,7 +20,7 @@ interface ISelectionState { } export class RenderService extends Disposable implements IRenderService { - public serviceBrand: any; + public serviceBrand: undefined; private _renderDebouncer: RenderDebouncer; private _screenDprMonitor: ScreenDprMonitor; @@ -51,7 +51,8 @@ export class RenderService extends Disposable implements IRenderService { private _rowCount: number, screenElement: HTMLElement, @IOptionsService optionsService: IOptionsService, - @ICharSizeService charSizeService: ICharSizeService + @ICharSizeService charSizeService: ICharSizeService, + @IBufferService private readonly _bufferService: IBufferService ) { super(); this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end)); @@ -61,6 +62,7 @@ export class RenderService extends Disposable implements IRenderService { this._screenDprMonitor.setListener(() => this.onDevicePixelRatioChange()); this.register(this._screenDprMonitor); + this.register(this._bufferService.onResize(e => this._fullRefresh())); this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged())); this.register(charSizeService.onCharSizeChange(() => this.onCharSizeChanged())); diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 43875f1b..d70cfa11 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -67,7 +67,7 @@ export const enum SelectionMode { * when the selection is ready to be redrawn (on an animation frame). */ export class SelectionService implements ISelectionService { - public serviceBrand: any; + public serviceBrand: undefined; protected _model: SelectionModel; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 7a559dab..445cab05 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -12,7 +12,7 @@ import { IDisposable } from 'common/Types'; export const ICharSizeService = createDecorator('CharSizeService'); export interface ICharSizeService { - serviceBrand: any; + serviceBrand: undefined; readonly width: number; readonly height: number; @@ -25,14 +25,14 @@ export interface ICharSizeService { export const ICoreBrowserService = createDecorator('CoreBrowserService'); export interface ICoreBrowserService { - serviceBrand: any; + serviceBrand: undefined; readonly isFocused: boolean; } export const IMouseService = createDecorator('MouseService'); export interface IMouseService { - serviceBrand: any; + serviceBrand: undefined; getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined; getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined; @@ -40,7 +40,7 @@ export interface IMouseService { export const IRenderService = createDecorator('RenderService'); export interface IRenderService extends IDisposable { - serviceBrand: any; + serviceBrand: undefined; onDimensionsChange: IEvent; /** @@ -72,7 +72,7 @@ export interface IRenderService extends IDisposable { export const ISelectionService = createDecorator('SelectionService'); export interface ISelectionService { - serviceBrand: any; + serviceBrand: undefined; readonly selectionText: string; readonly hasSelection: boolean; @@ -100,7 +100,7 @@ export interface ISelectionService { export const ISoundService = createDecorator('SoundService'); export interface ISoundService { - serviceBrand: any; + serviceBrand: undefined; playBellSound(): void; } diff --git a/src/browser/services/SoundService.ts b/src/browser/services/SoundService.ts index 4b54c999..8d940c13 100644 --- a/src/browser/services/SoundService.ts +++ b/src/browser/services/SoundService.ts @@ -7,7 +7,7 @@ import { IOptionsService } from 'common/services/Services'; import { ISoundService } from 'browser/services/Services'; export class SoundService implements ISoundService { - public serviceBrand: any; + public serviceBrand: undefined; private static _audioContext: AudioContext; diff --git a/src/browser/tsconfig.json b/src/browser/tsconfig.json index 7465bbd3..74b4c057 100644 --- a/src/browser/tsconfig.json +++ b/src/browser/tsconfig.json @@ -14,7 +14,10 @@ "common/*": [ "./common/*" ] } }, - "include": [ "./**/*" ], + "include": [ + "./**/*", + "../../typings/xterm.d.ts" + ], "references": [ { "path": "../common" } ] diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts new file mode 100644 index 00000000..49f3509d --- /dev/null +++ b/src/common/CoreTerminal.ts @@ -0,0 +1,160 @@ +/** + * Copyright (c) 2014-2020 The xterm.js authors. All rights reserved. + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * @license MIT + * + * Originally forked from (with the author's permission): + * Fabrice Bellard's javascript vt100 for jslinux: + * http://bellard.org/jslinux/ + * Copyright (c) 2011 Fabrice Bellard + * The original design remains. The terminal itself + * has been extended to include xterm CSI codes, among + * other features. + * + * Terminal Emulation References: + * http://vt100.net/ + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * http://invisible-island.net/vttest/ + * http://www.inwap.com/pdp10/ansicode.txt + * http://linux.die.net/man/4/console_codes + * http://linux.die.net/man/7/urxvt + */ + +import { Disposable } from 'common/Lifecycle'; +import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService } from 'common/services/Services'; +import { InstantiationService } from 'common/services/InstantiationService'; +import { LogService } from 'common/services/LogService'; +import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; +import { OptionsService } from 'common/services/OptionsService'; +import { ITerminalOptions, IDisposable } from './Types'; +import { CoreService } from 'common/services/CoreService'; +import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; +import { CoreMouseService } from 'common/services/CoreMouseService'; +import { DirtyRowService } from 'common/services/DirtyRowService'; +import { UnicodeService } from 'common/services/UnicodeService'; +import { CharsetService } from 'common/services/CharsetService'; +import { updateWindowsModeWrappedState } from 'common/WindowsMode'; +import { IFunctionIdentifier, IParams } from 'common/parser/Types'; +import { IBufferSet } from 'common/buffer/Types'; + +export abstract class CoreTerminal extends Disposable { + 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 unicodeService: IUnicodeService; + public readonly optionsService: IOptionsService; + + private _windowsMode: IDisposable | undefined; + + private _onBinary = new EventEmitter(); + public get onBinary(): IEvent { return this._onBinary.event; } + private _onData = new EventEmitter(); + public get onData(): IEvent { return this._onData.event; } + protected _onLineFeed = new EventEmitter(); + public get onLineFeed(): IEvent { return this._onLineFeed.event; } + private _onResize = new EventEmitter<{ cols: number, rows: number }>(); + public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } + + public get cols(): number { return this._bufferService.cols; } + public get rows(): number { return this._bufferService.rows; } + public get buffers(): IBufferSet { return this._bufferService.buffers; } + + constructor( + options: ITerminalOptions + ) { + super(); + + // Setup and initialize services + this._instantiationService = new InstantiationService(); + this.optionsService = new OptionsService(options); + this._instantiationService.setService(IOptionsService, this.optionsService); + this._bufferService = this._instantiationService.createInstance(BufferService); + this._instantiationService.setService(IBufferService, this._bufferService); + this._logService = this._instantiationService.createInstance(LogService); + this._instantiationService.setService(ILogService, this._logService); + this._coreService = 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); + this._instantiationService.setService(IUnicodeService, this.unicodeService); + this._charsetService = this._instantiationService.createInstance(CharsetService); + this._instantiationService.setService(ICharsetService, this._charsetService); + + // 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(this.optionsService.onOptionChange(key => this._updateOptions(key))); + } + + public dispose(): void { + if (this._isDisposed) { + return; + } + super.dispose(); + this._windowsMode?.dispose(); + this._windowsMode = undefined; + } + + public resize(x: number, y: number): void { + if (isNaN(x) || isNaN(y)) { + return; + } + + x = Math.max(x, MINIMUM_COLS); + y = Math.max(y, MINIMUM_ROWS); + + this._bufferService.resize(x, y); + } + + protected _setup(): void { + if (this.optionsService.options.windowsMode) { + this._enableWindowsMode(); + } + } + + protected _updateOptions(key: string): void { + // TODO: These listeners should be owned by individual components + switch (key) { + case 'scrollback': + this.buffers.resize(this.cols, this.rows); + break; + case 'windowsMode': + if (this.optionsService.options.windowsMode) { + this._enableWindowsMode(); + } else { + this._windowsMode?.dispose(); + this._windowsMode = undefined; + } + break; + } + } + + protected _enableWindowsMode(): void { + if (!this._windowsMode) { + const disposables: IDisposable[] = []; + disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService))); + disposables.push(this.addCsiHandler({ final: 'H' }, () => { + updateWindowsModeWrappedState(this._bufferService); + return false; + })); + this._windowsMode = { + dispose: () => { + disposables.forEach(d => d.dispose()); + } + }; + } + } + + public abstract scrollToBottom(): void; + public abstract addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable; +} diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index 301574e4..4684809f 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -63,3 +63,7 @@ export class EventEmitter implements IEventEmitter { this._disposed = true; } } + +export function forwardEvent(from: IEvent, to: IEventEmitter): IDisposable { + return from(e => to.fire(e)); +} diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts new file mode 100644 index 00000000..8f3ff9af --- /dev/null +++ b/src/common/InputHandler.test.ts @@ -0,0 +1,1427 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert, expect } from 'chai'; +import { InputHandler } from 'common/InputHandler'; +import { IBufferLine, IAttributeData } from 'common/Types'; +import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; +import { CellData } from 'common/buffer/CellData'; +import { Attributes } from 'common/buffer/Constants'; +import { AttributeData } from 'common/buffer/AttributeData'; +import { Params } from 'common/parser/Params'; +import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService, MockCharsetService, MockUnicodeService } from 'common/TestUtils.test'; +import { IBufferService, ICoreService } from 'common/services/Services'; +import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; +import { clone } from 'common/Clone'; +import { BufferService } from 'common/services/BufferService'; +import { CoreService } from 'common/services/CoreService'; + +function getCursor(bufferService: IBufferService): number[] { + return [ + bufferService.buffer.x, + bufferService.buffer.y + ]; +} + +function getLines(bufferService: IBufferService, limit: number = bufferService.rows): string[] { + const res: string[] = []; + for (let i = 0; i < limit; ++i) { + res.push(bufferService.buffer.lines.get(i)!.translateToString(true)); + } + return res; +} + +class TestInputHandler extends InputHandler { + public get curAttrData(): IAttributeData { return (this as any)._curAttrData; } + public get windowTitleStack(): string[] { return this._windowTitleStack; } + public get iconNameStack(): string[] { return this._iconNameStack; } +} + +describe('InputHandler', () => { + let bufferService: IBufferService; + let coreService: ICoreService; + let optionsService: MockOptionsService; + let inputHandler: TestInputHandler; + + beforeEach(() => { + optionsService = new MockOptionsService(); + bufferService = new BufferService(optionsService); + bufferService.resize(80, 30); + coreService = new CoreService(() => {}, bufferService, new MockLogService(), optionsService); + + inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService()); + }); + + it('save and restore cursor', () => { + bufferService.buffer.x = 1; + bufferService.buffer.y = 2; + bufferService.buffer.ybase = 0; + inputHandler.curAttrData.fg = 3; + // Save cursor position + inputHandler.saveCursor(); + assert.equal(bufferService.buffer.x, 1); + assert.equal(bufferService.buffer.y, 2); + assert.equal(inputHandler.curAttrData.fg, 3); + // Change cursor position + bufferService.buffer.x = 10; + bufferService.buffer.y = 20; + inputHandler.curAttrData.fg = 30; + // Restore cursor position + inputHandler.restoreCursor(); + assert.equal(bufferService.buffer.x, 1); + assert.equal(bufferService.buffer.y, 2); + assert.equal(inputHandler.curAttrData.fg, 3); + }); + describe('setCursorStyle', () => { + it('should call Terminal.setOption with correct params', () => { + inputHandler.setCursorStyle(Params.fromArray([0])); + assert.equal(optionsService.options['cursorStyle'], 'block'); + assert.equal(optionsService.options['cursorBlink'], true); + + optionsService.options = clone(DEFAULT_OPTIONS); + inputHandler.setCursorStyle(Params.fromArray([1])); + assert.equal(optionsService.options['cursorStyle'], 'block'); + assert.equal(optionsService.options['cursorBlink'], true); + + optionsService.options = clone(DEFAULT_OPTIONS); + inputHandler.setCursorStyle(Params.fromArray([2])); + assert.equal(optionsService.options['cursorStyle'], 'block'); + assert.equal(optionsService.options['cursorBlink'], false); + + optionsService.options = clone(DEFAULT_OPTIONS); + inputHandler.setCursorStyle(Params.fromArray([3])); + assert.equal(optionsService.options['cursorStyle'], 'underline'); + assert.equal(optionsService.options['cursorBlink'], true); + + optionsService.options = clone(DEFAULT_OPTIONS); + inputHandler.setCursorStyle(Params.fromArray([4])); + assert.equal(optionsService.options['cursorStyle'], 'underline'); + assert.equal(optionsService.options['cursorBlink'], false); + + optionsService.options = clone(DEFAULT_OPTIONS); + inputHandler.setCursorStyle(Params.fromArray([5])); + assert.equal(optionsService.options['cursorStyle'], 'bar'); + assert.equal(optionsService.options['cursorBlink'], true); + + optionsService.options = clone(DEFAULT_OPTIONS); + inputHandler.setCursorStyle(Params.fromArray([6])); + assert.equal(optionsService.options['cursorStyle'], 'bar'); + assert.equal(optionsService.options['cursorBlink'], false); + }); + }); + describe('setMode', () => { + it('should toggle bracketedPasteMode', () => { + const coreService = new MockCoreService(); + const inputHandler = new InputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + // Set bracketed paste mode + inputHandler.setModePrivate(Params.fromArray([2004])); + assert.equal(coreService.decPrivateModes.bracketedPasteMode, true); + // Reset bracketed paste mode + inputHandler.resetModePrivate(Params.fromArray([2004])); + assert.equal(coreService.decPrivateModes.bracketedPasteMode, false); + }); + }); + describe('regression tests', function(): void { + function termContent(bufferService: IBufferService, trim: boolean): string[] { + const result = []; + for (let i = 0; i < bufferService.rows; ++i) result.push(bufferService.buffer.lines.get(i)!.translateToString(trim)); + return result; + } + + it('insertChars', function(): void { + const bufferService = new MockBufferService(80, 30); + const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + + // insert some data in first and second line + inputHandler.parse(Array(bufferService.cols - 9).join('a')); + inputHandler.parse('1234567890'); + inputHandler.parse(Array(bufferService.cols - 9).join('a')); + inputHandler.parse('1234567890'); + const line1: IBufferLine = bufferService.buffer.lines.get(0)!; + expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '1234567890'); + + // insert one char from params = [0] + bufferService.buffer.y = 0; + bufferService.buffer.x = 70; + inputHandler.insertChars(Params.fromArray([0])); + expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' 123456789'); + + // insert one char from params = [1] + bufferService.buffer.y = 0; + bufferService.buffer.x = 70; + inputHandler.insertChars(Params.fromArray([1])); + expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' 12345678'); + + // insert two chars from params = [2] + bufferService.buffer.y = 0; + bufferService.buffer.x = 70; + inputHandler.insertChars(Params.fromArray([2])); + expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' 123456'); + + // insert 10 chars from params = [10] + bufferService.buffer.y = 0; + bufferService.buffer.x = 70; + inputHandler.insertChars(Params.fromArray([10])); + expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' '); + expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a')); + }); + it('deleteChars', function(): void { + const bufferService = new MockBufferService(80, 30); + const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + + // insert some data in first and second line + inputHandler.parse(Array(bufferService.cols - 9).join('a')); + inputHandler.parse('1234567890'); + inputHandler.parse(Array(bufferService.cols - 9).join('a')); + inputHandler.parse('1234567890'); + const line1: IBufferLine = bufferService.buffer.lines.get(0)!; + expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '1234567890'); + + // delete one char from params = [0] + bufferService.buffer.y = 0; + bufferService.buffer.x = 70; + inputHandler.deleteChars(Params.fromArray([0])); + expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '234567890 '); + expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a') + '234567890'); + + // insert one char from params = [1] + bufferService.buffer.y = 0; + bufferService.buffer.x = 70; + inputHandler.deleteChars(Params.fromArray([1])); + expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '34567890 '); + expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a') + '34567890'); + + // insert two chars from params = [2] + bufferService.buffer.y = 0; + bufferService.buffer.x = 70; + inputHandler.deleteChars(Params.fromArray([2])); + expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '567890 '); + expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a') + '567890'); + + // insert 10 chars from params = [10] + bufferService.buffer.y = 0; + bufferService.buffer.x = 70; + inputHandler.deleteChars(Params.fromArray([10])); + expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' '); + expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a')); + }); + it('eraseInLine', function(): void { + const bufferService = new MockBufferService(80, 30); + const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + + // fill 6 lines to test 3 different states + inputHandler.parse(Array(bufferService.cols + 1).join('a')); + inputHandler.parse(Array(bufferService.cols + 1).join('a')); + inputHandler.parse(Array(bufferService.cols + 1).join('a')); + + // params[0] - right erase + bufferService.buffer.y = 0; + bufferService.buffer.x = 70; + inputHandler.eraseInLine(Params.fromArray([0])); + expect(bufferService.buffer.lines.get(0)!.translateToString(false)).equals(Array(71).join('a') + ' '); + + // params[1] - left erase + bufferService.buffer.y = 1; + bufferService.buffer.x = 70; + inputHandler.eraseInLine(Params.fromArray([1])); + expect(bufferService.buffer.lines.get(1)!.translateToString(false)).equals(Array(71).join(' ') + ' aaaaaaaaa'); + + // params[1] - left erase + bufferService.buffer.y = 2; + bufferService.buffer.x = 70; + inputHandler.eraseInLine(Params.fromArray([2])); + expect(bufferService.buffer.lines.get(2)!.translateToString(false)).equals(Array(bufferService.cols + 1).join(' ')); + + }); + it('eraseInDisplay', function(): void { + const bufferService = new MockBufferService(80, 7); + const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + + // fill display with a's + for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); + + // params [0] - right and below erase + bufferService.buffer.y = 5; + bufferService.buffer.x = 40; + inputHandler.eraseInDisplay(Params.fromArray([0])); + expect(termContent(bufferService, false)).eql([ + Array(bufferService.cols + 1).join('a'), + Array(bufferService.cols + 1).join('a'), + Array(bufferService.cols + 1).join('a'), + Array(bufferService.cols + 1).join('a'), + Array(bufferService.cols + 1).join('a'), + Array(40 + 1).join('a') + Array(bufferService.cols - 40 + 1).join(' '), + Array(bufferService.cols + 1).join(' ') + ]); + expect(termContent(bufferService, true)).eql([ + Array(bufferService.cols + 1).join('a'), + Array(bufferService.cols + 1).join('a'), + Array(bufferService.cols + 1).join('a'), + Array(bufferService.cols + 1).join('a'), + Array(bufferService.cols + 1).join('a'), + Array(40 + 1).join('a'), + '' + ]); + + // reset + bufferService.buffer.y = 0; + bufferService.buffer.x = 0; + for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); + + // params [1] - left and above + bufferService.buffer.y = 5; + bufferService.buffer.x = 40; + inputHandler.eraseInDisplay(Params.fromArray([1])); + expect(termContent(bufferService, false)).eql([ + Array(bufferService.cols + 1).join(' '), + Array(bufferService.cols + 1).join(' '), + Array(bufferService.cols + 1).join(' '), + Array(bufferService.cols + 1).join(' '), + Array(bufferService.cols + 1).join(' '), + Array(41 + 1).join(' ') + Array(bufferService.cols - 41 + 1).join('a'), + Array(bufferService.cols + 1).join('a') + ]); + expect(termContent(bufferService, true)).eql([ + '', + '', + '', + '', + '', + Array(41 + 1).join(' ') + Array(bufferService.cols - 41 + 1).join('a'), + Array(bufferService.cols + 1).join('a') + ]); + + // reset + bufferService.buffer.y = 0; + bufferService.buffer.x = 0; + for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); + + // params [2] - whole screen + bufferService.buffer.y = 5; + bufferService.buffer.x = 40; + inputHandler.eraseInDisplay(Params.fromArray([2])); + expect(termContent(bufferService, false)).eql([ + Array(bufferService.cols + 1).join(' '), + Array(bufferService.cols + 1).join(' '), + Array(bufferService.cols + 1).join(' '), + Array(bufferService.cols + 1).join(' '), + Array(bufferService.cols + 1).join(' '), + Array(bufferService.cols + 1).join(' '), + Array(bufferService.cols + 1).join(' ') + ]); + expect(termContent(bufferService, true)).eql([ + '', + '', + '', + '', + '', + '', + '' + ]); + + // reset and add a wrapped line + bufferService.buffer.y = 0; + bufferService.buffer.x = 0; + inputHandler.parse(Array(bufferService.cols + 1).join('a')); // line 0 + inputHandler.parse(Array(bufferService.cols + 10).join('a')); // line 1 and 2 + for (let i = 3; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); + + // params[1] left and above with wrap + // confirm precondition that line 2 is wrapped + expect(bufferService.buffer.lines.get(2)!.isWrapped).true; + bufferService.buffer.y = 2; + bufferService.buffer.x = 40; + inputHandler.eraseInDisplay(Params.fromArray([1])); + expect(bufferService.buffer.lines.get(2)!.isWrapped).false; + + // reset and add a wrapped line + bufferService.buffer.y = 0; + bufferService.buffer.x = 0; + inputHandler.parse(Array(bufferService.cols + 1).join('a')); // line 0 + inputHandler.parse(Array(bufferService.cols + 10).join('a')); // line 1 and 2 + for (let i = 3; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); + + // params[1] left and above with wrap + // confirm precondition that line 2 is wrapped + expect(bufferService.buffer.lines.get(2)!.isWrapped).true; + bufferService.buffer.y = 1; + bufferService.buffer.x = 90; // Cursor is beyond last column + inputHandler.eraseInDisplay(Params.fromArray([1])); + expect(bufferService.buffer.lines.get(2)!.isWrapped).false; + }); + }); + describe('print', () => { + it('should not cause an infinite loop (regression test)', () => { + const inputHandler = new InputHandler(new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + const container = new Uint32Array(10); + container[0] = 0x200B; + inputHandler.print(container, 0, 1); + }); + }); + + describe('alt screen', () => { + let bufferService: IBufferService; + let handler: InputHandler; + + beforeEach(() => { + bufferService = new MockBufferService(80, 30); + handler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); + }); + it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { + handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); + expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal(''); + expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); + // Text color of 'TEST' should be red + expect((bufferService.buffer.lines.get(1)!.loadCell(4, new CellData()).getFgColor())).to.equal(1); + }); + it('should handle DECSET/DECRST 1047 (alt screen buffer)', () => { + handler.parse('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST'); + expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal(''); + expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); + // Text color of 'TEST' should be red + expect((bufferService.buffer.lines.get(1)!.loadCell(4, new CellData()).getFgColor())).to.equal(1); + }); + it('should handle DECSET/DECRST 1048 (alt screen cursor)', () => { + handler.parse('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST'); + expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); + expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal('JUNK'); + // Text color of 'TEST' should be default + expect(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); + // Text color of 'JUNK' should be red + expect((bufferService.buffer.lines.get(1)!.loadCell(0, new CellData()).getFgColor())).to.equal(1); + }); + it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => { + handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST'); + expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); + expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(''); + // Text color of 'TEST' should be default + expect(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); + }); + it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', () => { + handler.parse('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST'); + expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); + // Text color of 'TEST' should be default + expect(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); + handler.parse('\x1b[?1049h\x1b[uTEST'); + expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal('TEST'); + // Text color of 'TEST' should be red + expect((bufferService.buffer.lines.get(1)!.loadCell(0, new CellData()).getFgColor())).to.equal(1); + }); + it('should handle DECSET/DECRST 1049 - clears alt buffer with erase attributes', () => { + handler.parse('\x1b[42m\x1b[?1049h'); + // Buffer should be filled with green background + expect(bufferService.buffer.lines.get(20)!.loadCell(10, new CellData()).getBgColor()).to.equal(2); + }); + }); + + describe('text attributes', () => { + it('bold', () => { + inputHandler.parse('\x1b[1m'); + assert.equal(!!inputHandler.curAttrData.isBold(), true); + inputHandler.parse('\x1b[22m'); + assert.equal(!!inputHandler.curAttrData.isBold(), false); + }); + it('dim', () => { + inputHandler.parse('\x1b[2m'); + assert.equal(!!inputHandler.curAttrData.isDim(), true); + inputHandler.parse('\x1b[22m'); + assert.equal(!!inputHandler.curAttrData.isDim(), false); + }); + it('italic', () => { + inputHandler.parse('\x1b[3m'); + assert.equal(!!inputHandler.curAttrData.isItalic(), true); + inputHandler.parse('\x1b[23m'); + assert.equal(!!inputHandler.curAttrData.isItalic(), false); + }); + it('underline', () => { + inputHandler.parse('\x1b[4m'); + assert.equal(!!inputHandler.curAttrData.isUnderline(), true); + inputHandler.parse('\x1b[24m'); + assert.equal(!!inputHandler.curAttrData.isUnderline(), false); + }); + it('blink', () => { + inputHandler.parse('\x1b[5m'); + assert.equal(!!inputHandler.curAttrData.isBlink(), true); + inputHandler.parse('\x1b[25m'); + assert.equal(!!inputHandler.curAttrData.isBlink(), false); + }); + it('inverse', () => { + inputHandler.parse('\x1b[7m'); + assert.equal(!!inputHandler.curAttrData.isInverse(), true); + inputHandler.parse('\x1b[27m'); + assert.equal(!!inputHandler.curAttrData.isInverse(), false); + }); + it('invisible', () => { + inputHandler.parse('\x1b[8m'); + assert.equal(!!inputHandler.curAttrData.isInvisible(), true); + inputHandler.parse('\x1b[28m'); + assert.equal(!!inputHandler.curAttrData.isInvisible(), false); + }); + it('colormode palette 16', () => { + assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT + assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT + // lower 8 colors + for (let i = 0; i < 8; ++i) { + inputHandler.parse(`\x1b[${i + 30};${i + 40}m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P16); + assert.equal(inputHandler.curAttrData.getFgColor(), i); + assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P16); + assert.equal(inputHandler.curAttrData.getBgColor(), i); + } + // reset to DEFAULT + inputHandler.parse(`\x1b[39;49m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); + assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); + }); + it('colormode palette 256', () => { + assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT + assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT + // lower 8 colors + for (let i = 0; i < 256; ++i) { + inputHandler.parse(`\x1b[38;5;${i};48;5;${i}m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P256); + assert.equal(inputHandler.curAttrData.getFgColor(), i); + assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P256); + assert.equal(inputHandler.curAttrData.getBgColor(), i); + } + // reset to DEFAULT + inputHandler.parse(`\x1b[39;49m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); + assert.equal(inputHandler.curAttrData.getFgColor(), -1); + assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); + assert.equal(inputHandler.curAttrData.getBgColor(), -1); + }); + it('colormode RGB', () => { + assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT + assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT + inputHandler.parse(`\x1b[38;2;1;2;3;48;2;4;5;6m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_RGB); + assert.equal(inputHandler.curAttrData.getFgColor(), 1 << 16 | 2 << 8 | 3); + assert.deepEqual(AttributeData.toColorRGB(inputHandler.curAttrData.getFgColor()), [1, 2, 3]); + assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_RGB); + assert.deepEqual(AttributeData.toColorRGB(inputHandler.curAttrData.getBgColor()), [4, 5, 6]); + // reset to DEFAULT + inputHandler.parse(`\x1b[39;49m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); + assert.equal(inputHandler.curAttrData.getFgColor(), -1); + assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); + assert.equal(inputHandler.curAttrData.getBgColor(), -1); + }); + it('colormode transition RGB to 256', () => { + // enter RGB for FG and BG + inputHandler.parse(`\x1b[38;2;1;2;3;48;2;4;5;6m`); + // enter 256 for FG and BG + inputHandler.parse(`\x1b[38;5;255;48;5;255m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P256); + assert.equal(inputHandler.curAttrData.getFgColor(), 255); + assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P256); + assert.equal(inputHandler.curAttrData.getBgColor(), 255); + }); + it('colormode transition RGB to 16', () => { + // enter RGB for FG and BG + inputHandler.parse(`\x1b[38;2;1;2;3;48;2;4;5;6m`); + // enter 16 for FG and BG + inputHandler.parse(`\x1b[37;47m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P16); + assert.equal(inputHandler.curAttrData.getFgColor(), 7); + assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P16); + assert.equal(inputHandler.curAttrData.getBgColor(), 7); + }); + it('colormode transition 16 to 256', () => { + // enter 16 for FG and BG + inputHandler.parse(`\x1b[37;47m`); + // enter 256 for FG and BG + inputHandler.parse(`\x1b[38;5;255;48;5;255m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P256); + assert.equal(inputHandler.curAttrData.getFgColor(), 255); + assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P256); + assert.equal(inputHandler.curAttrData.getBgColor(), 255); + }); + it('colormode transition 256 to 16', () => { + // enter 256 for FG and BG + inputHandler.parse(`\x1b[38;5;255;48;5;255m`); + // enter 16 for FG and BG + inputHandler.parse(`\x1b[37;47m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P16); + assert.equal(inputHandler.curAttrData.getFgColor(), 7); + assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P16); + assert.equal(inputHandler.curAttrData.getBgColor(), 7); + }); + it('should zero missing RGB values', () => { + inputHandler.parse(`\x1b[38;2;1;2;3m`); + inputHandler.parse(`\x1b[38;2;5m`); + assert.deepEqual(AttributeData.toColorRGB(inputHandler.curAttrData.getFgColor()), [5, 0, 0]); + }); + }); + describe('colon notation', () => { + let inputHandler2: TestInputHandler; + beforeEach(() => { + inputHandler2 = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService()); + }); + describe('should equal to semicolon', () => { + it('CSI 38:2::50:100:150 m', () => { + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2;50;100;150m'); + inputHandler.parse('\x1b[38:2::50:100:150m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + it('CSI 38:2::50:100: m', () => { + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2;50;100;m'); + inputHandler.parse('\x1b[38:2::50:100:m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + it('CSI 38:2::50:: m', () => { + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2;50;;m'); + inputHandler.parse('\x1b[38:2::50::m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 0 << 8 | 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + it('CSI 38:2:::: m', () => { + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2;;;m'); + inputHandler.parse('\x1b[38:2::::m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + it('CSI 38;2::50:100:150 m', () => { + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2;50;100;150m'); + inputHandler.parse('\x1b[38;2::50:100:150m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + it('CSI 38;2;50:100:150 m', () => { + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2;50;100;150m'); + inputHandler.parse('\x1b[38;2;50:100:150m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + it('CSI 38;2;50;100:150 m', () => { + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2;50;100;150m'); + inputHandler.parse('\x1b[38;2;50;100:150m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + it('CSI 38:5:50 m', () => { + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;5;50m'); + inputHandler.parse('\x1b[38:5:50m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFF, 50); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + it('CSI 38:5: m', () => { + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;5;m'); + inputHandler.parse('\x1b[38:5:m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFF, 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + it('CSI 38;5:50 m', () => { + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;5;50m'); + inputHandler.parse('\x1b[38;5:50m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFF, 50); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + }); + describe('should fill early sequence end with default of 0', () => { + it('CSI 38:2 m', () => { + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2m'); + inputHandler.parse('\x1b[38:2m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + it('CSI 38:5 m', () => { + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;5m'); + inputHandler.parse('\x1b[38:5m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFF, 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + }); + describe('should not interfere with leading/following SGR attrs', () => { + it('CSI 1 ; 38:2::50:100:150 ; 4 m', () => { + inputHandler2.parse('\x1b[1;38;2;50;100;150;4m'); + inputHandler.parse('\x1b[1;38:2::50:100:150;4m'); + assert.equal(!!inputHandler2.curAttrData.isBold(), true); + assert.equal(!!inputHandler2.curAttrData.isUnderline(), true); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + it('CSI 1 ; 38:2::50:100: ; 4 m', () => { + inputHandler2.parse('\x1b[1;38;2;50;100;;4m'); + inputHandler.parse('\x1b[1;38:2::50:100:;4m'); + assert.equal(!!inputHandler2.curAttrData.isBold(), true); + assert.equal(!!inputHandler2.curAttrData.isUnderline(), true); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + it('CSI 1 ; 38:2::50:100 ; 4 m', () => { + inputHandler2.parse('\x1b[1;38;2;50;100;;4m'); + inputHandler.parse('\x1b[1;38:2::50:100;4m'); + assert.equal(!!inputHandler2.curAttrData.isBold(), true); + assert.equal(!!inputHandler2.curAttrData.isUnderline(), true); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + it('CSI 1 ; 38:2:: ; 4 m', () => { + inputHandler2.parse('\x1b[1;38;2;;;;4m'); + inputHandler.parse('\x1b[1;38:2::;4m'); + assert.equal(!!inputHandler2.curAttrData.isBold(), true); + assert.equal(!!inputHandler2.curAttrData.isUnderline(), true); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + it('CSI 1 ; 38;2:: ; 4 m', () => { + inputHandler2.parse('\x1b[1;38;2;;;;4m'); + inputHandler.parse('\x1b[1;38;2::;4m'); + assert.equal(!!inputHandler2.curAttrData.isBold(), true); + assert.equal(!!inputHandler2.curAttrData.isUnderline(), true); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); + }); + }); + }); + describe('cursor positioning', () => { + beforeEach(() => { + bufferService.resize(10, 10); + }); + it('cursor forward (CUF)', () => { + inputHandler.parse('\x1b[C'); + assert.deepEqual(getCursor(bufferService), [1, 0]); + inputHandler.parse('\x1b[1C'); + assert.deepEqual(getCursor(bufferService), [2, 0]); + inputHandler.parse('\x1b[4C'); + assert.deepEqual(getCursor(bufferService), [6, 0]); + inputHandler.parse('\x1b[100C'); + assert.deepEqual(getCursor(bufferService), [9, 0]); + // should not change y + bufferService.buffer.x = 8; + bufferService.buffer.y = 4; + inputHandler.parse('\x1b[C'); + assert.deepEqual(getCursor(bufferService), [9, 4]); + }); + it('cursor backward (CUB)', () => { + inputHandler.parse('\x1b[D'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[1D'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + // place cursor at end of first line + inputHandler.parse('\x1b[100C'); + inputHandler.parse('\x1b[D'); + assert.deepEqual(getCursor(bufferService), [8, 0]); + inputHandler.parse('\x1b[1D'); + assert.deepEqual(getCursor(bufferService), [7, 0]); + inputHandler.parse('\x1b[4D'); + assert.deepEqual(getCursor(bufferService), [3, 0]); + inputHandler.parse('\x1b[100D'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + // should not change y + bufferService.buffer.x = 4; + bufferService.buffer.y = 4; + inputHandler.parse('\x1b[D'); + assert.deepEqual(getCursor(bufferService), [3, 4]); + }); + it('cursor down (CUD)', () => { + inputHandler.parse('\x1b[B'); + assert.deepEqual(getCursor(bufferService), [0, 1]); + inputHandler.parse('\x1b[1B'); + assert.deepEqual(getCursor(bufferService), [0, 2]); + inputHandler.parse('\x1b[4B'); + assert.deepEqual(getCursor(bufferService), [0, 6]); + inputHandler.parse('\x1b[100B'); + assert.deepEqual(getCursor(bufferService), [0, 9]); + // should not change x + bufferService.buffer.x = 8; + bufferService.buffer.y = 0; + inputHandler.parse('\x1b[B'); + assert.deepEqual(getCursor(bufferService), [8, 1]); + }); + it('cursor up (CUU)', () => { + inputHandler.parse('\x1b[A'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[1A'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + // place cursor at beginning of last row + inputHandler.parse('\x1b[100B'); + inputHandler.parse('\x1b[A'); + assert.deepEqual(getCursor(bufferService), [0, 8]); + inputHandler.parse('\x1b[1A'); + assert.deepEqual(getCursor(bufferService), [0, 7]); + inputHandler.parse('\x1b[4A'); + assert.deepEqual(getCursor(bufferService), [0, 3]); + inputHandler.parse('\x1b[100A'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + // should not change x + bufferService.buffer.x = 8; + bufferService.buffer.y = 9; + inputHandler.parse('\x1b[A'); + assert.deepEqual(getCursor(bufferService), [8, 8]); + }); + it('cursor next line (CNL)', () => { + inputHandler.parse('\x1b[E'); + assert.deepEqual(getCursor(bufferService), [0, 1]); + inputHandler.parse('\x1b[1E'); + assert.deepEqual(getCursor(bufferService), [0, 2]); + inputHandler.parse('\x1b[4E'); + assert.deepEqual(getCursor(bufferService), [0, 6]); + inputHandler.parse('\x1b[100E'); + assert.deepEqual(getCursor(bufferService), [0, 9]); + // should reset x to zero + bufferService.buffer.x = 8; + bufferService.buffer.y = 0; + inputHandler.parse('\x1b[E'); + assert.deepEqual(getCursor(bufferService), [0, 1]); + }); + it('cursor previous line (CPL)', () => { + inputHandler.parse('\x1b[F'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[1F'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + // place cursor at beginning of last row + inputHandler.parse('\x1b[100E'); + inputHandler.parse('\x1b[F'); + assert.deepEqual(getCursor(bufferService), [0, 8]); + inputHandler.parse('\x1b[1F'); + assert.deepEqual(getCursor(bufferService), [0, 7]); + inputHandler.parse('\x1b[4F'); + assert.deepEqual(getCursor(bufferService), [0, 3]); + inputHandler.parse('\x1b[100F'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + // should reset x to zero + bufferService.buffer.x = 8; + bufferService.buffer.y = 9; + inputHandler.parse('\x1b[F'); + assert.deepEqual(getCursor(bufferService), [0, 8]); + }); + it('cursor character absolute (CHA)', () => { + inputHandler.parse('\x1b[G'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[1G'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[2G'); + assert.deepEqual(getCursor(bufferService), [1, 0]); + inputHandler.parse('\x1b[5G'); + assert.deepEqual(getCursor(bufferService), [4, 0]); + inputHandler.parse('\x1b[100G'); + assert.deepEqual(getCursor(bufferService), [9, 0]); + }); + it('cursor position (CUP)', () => { + bufferService.buffer.x = 5; + bufferService.buffer.y = 5; + inputHandler.parse('\x1b[H'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + bufferService.buffer.x = 5; + bufferService.buffer.y = 5; + inputHandler.parse('\x1b[1H'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + bufferService.buffer.x = 5; + bufferService.buffer.y = 5; + inputHandler.parse('\x1b[1;1H'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + bufferService.buffer.x = 5; + bufferService.buffer.y = 5; + inputHandler.parse('\x1b[8H'); + assert.deepEqual(getCursor(bufferService), [0, 7]); + bufferService.buffer.x = 5; + bufferService.buffer.y = 5; + inputHandler.parse('\x1b[;8H'); + assert.deepEqual(getCursor(bufferService), [7, 0]); + bufferService.buffer.x = 5; + bufferService.buffer.y = 5; + inputHandler.parse('\x1b[100;100H'); + assert.deepEqual(getCursor(bufferService), [9, 9]); + }); + it('horizontal position absolute (HPA)', () => { + inputHandler.parse('\x1b[`'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[1`'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[2`'); + assert.deepEqual(getCursor(bufferService), [1, 0]); + inputHandler.parse('\x1b[5`'); + assert.deepEqual(getCursor(bufferService), [4, 0]); + inputHandler.parse('\x1b[100`'); + assert.deepEqual(getCursor(bufferService), [9, 0]); + }); + it('horizontal position relative (HPR)', () => { + inputHandler.parse('\x1b[a'); + assert.deepEqual(getCursor(bufferService), [1, 0]); + inputHandler.parse('\x1b[1a'); + assert.deepEqual(getCursor(bufferService), [2, 0]); + inputHandler.parse('\x1b[4a'); + assert.deepEqual(getCursor(bufferService), [6, 0]); + inputHandler.parse('\x1b[100a'); + assert.deepEqual(getCursor(bufferService), [9, 0]); + // should not change y + bufferService.buffer.x = 8; + bufferService.buffer.y = 4; + inputHandler.parse('\x1b[a'); + assert.deepEqual(getCursor(bufferService), [9, 4]); + }); + it('vertical position absolute (VPA)', () => { + inputHandler.parse('\x1b[d'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[1d'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[2d'); + assert.deepEqual(getCursor(bufferService), [0, 1]); + inputHandler.parse('\x1b[5d'); + assert.deepEqual(getCursor(bufferService), [0, 4]); + inputHandler.parse('\x1b[100d'); + assert.deepEqual(getCursor(bufferService), [0, 9]); + // should not change x + bufferService.buffer.x = 8; + bufferService.buffer.y = 4; + inputHandler.parse('\x1b[d'); + assert.deepEqual(getCursor(bufferService), [8, 0]); + }); + it('vertical position relative (VPR)', () => { + inputHandler.parse('\x1b[e'); + assert.deepEqual(getCursor(bufferService), [0, 1]); + inputHandler.parse('\x1b[1e'); + assert.deepEqual(getCursor(bufferService), [0, 2]); + inputHandler.parse('\x1b[4e'); + assert.deepEqual(getCursor(bufferService), [0, 6]); + inputHandler.parse('\x1b[100e'); + assert.deepEqual(getCursor(bufferService), [0, 9]); + // should not change x + bufferService.buffer.x = 8; + bufferService.buffer.y = 4; + inputHandler.parse('\x1b[e'); + assert.deepEqual(getCursor(bufferService), [8, 5]); + }); + describe('should clamp cursor into addressible range', () => { + it('CUF', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[C'); + assert.deepEqual(getCursor(bufferService), [9, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[C'); + assert.deepEqual(getCursor(bufferService), [1, 0]); + }); + it('CUB', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[D'); + assert.deepEqual(getCursor(bufferService), [8, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[D'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + }); + it('CUD', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[B'); + assert.deepEqual(getCursor(bufferService), [9, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[B'); + assert.deepEqual(getCursor(bufferService), [0, 1]); + }); + it('CUU', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[A'); + assert.deepEqual(getCursor(bufferService), [9, 8]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[A'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + }); + it('CNL', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[E'); + assert.deepEqual(getCursor(bufferService), [0, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[E'); + assert.deepEqual(getCursor(bufferService), [0, 1]); + }); + it('CPL', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[F'); + assert.deepEqual(getCursor(bufferService), [0, 8]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[F'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + }); + it('CHA', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[5G'); + assert.deepEqual(getCursor(bufferService), [4, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[5G'); + assert.deepEqual(getCursor(bufferService), [4, 0]); + }); + it('CUP', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[5;5H'); + assert.deepEqual(getCursor(bufferService), [4, 4]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[5;5H'); + assert.deepEqual(getCursor(bufferService), [4, 4]); + }); + it('HPA', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[5`'); + assert.deepEqual(getCursor(bufferService), [4, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[5`'); + assert.deepEqual(getCursor(bufferService), [4, 0]); + }); + it('HPR', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[a'); + assert.deepEqual(getCursor(bufferService), [9, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[a'); + assert.deepEqual(getCursor(bufferService), [1, 0]); + }); + it('VPA', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[5d'); + assert.deepEqual(getCursor(bufferService), [9, 4]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[5d'); + assert.deepEqual(getCursor(bufferService), [0, 4]); + }); + it('VPR', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[e'); + assert.deepEqual(getCursor(bufferService), [9, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[e'); + assert.deepEqual(getCursor(bufferService), [0, 1]); + }); + it('DCH', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[P'); + assert.deepEqual(getCursor(bufferService), [9, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[P'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + }); + it('DCH - should delete last cell', () => { + inputHandler.parse('0123456789\x1b[P'); + assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), '012345678 '); + }); + it('ECH', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[X'); + assert.deepEqual(getCursor(bufferService), [9, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[X'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + }); + it('ECH - should delete last cell', () => { + inputHandler.parse('0123456789\x1b[X'); + assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), '012345678 '); + }); + it('ICH', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[@'); + assert.deepEqual(getCursor(bufferService), [9, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[@'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + }); + it('ICH - should delete last cell', () => { + inputHandler.parse('0123456789\x1b[@'); + assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), '012345678 '); + }); + }); + }); + describe('DECSTBM - scroll margins', () => { + beforeEach(() => { + bufferService.resize(10, 10); + }); + it('should default to whole viewport', () => { + inputHandler.parse('\x1b[r'); + assert.equal(bufferService.buffer.scrollTop, 0); + assert.equal(bufferService.buffer.scrollBottom, 9); + inputHandler.parse('\x1b[3;7r'); + assert.equal(bufferService.buffer.scrollTop, 2); + assert.equal(bufferService.buffer.scrollBottom, 6); + inputHandler.parse('\x1b[0;0r'); + assert.equal(bufferService.buffer.scrollTop, 0); + assert.equal(bufferService.buffer.scrollBottom, 9); + }); + it('should clamp bottom', () => { + inputHandler.parse('\x1b[3;1000r'); + assert.equal(bufferService.buffer.scrollTop, 2); + assert.equal(bufferService.buffer.scrollBottom, 9); + }); + it('should only apply for top < bottom', () => { + inputHandler.parse('\x1b[7;2r'); + assert.equal(bufferService.buffer.scrollTop, 0); + assert.equal(bufferService.buffer.scrollBottom, 9); + }); + it('should home cursor', () => { + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[2;7r'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + }); + }); + describe('scroll margins', () => { + beforeEach(() => { + bufferService.resize(10, 10); + }); + it('scrollUp', () => { + inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Sm'); + assert.deepEqual(getLines(bufferService), ['m', '3', '', '', '4', '5', '6', '7', '8', '9']); + }); + it('scrollDown', () => { + inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Tm'); + assert.deepEqual(getLines(bufferService), ['m', '', '', '1', '4', '5', '6', '7', '8', '9']); + }); + it('insertLines - out of margins', () => { + inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); + assert.equal(bufferService.buffer.scrollTop, 2); + assert.equal(bufferService.buffer.scrollBottom, 5); + inputHandler.parse('\x1b[2Lm'); + assert.deepEqual(getLines(bufferService), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']); + inputHandler.parse('\x1b[2H\x1b[2Ln'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']); + // skip below scrollbottom + inputHandler.parse('\x1b[7H\x1b[2Lo'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']); + inputHandler.parse('\x1b[8H\x1b[2Lp'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']); + inputHandler.parse('\x1b[100H\x1b[2Lq'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']); + }); + it('insertLines - within margins', () => { + inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); + assert.equal(bufferService.buffer.scrollTop, 2); + assert.equal(bufferService.buffer.scrollBottom, 5); + inputHandler.parse('\x1b[3H\x1b[2Lm'); + assert.deepEqual(getLines(bufferService), ['0', '1', 'm', '', '2', '3', '6', '7', '8', '9']); + inputHandler.parse('\x1b[6H\x1b[2Ln'); + assert.deepEqual(getLines(bufferService), ['0', '1', 'm', '', '2', 'n', '6', '7', '8', '9']); + }); + it('deleteLines - out of margins', () => { + inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); + assert.equal(bufferService.buffer.scrollTop, 2); + assert.equal(bufferService.buffer.scrollBottom, 5); + inputHandler.parse('\x1b[2Mm'); + assert.deepEqual(getLines(bufferService), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']); + inputHandler.parse('\x1b[2H\x1b[2Mn'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']); + // skip below scrollbottom + inputHandler.parse('\x1b[7H\x1b[2Mo'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']); + inputHandler.parse('\x1b[8H\x1b[2Mp'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']); + inputHandler.parse('\x1b[100H\x1b[2Mq'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']); + }); + it('deleteLines - within margins', () => { + inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); + assert.equal(bufferService.buffer.scrollTop, 2); + assert.equal(bufferService.buffer.scrollBottom, 5); + inputHandler.parse('\x1b[6H\x1b[2Mm'); + assert.deepEqual(getLines(bufferService), ['0', '1', '2', '3', '4', 'm', '6', '7', '8', '9']); + inputHandler.parse('\x1b[3H\x1b[2Mn'); + assert.deepEqual(getLines(bufferService), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']); + }); + }); + it('should parse big chunks in smaller subchunks', () => { + // max single chunk size is hardcoded as 131072 + const calls: any[] = []; + bufferService.resize(10, 10); + (inputHandler as any)._parser.parse = (data: Uint32Array, length: number) => { + calls.push([data.length, length]); + }; + inputHandler.parse('12345'); + inputHandler.parse('a'.repeat(10000)); + inputHandler.parse('a'.repeat(200000)); + inputHandler.parse('a'.repeat(300000)); + assert.deepEqual(calls, [ + [4096, 5], + [10000, 10000], + [131072, 131072], [131072, 200000 - 131072], + [131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072] + ]); + }); + describe('windowOptions', () => { + it('all should be disabled by default and not report', () => { + bufferService.resize(10, 10); + const stack: string[] = []; + coreService.onData(data => stack.push(data)); + inputHandler.parse('\x1b[14t'); + inputHandler.parse('\x1b[16t'); + inputHandler.parse('\x1b[18t'); + inputHandler.parse('\x1b[20t'); + inputHandler.parse('\x1b[21t'); + assert.deepEqual(stack, []); + }); + it('14 - GetWinSizePixels', () => { + bufferService.resize(10, 10); + optionsService.options.windowOptions.getWinSizePixels = true; + const stack: string[] = []; + coreService.onData(data => stack.push(data)); + inputHandler.parse('\x1b[14t'); + // does not report in test terminal due to missing renderer + assert.deepEqual(stack, []); + }); + it('16 - GetCellSizePixels', () => { + bufferService.resize(10, 10); + optionsService.options.windowOptions.getCellSizePixels = true; + const stack: string[] = []; + coreService.onData(data => stack.push(data)); + inputHandler.parse('\x1b[16t'); + // does not report in test terminal due to missing renderer + assert.deepEqual(stack, []); + }); + it('18 - GetWinSizeChars', () => { + bufferService.resize(10, 10); + optionsService.options.windowOptions.getWinSizeChars = true; + const stack: string[] = []; + coreService.onData(data => stack.push(data)); + inputHandler.parse('\x1b[18t'); + assert.deepEqual(stack, ['\x1b[8;10;10t']); + bufferService.resize(50, 20); + inputHandler.parse('\x1b[18t'); + assert.deepEqual(stack, ['\x1b[8;10;10t', '\x1b[8;20;50t']); + }); + it('22/23 - PushTitle/PopTitle', () => { + bufferService.resize(10, 10); + optionsService.options.windowOptions.pushTitle = true; + optionsService.options.windowOptions.popTitle = true; + const stack: string[] = []; + inputHandler.onTitleChange(data => stack.push(data)); + inputHandler.parse('\x1b]0;1\x07'); + inputHandler.parse('\x1b[22t'); + inputHandler.parse('\x1b]0;2\x07'); + inputHandler.parse('\x1b[22t'); + inputHandler.parse('\x1b]0;3\x07'); + inputHandler.parse('\x1b[22t'); + assert.deepEqual(inputHandler.windowTitleStack, ['1', '2', '3']); + assert.deepEqual(inputHandler.iconNameStack, ['1', '2', '3']); + assert.deepEqual(stack, ['1', '2', '3']); + inputHandler.parse('\x1b[23t'); + inputHandler.parse('\x1b[23t'); + inputHandler.parse('\x1b[23t'); + inputHandler.parse('\x1b[23t'); // one more to test "overflow" + assert.deepEqual(inputHandler.windowTitleStack, []); + assert.deepEqual(inputHandler.iconNameStack, []); + assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); + }); + it('22/23 - PushTitle/PopTitle with ;1', () => { + bufferService.resize(10, 10); + optionsService.options.windowOptions.pushTitle = true; + optionsService.options.windowOptions.popTitle = true; + const stack: string[] = []; + inputHandler.onTitleChange(data => stack.push(data)); + inputHandler.parse('\x1b]0;1\x07'); + inputHandler.parse('\x1b[22;1t'); + inputHandler.parse('\x1b]0;2\x07'); + inputHandler.parse('\x1b[22;1t'); + inputHandler.parse('\x1b]0;3\x07'); + inputHandler.parse('\x1b[22;1t'); + assert.deepEqual(inputHandler.windowTitleStack, []); + assert.deepEqual(inputHandler.iconNameStack, ['1', '2', '3']); + assert.deepEqual(stack, ['1', '2', '3']); + inputHandler.parse('\x1b[23;1t'); + inputHandler.parse('\x1b[23;1t'); + inputHandler.parse('\x1b[23;1t'); + inputHandler.parse('\x1b[23;1t'); // one more to test "overflow" + assert.deepEqual(inputHandler.windowTitleStack, []); + assert.deepEqual(inputHandler.iconNameStack, []); + assert.deepEqual(stack, ['1', '2', '3']); + }); + it('22/23 - PushTitle/PopTitle with ;2', () => { + bufferService.resize(10, 10); + optionsService.options.windowOptions.pushTitle = true; + optionsService.options.windowOptions.popTitle = true; + const stack: string[] = []; + inputHandler.onTitleChange(data => stack.push(data)); + inputHandler.parse('\x1b]0;1\x07'); + inputHandler.parse('\x1b[22;2t'); + inputHandler.parse('\x1b]0;2\x07'); + inputHandler.parse('\x1b[22;2t'); + inputHandler.parse('\x1b]0;3\x07'); + inputHandler.parse('\x1b[22;2t'); + assert.deepEqual(inputHandler.windowTitleStack, ['1', '2', '3']); + assert.deepEqual(inputHandler.iconNameStack, []); + assert.deepEqual(stack, ['1', '2', '3']); + inputHandler.parse('\x1b[23;2t'); + inputHandler.parse('\x1b[23;2t'); + inputHandler.parse('\x1b[23;2t'); + inputHandler.parse('\x1b[23;2t'); // one more to test "overflow" + assert.deepEqual(inputHandler.windowTitleStack, []); + assert.deepEqual(inputHandler.iconNameStack, []); + assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); + }); + it('DECCOLM - should only work with "SetWinLines" (24) enabled', () => { + // disabled + bufferService.resize(10, 10); + inputHandler.parse('\x1b[?3l'); + assert.equal(bufferService.cols, 10); + inputHandler.parse('\x1b[?3h'); + assert.equal(bufferService.cols, 10); + // enabled + inputHandler.reset(); + optionsService.options.windowOptions.setWinLines = true; + inputHandler.parse('\x1b[?3l'); + assert.equal(bufferService.cols, 80); + inputHandler.parse('\x1b[?3h'); + assert.equal(bufferService.cols, 132); + }); + }); + describe('should correctly reset cells taken by wide chars', () => { + beforeEach(() => { + bufferService.resize(10, 5); + optionsService.options.scrollback = 1; + inputHandler.parse('¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥'); + }); + it('print', () => { + inputHandler.parse('\x1b[H#'); + assert.deepEqual(getLines(bufferService), ['# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[1;6H######'); + assert.deepEqual(getLines(bufferService), ['# ¥ #####', '# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('#'); + assert.deepEqual(getLines(bufferService), ['# ¥ #####', '##¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('#'); + assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[3;9H#'); + assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥#', '¥¥¥¥¥', '']); + inputHandler.parse('#'); + assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '¥¥¥¥¥', '']); + inputHandler.parse('#'); + assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥¥', '']); + inputHandler.parse('\x1b[4;10H#'); + assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥ #', '']); + }); + it('EL', () => { + inputHandler.parse('\x1b[1;6H\x1b[K#'); + assert.deepEqual(getLines(bufferService), ['¥¥ #', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[2;5H\x1b[1K'); + assert.deepEqual(getLines(bufferService), ['¥¥ #', ' ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[3;6H\x1b[1K'); + assert.deepEqual(getLines(bufferService), ['¥¥ #', ' ¥¥', ' ¥¥', '¥¥¥¥¥', '']); + }); + it('ICH', () => { + inputHandler.parse('\x1b[1;6H\x1b[@'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[2;4H\x1b[2@'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[3;4H\x1b[3@'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[4;4H\x1b[4@'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥ ¥', '']); + }); + it('DCH', () => { + inputHandler.parse('\x1b[1;6H\x1b[P'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[2;6H\x1b[2P'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[3;6H\x1b[3P'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); + }); + it('ECH', () => { + inputHandler.parse('\x1b[1;6H\x1b[X'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[2;6H\x1b[2X'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[3;6H\x1b[3X'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); + }); + }); + describe('DECSTR', () => { + beforeEach(() => { + bufferService.resize(10, 5); + optionsService.options.scrollback = 1; + inputHandler.parse('01234567890123'); + }); + it('should reset IRM', () => { + inputHandler.parse('\x1b[4h'); + assert.equal(coreService.modes.insertMode, true); + inputHandler.parse('\x1b[!p'); + assert.equal(coreService.modes.insertMode, false); + }); + it('should reset cursor visibility', () => { + inputHandler.parse('\x1b[?25l'); + assert.equal(coreService.isCursorHidden, true); + inputHandler.parse('\x1b[!p'); + assert.equal(coreService.isCursorHidden, false); + }); + it('should reset scroll margins', () => { + inputHandler.parse('\x1b[2;4r'); + assert.equal(bufferService.buffer.scrollTop, 1); + assert.equal(bufferService.buffer.scrollBottom, 3); + inputHandler.parse('\x1b[!p'); + assert.equal(bufferService.buffer.scrollTop, 0); + assert.equal(bufferService.buffer.scrollBottom, bufferService.rows - 1); + }); + it('should reset text attributes', () => { + inputHandler.parse('\x1b[1;2;32;43m'); + assert.equal(!!inputHandler.curAttrData.isBold(), true); + inputHandler.parse('\x1b[!p'); + assert.equal(!!inputHandler.curAttrData.isBold(), false); + assert.equal(inputHandler.curAttrData.fg, 0); + assert.equal(inputHandler.curAttrData.bg, 0); + }); + it('should reset DECSC data', () => { + inputHandler.parse('\x1b7'); + assert.equal(bufferService.buffer.savedX, 4); + assert.equal(bufferService.buffer.savedY, 1); + inputHandler.parse('\x1b[!p'); + assert.equal(bufferService.buffer.savedX, 0); + assert.equal(bufferService.buffer.savedY, 0); + }); + it('should reset DECOM', () => { + inputHandler.parse('\x1b[?6h'); + assert.equal(coreService.decPrivateModes.origin, true); + inputHandler.parse('\x1b[!p'); + assert.equal(coreService.decPrivateModes.origin, false); + }); + }); +}); diff --git a/src/InputHandler.ts b/src/common/InputHandler.ts similarity index 96% rename from src/InputHandler.ts rename to src/common/InputHandler.ts index e80887cc..1ec2a68d 100644 --- a/src/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { IInputHandler, IInputHandlingTerminal } from './Types'; +import { IInputHandler, IAttributeData, IDisposable, IWindowOptions } from 'common/Types'; import { C0, C1 } from 'common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; @@ -17,11 +17,9 @@ import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IFunctionId import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; -import { IAttributeData, IDisposable, IWindowOptions } from 'common/Types'; -import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IInstantiationService } from 'common/services/Services'; +import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; -import { IRenderService } from 'browser/services/Services'; /** * Map collect to glevel. Used in `selectCharset`. @@ -94,7 +92,10 @@ function paramToWindowOption(n: number, opts: IWindowOptions): boolean { return false; } - +export enum WindowsOptionsReportType { + GET_WIN_SIZE_PIXELS = 0, + GET_CELL_SIZE_PIXELS = 1 +} /** * DCS subparser implementations @@ -218,27 +219,39 @@ export class InputHandler extends Disposable implements IInputHandler { private _workCell: CellData = new CellData(); private _windowTitle = ''; private _iconName = ''; - private _windowTitleStack: string[] = []; - private _iconNameStack: string[] = []; + protected _windowTitleStack: string[] = []; + protected _iconNameStack: string[] = []; private _curAttrData: IAttributeData = DEFAULT_ATTR_DATA.clone(); private _eraseAttrDataInternal: IAttributeData = DEFAULT_ATTR_DATA.clone(); + private _onRequestBell = new EventEmitter(); + public get onRequestBell(): IEvent { return this._onRequestBell.event; } private _onRequestRefreshRows = new EventEmitter(); public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } private _onRequestReset = new EventEmitter(); public get onRequestReset(): IEvent { return this._onRequestReset.event; } - private _onRequestBell = new EventEmitter(); - public get onRequestBell(): IEvent { return this._onRequestBell.event; } + private _onRequestScroll = new EventEmitter(); + public get onRequestScroll(): IEvent { return this._onRequestScroll.event; } + private _onRequestSyncScrollBar = new EventEmitter(); + public get onRequestSyncScrollBar(): IEvent { return this._onRequestSyncScrollBar.event; } + private _onRequestWindowsOptionsReport = new EventEmitter(); + public get onRequestWindowsOptionsReport(): IEvent { return this._onRequestWindowsOptionsReport.event; } + + private _onA11yChar = new EventEmitter(); + public get onA11yChar(): IEvent { return this._onA11yChar.event; } + private _onA11yTab = new EventEmitter(); + public get onA11yTab(): IEvent { return this._onA11yTab.event; } private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } private _onLineFeed = new EventEmitter(); public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onScroll = new EventEmitter(); public get onScroll(): IEvent { return this._onScroll.event; } + private _onTitleChange = new EventEmitter(); + public get onTitleChange(): IEvent { return this._onTitleChange.event; } constructor( - private _terminal: IInputHandlingTerminal, private readonly _bufferService: IBufferService, private readonly _charsetService: ICharsetService, private readonly _coreService: ICoreService, @@ -247,9 +260,8 @@ export class InputHandler extends Disposable implements IInputHandler { private readonly _optionsService: IOptionsService, private readonly _coreMouseService: ICoreMouseService, private readonly _unicodeService: IUnicodeService, - private readonly _instantiationService: IInstantiationService, - private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser()) - { + private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser() + ) { super(); this.register(this._parser); @@ -489,9 +501,9 @@ export class InputHandler extends Disposable implements IInputHandler { const screenReaderMode = this._optionsService.options.screenReaderMode; const cols = this._bufferService.cols; const wraparoundMode = this._coreService.decPrivateModes.wraparound; - const insertMode = this._terminal.insertMode; + const insertMode = this._coreService.modes.insertMode; const curAttr = this._curAttrData; - let bufferRow = buffer.lines.get(buffer.ybase + buffer.y); + let bufferRow = buffer.lines.get(buffer.ybase + buffer.y)!; this._dirtyRowService.markDirty(buffer.y); @@ -518,7 +530,7 @@ export class InputHandler extends Disposable implements IInputHandler { } if (screenReaderMode) { - this._terminal.onA11yCharEmitter.fire(stringFromCodePoint(code)); + this._onA11yChar.fire(stringFromCodePoint(code)); } // insert combining char at last cursor position @@ -548,17 +560,17 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._terminal.scroll(this._eraseAttrData(), true); + this._onRequestScroll.fire(this._eraseAttrData(), true); } else { if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; } // The line already exists (eg. the initial viewport), mark it as a // wrapped line - buffer.lines.get(buffer.ybase + buffer.y).isWrapped = true; + buffer.lines.get(buffer.ybase + buffer.y)!.isWrapped = true; } // row changed, get it again - bufferRow = buffer.lines.get(buffer.ybase + buffer.y); + bufferRow = buffer.lines.get(buffer.ybase + buffer.y)!; } else { buffer.x = cols - 1; if (chWidth === 2) { @@ -687,7 +699,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._terminal.scroll(this._eraseAttrData()); + this._onRequestScroll.fire(this._eraseAttrData()); } else if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; } @@ -736,7 +748,7 @@ export class InputHandler extends Disposable implements IInputHandler { const originalX = this._bufferService.buffer.x; this._bufferService.buffer.x = this._bufferService.buffer.nextStop(); if (this._optionsService.options.screenReaderMode) { - this._terminal.onA11yTabEmitter.fire(this._bufferService.buffer.x - originalX); + this._onA11yTab.fire(this._bufferService.buffer.x - originalX); } } @@ -1024,7 +1036,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @param end end - 1 is last erased cell */ private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false): void { - const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + y); + const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + y)!; line.replaceCells( start, end, @@ -1042,7 +1054,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @param y row index */ private _resetBufferLine(y: number): void { - const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + y); + const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + y)!; line.fill(this._bufferService.buffer.getNullCell(this._eraseAttrData())); line.isWrapped = false; } @@ -1091,7 +1103,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._eraseInBufferLine(j, 0, this._bufferService.buffer.x + 1, true); if (this._bufferService.buffer.x + 1 >= this._bufferService.cols) { // Deleted entire previous line. This next line can no longer be wrapped. - this._bufferService.buffer.lines.get(j + 1).isWrapped = false; + this._bufferService.buffer.lines.get(j + 1)!.isWrapped = false; } while (j--) { this._resetBufferLine(j); @@ -1343,7 +1355,7 @@ export class InputHandler extends Disposable implements IInputHandler { } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { - const line = buffer.lines.get(buffer.ybase + y); + const line = buffer.lines.get(buffer.ybase + y)!; line.deleteCells(0, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } @@ -1376,7 +1388,7 @@ export class InputHandler extends Disposable implements IInputHandler { } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { - const line = buffer.lines.get(buffer.ybase + y); + const line = buffer.lines.get(buffer.ybase + y)!; line.insertCells(0, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } @@ -1399,7 +1411,7 @@ export class InputHandler extends Disposable implements IInputHandler { } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { - const line = this._bufferService.buffer.lines.get(buffer.ybase + y); + const line = this._bufferService.buffer.lines.get(buffer.ybase + y)!; line.insertCells(buffer.x, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } @@ -1422,7 +1434,7 @@ export class InputHandler extends Disposable implements IInputHandler { } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { - const line = buffer.lines.get(buffer.ybase + y); + const line = buffer.lines.get(buffer.ybase + y)!; line.deleteCells(buffer.x, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } @@ -1520,9 +1532,9 @@ export class InputHandler extends Disposable implements IInputHandler { if (params.params[0] > 0) { return; } - if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) { + if (this._is('xterm') || this._is('rxvt-unicode') || this._is('screen')) { this._coreService.triggerDataEvent(C0.ESC + '[?1;2c'); - } else if (this._terminal.is('linux')) { + } else if (this._is('linux')) { this._coreService.triggerDataEvent(C0.ESC + '[?6c'); } } @@ -1558,19 +1570,27 @@ export class InputHandler extends Disposable implements IInputHandler { // xterm and urxvt // seem to spit this // out around ~370 times (?). - if (this._terminal.is('xterm')) { + if (this._is('xterm')) { this._coreService.triggerDataEvent(C0.ESC + '[>0;276;0c'); - } else if (this._terminal.is('rxvt-unicode')) { + } else if (this._is('rxvt-unicode')) { this._coreService.triggerDataEvent(C0.ESC + '[>85;95;0c'); - } else if (this._terminal.is('linux')) { + } else if (this._is('linux')) { // not supported by linux console. // linux console echoes parameters. this._coreService.triggerDataEvent(params.params[0] + 'c'); - } else if (this._terminal.is('screen')) { + } else if (this._is('screen')) { this._coreService.triggerDataEvent(C0.ESC + '[>83;40003;0c'); } } + /** + * Evaluate if the current terminal is the given argument. + * @param term The terminal name to evaluate + */ + private _is(term: string): boolean { + return (this._optionsService.options.termName + '').indexOf(term) === 0; + } + /** * CSI Pm h Set Mode (SM). * Ps = 2 -> Keyboard Action Mode (AM). @@ -1587,15 +1607,12 @@ export class InputHandler extends Disposable implements IInputHandler { * | 4 | Insert Mode (IRM). | #Y | * | 12 | Send/receive (SRM). Always off. | #N | * | 20 | Automatic Newline (LNM). Always off. | #N | - * - * - * FIXME: why is LNM commented out? */ public setMode(params: IParams): void { for (let i = 0; i < params.length; i++) { switch (params.params[i]) { case 4: - this._terminal.insertMode = true; + this._coreService.modes.insertMode = true; break; case 20: // this._t.convertEol = true; @@ -1736,7 +1753,7 @@ export class InputHandler extends Disposable implements IInputHandler { * through `options.windowsOptions`. */ if (this._optionsService.options.windowOptions.setWinLines) { - this._terminal.resize(132, this._bufferService.rows); + this._bufferService.resize(132, this._bufferService.rows); this._onRequestReset.fire(); } break; @@ -1753,7 +1770,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - this._terminal.viewport?.syncScrollArea(); + this._onRequestSyncScrollBar.fire(); break; case 9: // X10 Mouse // no release, no motion, no wheel, no modifiers. @@ -1774,7 +1791,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 1004: // send focusin/focusout events // focusin: ^[[I // focusout: ^[[O - this._terminal.sendFocus = true; + this._coreService.decPrivateModes.sendFocus = true; break; case 1005: // utf8 ext mode mouse - removed in #2507 this._logService.debug('DECSET 1005 not supported (see #2507)'); @@ -1797,12 +1814,12 @@ export class InputHandler extends Disposable implements IInputHandler { case 47: // alt screen buffer case 1047: // alt screen buffer this._bufferService.buffers.activateAltBuffer(this._eraseAttrData()); + this._coreService.isCursorInitialized = true; this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - this._terminal.viewport?.syncScrollArea(); - this._terminal.showCursor(); + this._onRequestSyncScrollBar.fire(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) - this._terminal.bracketedPasteMode = true; + this._coreService.decPrivateModes.bracketedPasteMode = true; break; } } @@ -1833,7 +1850,7 @@ export class InputHandler extends Disposable implements IInputHandler { for (let i = 0; i < params.length; i++) { switch (params.params[i]) { case 4: - this._terminal.insertMode = false; + this._coreService.modes.insertMode = false; break; case 20: // this._t.convertEol = false; @@ -1963,7 +1980,7 @@ export class InputHandler extends Disposable implements IInputHandler { * through `options.windowsOptions`. */ if (this._optionsService.options.windowOptions.setWinLines) { - this._terminal.resize(80, this._bufferService.rows); + this._bufferService.resize(80, this._bufferService.rows); this._onRequestReset.fire(); } break; @@ -1980,7 +1997,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - this._terminal.viewport?.syncScrollArea(); + this._onRequestSyncScrollBar.fire(); break; case 9: // X10 Mouse case 1000: // vt200 mouse @@ -1989,7 +2006,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._coreMouseService.activeProtocol = 'NONE'; break; case 1004: // send focusin/focusout events - this._terminal.sendFocus = false; + this._coreService.decPrivateModes.sendFocus = false; break; case 1005: // utf8 ext mode mouse - removed in #2507 this._logService.debug('DECRST 1005 not supported (see #2507)'); @@ -2015,12 +2032,12 @@ export class InputHandler extends Disposable implements IInputHandler { if (params.params[i] === 1049) { this.restoreCursor(); } + this._coreService.isCursorInitialized = true; this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - this._terminal.viewport?.syncScrollArea(); - this._terminal.showCursor(); + this._onRequestSyncScrollBar.fire(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) - this._terminal.bracketedPasteMode = false; + this._coreService.decPrivateModes.bracketedPasteMode = false; break; } } @@ -2046,7 +2063,7 @@ export class InputHandler extends Disposable implements IInputHandler { do { accu[advance + cSpace] = params.params[pos + advance]; if (params.hasSubParams(pos + advance)) { - const subparams = params.getSubParams(pos + advance); + const subparams = params.getSubParams(pos + advance)!; let i = 0; do { if (accu[1] === 5) { @@ -2356,8 +2373,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public softReset(params: IParams): void { this._coreService.isCursorHidden = false; - this._terminal.insertMode = false; - this._terminal.viewport?.syncScrollArea(); + this._onRequestSyncScrollBar.fire(); this._bufferService.buffer.scrollTop = 0; this._bufferService.buffer.scrollBottom = this._bufferService.rows - 1; this._curAttrData = DEFAULT_ATTR_DATA.clone(); @@ -2471,22 +2487,14 @@ export class InputHandler extends Disposable implements IInputHandler { return; } const second = (params.length > 1) ? params.params[1] : 0; - const rs = this._instantiationService.getService(IRenderService); switch (params.params[0]) { case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t - if (rs && second !== 2) { - console.log(rs.dimensions); - const w = rs.dimensions.scaledCanvasWidth.toFixed(0); - const h = rs.dimensions.scaledCanvasHeight.toFixed(0); - this._coreService.triggerDataEvent(`${C0.ESC}[4;${h};${w}t`); + if (second !== 2) { + this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_WIN_SIZE_PIXELS); } break; case 16: // GetCellSizePixels, returns CSI 6 ; height ; width t - if (rs) { - const w = rs.dimensions.scaledCellWidth.toFixed(0); - const h = rs.dimensions.scaledCellHeight.toFixed(0); - this._coreService.triggerDataEvent(`${C0.ESC}[6;${h};${w}t`); - } + this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_CELL_SIZE_PIXELS); break; case 18: // GetWinSizeChars, returns CSI 8 ; height ; width t if (this._bufferService) { @@ -2510,12 +2518,12 @@ export class InputHandler extends Disposable implements IInputHandler { case 23: // PopTitle if (second === 0 || second === 2) { if (this._windowTitleStack.length) { - this.setTitle(this._windowTitleStack.pop()); + this.setTitle(this._windowTitleStack.pop()!); } } if (second === 0 || second === 1) { if (this._iconNameStack.length) { - this.setIconName(this._iconNameStack.pop()); + this.setIconName(this._iconNameStack.pop()!); } } break; @@ -2573,7 +2581,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public setTitle(data: string): void { this._windowTitle = data; - this._terminal.handleTitle(data); + this._onTitleChange.fire(data); } /** @@ -2606,7 +2614,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadApplicationMode(): void { this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - this._terminal.viewport?.syncScrollArea(); + this._onRequestSyncScrollBar.fire(); } /** @@ -2617,7 +2625,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadNumericMode(): void { this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - this._terminal.viewport?.syncScrollArea(); + this._onRequestSyncScrollBar.fire(); } /** @@ -2674,7 +2682,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._terminal.scroll(this._eraseAttrData()); + this._onRequestScroll.fire(this._eraseAttrData()); } else if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; } @@ -2779,8 +2787,11 @@ export class InputHandler extends Disposable implements IInputHandler { this._setCursor(0, 0); for (let yOffset = 0; yOffset < this._bufferService.rows; ++yOffset) { const row = buffer.ybase + buffer.y + yOffset; - buffer.lines.get(row).fill(cell); - buffer.lines.get(row).isWrapped = false; + const line = buffer.lines.get(row); + if (line) { + line.fill(cell); + line.isWrapped = false; + } } this._dirtyRowService.markAllDirty(); this._setCursor(0, 0); diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index cfa1abd7..55e34084 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -9,13 +9,14 @@ import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { BufferSet } from 'common/buffer/BufferSet'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes } from 'common/Types'; import { UnicodeV6 } from 'common/input/UnicodeV6'; export class MockBufferService implements IBufferService { public serviceBrand: any; public get buffer(): IBuffer { return this.buffers.active; } public buffers: IBufferSet = {} as any; + public onResize: IEvent<{ cols: number, rows: number }> = new EventEmitter<{ cols: number, rows: number }>().event; constructor( public cols: number, public rows: number, @@ -31,6 +32,7 @@ export class MockBufferService implements IBufferService { } export class MockCoreMouseService implements ICoreMouseService { + public areMouseEventsActive: boolean = false; public activeEncoding: string = ''; public activeProtocol: string = ''; public addEncoding(name: string): void {} @@ -47,7 +49,6 @@ export class MockCharsetService implements ICharsetService { public serviceBrand: any; public charset: ICharset | undefined; public glevel: number = 0; - public charsets: ReadonlyArray = []; public reset(): void {} public setgLevel(g: number): void {} public setgCharset(g: number, charset: ICharset): void {} @@ -58,10 +59,15 @@ export class MockCoreService implements ICoreService { public isCursorInitialized: boolean = false; public isCursorHidden: boolean = false; public isFocused: boolean = false; + public modes: IModes = { + insertMode: false + }; public decPrivateModes: IDecPrivateModes = { applicationCursorKeys: false, applicationKeypad: false, + bracketedPasteMode: false, origin: false, + sendFocus: false, wraparound: true }; public onData: IEvent = new EventEmitter().event; diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index b250b45e..49a723b2 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -3,13 +3,23 @@ * @license MIT */ +import { ITerminalOptions as IPublicTerminalOptions } from 'xterm'; import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IDeleteEvent, IInsertEvent } from 'common/CircularList'; +import { IParams } from 'common/parser/Types'; export interface IDisposable { dispose(): void; } +// TODO: The options that are not in the public API should be reviewed +export interface ITerminalOptions extends IPublicTerminalOptions { + [key: string]: any; + cancelEvents?: boolean; + convertEol?: boolean; + termName?: string; +} + export type XtermListener = (...args: any[]) => void; /** @@ -150,11 +160,16 @@ export interface IMarker extends IDisposable { readonly isDisposed: boolean; readonly line: number; } +export interface IModes { + insertMode: boolean; +} export interface IDecPrivateModes { applicationCursorKeys: boolean; applicationKeypad: boolean; + bracketedPasteMode: boolean; origin: boolean; + sendFocus: boolean; wraparound: boolean; // defaults: xterm - true, vt100 - false } @@ -286,3 +301,88 @@ export interface IWindowOptions { popTitle?: boolean; setWinLines?: boolean; } + +/** + * Calls the parser and handles actions generated by the parser. + */ +export interface IInputHandler { + onTitleChange: IEvent; + onRequestScroll: IEvent; + + parse(data: string | Uint8Array): void; + print(data: Uint32Array, start: number, end: number): void; + + /** C0 BEL */ bell(): void; + /** C0 LF */ lineFeed(): void; + /** C0 CR */ carriageReturn(): void; + /** C0 BS */ backspace(): void; + /** C0 HT */ tab(): void; + /** C0 SO */ shiftOut(): void; + /** C0 SI */ shiftIn(): void; + + /** CSI @ */ insertChars(params: IParams): void; + /** CSI SP @ */ scrollLeft(params: IParams): void; + /** CSI A */ cursorUp(params: IParams): void; + /** CSI SP A */ scrollRight(params: IParams): void; + /** CSI B */ cursorDown(params: IParams): void; + /** CSI C */ cursorForward(params: IParams): void; + /** CSI D */ cursorBackward(params: IParams): void; + /** CSI E */ cursorNextLine(params: IParams): void; + /** CSI F */ cursorPrecedingLine(params: IParams): void; + /** CSI G */ cursorCharAbsolute(params: IParams): void; + /** CSI H */ cursorPosition(params: IParams): void; + /** CSI I */ cursorForwardTab(params: IParams): void; + /** CSI J */ eraseInDisplay(params: IParams): void; + /** CSI K */ eraseInLine(params: IParams): void; + /** CSI L */ insertLines(params: IParams): void; + /** CSI M */ deleteLines(params: IParams): void; + /** CSI P */ deleteChars(params: IParams): void; + /** CSI S */ scrollUp(params: IParams): void; + /** CSI T */ scrollDown(params: IParams, collect?: string): void; + /** CSI X */ eraseChars(params: IParams): void; + /** CSI Z */ cursorBackwardTab(params: IParams): void; + /** CSI ` */ charPosAbsolute(params: IParams): void; + /** CSI a */ hPositionRelative(params: IParams): void; + /** CSI b */ repeatPrecedingCharacter(params: IParams): void; + /** CSI c */ sendDeviceAttributesPrimary(params: IParams): void; + /** CSI > c */ sendDeviceAttributesSecondary(params: IParams): void; + /** CSI d */ linePosAbsolute(params: IParams): void; + /** CSI e */ vPositionRelative(params: IParams): void; + /** CSI f */ hVPosition(params: IParams): void; + /** CSI g */ tabClear(params: IParams): void; + /** CSI h */ setMode(params: IParams, collect?: string): void; + /** CSI l */ resetMode(params: IParams, collect?: string): void; + /** CSI m */ charAttributes(params: IParams): void; + /** CSI n */ deviceStatus(params: IParams, collect?: string): void; + /** CSI p */ softReset(params: IParams, collect?: string): void; + /** CSI q */ setCursorStyle(params: IParams, collect?: string): void; + /** CSI r */ setScrollRegion(params: IParams, collect?: string): void; + /** CSI s */ saveCursor(params: IParams): void; + /** CSI u */ restoreCursor(params: IParams): void; + /** CSI ' } */ insertColumns(params: IParams): void; + /** CSI ' ~ */ deleteColumns(params: IParams): void; + /** OSC 0 + OSC 2 */ setTitle(data: string): void; + /** ESC E */ nextLine(): void; + /** ESC = */ keypadApplicationMode(): void; + /** ESC > */ keypadNumericMode(): void; + /** ESC % G + ESC % @ */ selectDefaultCharset(): void; + /** ESC ( C + ESC ) C + ESC * C + ESC + C + ESC - C + ESC . C + ESC / C */ selectCharset(collectAndFlag: string): void; + /** ESC D */ index(): void; + /** ESC H */ tabSet(): void; + /** ESC M */ reverseIndex(): void; + /** ESC c */ fullReset(): void; + /** ESC n + ESC o + ESC | + ESC } + ESC ~ */ setgLevel(level: number): void; + /** ESC # 8 */ screenAlignmentPattern(): void; +} diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index 152bab7a..1de39dbd 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -36,7 +36,7 @@ export class Buffer implements IBuffer { public savedY: number = 0; public savedX: number = 0; public savedCurAttrData = DEFAULT_ATTR_DATA.clone(); - public savedCharset: ICharset | null = DEFAULT_CHARSET; + public savedCharset: ICharset | undefined = DEFAULT_CHARSET; public markers: Marker[] = []; private _nullCell: ICellData = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); private _whitespaceCell: ICellData = CellData.fromCharData([0, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE]); diff --git a/src/common/buffer/Types.d.ts b/src/common/buffer/Types.d.ts index e229d69c..c271f33c 100644 --- a/src/common/buffer/Types.d.ts +++ b/src/common/buffer/Types.d.ts @@ -31,7 +31,7 @@ export interface IBuffer { hasScrollback: boolean; savedY: number; savedX: number; - savedCharset: ICharset | null; + savedCharset: ICharset | undefined; savedCurAttrData: IAttributeData; isCursorInViewport: boolean; markers: IMarker[]; diff --git a/src/common/data/Charsets.ts b/src/common/data/Charsets.ts index 01087d8b..c72d5a23 100644 --- a/src/common/data/Charsets.ts +++ b/src/common/data/Charsets.ts @@ -10,12 +10,12 @@ import { ICharset } from 'common/Types'; * to be represented within the terminal with only 8-bit encoding. See ISO 2022 * for a discussion on character sets. Only VT100 character sets are supported. */ -export const CHARSETS: { [key: string]: ICharset | null } = {}; +export const CHARSETS: { [key: string]: ICharset | undefined } = {}; /** * The default character set, US. */ -export const DEFAULT_CHARSET: ICharset | null = CHARSETS['B']; +export const DEFAULT_CHARSET: ICharset | undefined = CHARSETS['B']; /** * DEC Special Character and Line Drawing Set. @@ -74,7 +74,7 @@ CHARSETS['A'] = { * United States character set * ESC (B */ -CHARSETS['B'] = null; +CHARSETS['B'] = undefined; /** * Dutch character set diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 1b6da792..d62d2cb0 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -6,6 +6,7 @@ import { IBufferService, IOptionsService } from 'common/services/Services'; import { BufferSet } from 'common/buffer/BufferSet'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; export const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars export const MINIMUM_ROWS = 1; @@ -17,6 +18,9 @@ export class BufferService implements IBufferService { public rows: number; public buffers: IBufferSet; + private _onResize = new EventEmitter<{ cols: number, rows: number }>(); + public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } + public get buffer(): IBuffer { return this.buffers.active; } constructor( @@ -30,6 +34,9 @@ export class BufferService implements IBufferService { public resize(cols: number, rows: number): void { this.cols = cols; this.rows = rows; + this.buffers.resize(cols, rows); + this.buffers.setupTabStops(this.cols); + this._onResize.fire({ cols, rows }); } public reset(): void { diff --git a/src/common/services/CharsetService.ts b/src/common/services/CharsetService.ts index 47a015f6..c5381065 100644 --- a/src/common/services/CharsetService.ts +++ b/src/common/services/CharsetService.ts @@ -10,22 +10,23 @@ export class CharsetService implements ICharsetService { public serviceBrand: any; public charset: ICharset | undefined; - public charsets: ICharset[] = []; public glevel: number = 0; + private _charsets: (ICharset | undefined)[] = []; + public reset(): void { this.charset = undefined; - this.charsets = []; + this._charsets = []; this.glevel = 0; } public setgLevel(g: number): void { this.glevel = g; - this.charset = this.charsets[g]; + this.charset = this._charsets[g]; } - public setgCharset(g: number, charset: ICharset): void { - this.charsets[g] = charset; + public setgCharset(g: number, charset: ICharset | undefined): void { + this._charsets[g] = charset; if (this.glevel === g) { this.charset = charset; } diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index c0ed3c6a..29a84761 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -192,6 +192,10 @@ export class CoreMouseService implements ICoreMouseService { return this._activeProtocol; } + public get areMouseEventsActive(): boolean { + return this._protocols[this._activeProtocol].events !== 0; + } + public set activeProtocol(name: string) { if (!this._protocols[name]) { throw new Error(`unknown protocol "${name}"`); diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 2f0ff72f..55d425aa 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -5,13 +5,19 @@ import { ICoreService, ILogService, IOptionsService, IBufferService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { IDecPrivateModes } from 'common/Types'; +import { IDecPrivateModes, IModes } from 'common/Types'; import { clone } from 'common/Clone'; +const DEFAULT_MODES: IModes = Object.freeze({ + insertMode: false +}); + const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({ applicationCursorKeys: false, applicationKeypad: false, + bracketedPasteMode: false, origin: false, + sendFocus: false, wraparound: true // defaults: xterm - true, vt100 - false }); @@ -20,6 +26,7 @@ export class CoreService implements ICoreService { public isCursorInitialized: boolean = false; public isCursorHidden: boolean = false; + public modes: IModes; public decPrivateModes: IDecPrivateModes; private _onData = new EventEmitter(); @@ -36,10 +43,12 @@ export class CoreService implements ICoreService { @ILogService private readonly _logService: ILogService, @IOptionsService private readonly _optionsService: IOptionsService ) { + this.modes = clone(DEFAULT_MODES); this.decPrivateModes = clone(DEFAULT_DEC_PRIVATE_MODES); } public reset(): void { + this.modes = clone(DEFAULT_MODES); this.decPrivateModes = clone(DEFAULT_DEC_PRIVATE_MODES); } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 8a3e9b28..6829d7b9 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -5,19 +5,19 @@ import { IEvent } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; export const IBufferService = createDecorator('BufferService'); export interface IBufferService { - serviceBrand: any; + serviceBrand: undefined; readonly cols: number; readonly rows: number; readonly buffer: IBuffer; readonly buffers: IBufferSet; - // TODO: Move resize event here + onResize: IEvent<{ cols: number, rows: number }>; resize(cols: number, rows: number): void; reset(): void; @@ -27,6 +27,7 @@ export const ICoreMouseService = createDecorator('CoreMouseSe export interface ICoreMouseService { activeProtocol: string; activeEncoding: string; + areMouseEventsActive: boolean; addProtocol(name: string, protocol: ICoreMouseProtocol): void; addEncoding(name: string, encoding: CoreMouseEncoding): void; reset(): void; @@ -51,12 +52,12 @@ export interface ICoreMouseService { /** * Human readable version of mouse events. */ - explainEvents(events: CoreMouseEventType): {[event: string]: boolean}; + explainEvents(events: CoreMouseEventType): { [event: string]: boolean }; } export const ICoreService = createDecorator('CoreService'); export interface ICoreService { - serviceBrand: any; + serviceBrand: undefined; /** * Initially the cursor will not be visible until the first time the terminal @@ -65,6 +66,7 @@ export interface ICoreService { isCursorInitialized: boolean; isCursorHidden: boolean; + readonly modes: IModes; readonly decPrivateModes: IDecPrivateModes; readonly onData: IEvent; @@ -92,11 +94,10 @@ export interface ICoreService { export const ICharsetService = createDecorator('CharsetService'); export interface ICharsetService { - serviceBrand: any; + serviceBrand: undefined; charset: ICharset | undefined; readonly glevel: number; - readonly charsets: ReadonlyArray; reset(): void; @@ -111,12 +112,12 @@ export interface ICharsetService { * @param g * @param charset */ - setgCharset(g: number, charset: ICharset): void; + setgCharset(g: number, charset: ICharset | undefined): void; } export const IDirtyRowService = createDecorator('DirtyRowService'); export interface IDirtyRowService { - serviceBrand: any; + serviceBrand: undefined; readonly start: number; readonly end: number; @@ -132,61 +133,32 @@ export interface IServiceIdentifier { type: T; } -export interface IConstructorSignature0 { - new(...services: { serviceBrand: any }[]): T; -} +export interface IBrandedService { + serviceBrand: undefined; +}; -export interface IConstructorSignature1 { - new(first: A1, ...services: { serviceBrand: any }[]): T; -} - -export interface IConstructorSignature2 { - new(first: A1, second: A2, ...services: { serviceBrand: any }[]): T; -} - -export interface IConstructorSignature3 { - new(first: A1, second: A2, third: A3, ...services: { serviceBrand: any }[]): T; -} - -export interface IConstructorSignature4 { - new(first: A1, second: A2, third: A3, fourth: A4, ...services: { serviceBrand: any }[]): T; -} - -export interface IConstructorSignature5 { - new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, ...services: { serviceBrand: any }[]): T; -} - -export interface IConstructorSignature6 { - new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, ...services: { serviceBrand: any }[]): T; -} - -export interface IConstructorSignature7 { - new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, ...services: { serviceBrand: any }[]): T; -} - -export interface IConstructorSignature8 { - new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, eigth: A8, ...services: { serviceBrand: any }[]): T; -} +type GetLeadingNonServiceArgs = + Args extends [...IBrandedService[]] ? [] + : Args extends [infer A1, ...IBrandedService[]] ? [A1] + : Args extends [infer A1, infer A2, ...IBrandedService[]] ? [A1, A2] + : Args extends [infer A1, infer A2, infer A3, ...IBrandedService[]] ? [A1, A2, A3] + : Args extends [infer A1, infer A2, infer A3, infer A4, ...IBrandedService[]] ? [A1, A2, A3, A4] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, ...IBrandedService[]] ? [A1, A2, A3, A4, A5] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, infer A6, ...IBrandedService[]] ? [A1, A2, A3, A4, A5, A6] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, infer A6, infer A7, ...IBrandedService[]] ? [A1, A2, A3, A4, A5, A6, A7] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, infer A6, infer A7, infer A8, ...IBrandedService[]] ? [A1, A2, A3, A4, A5, A6, A7, A8] + : never; export const IInstantiationService = createDecorator('InstantiationService'); export interface IInstantiationService { setService(id: IServiceIdentifier, instance: T): void; getService(id: IServiceIdentifier): T | undefined; - - createInstance(ctor: IConstructorSignature0): T; - createInstance(ctor: IConstructorSignature1, first: A1): T; - createInstance(ctor: IConstructorSignature2, first: A1, second: A2): T; - createInstance(ctor: IConstructorSignature3, first: A1, second: A2, third: A3): T; - createInstance(ctor: IConstructorSignature4, first: A1, second: A2, third: A3, fourth: A4): T; - createInstance(ctor: IConstructorSignature5, first: A1, second: A2, third: A3, fourth: A4, fifth: A5): T; - createInstance(ctor: IConstructorSignature6, first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6): T; - createInstance(ctor: IConstructorSignature7, first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7): T; - createInstance(ctor: IConstructorSignature8, first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, eigth: A8): T; + createInstance any, R extends InstanceType>(t: Ctor, ...args: GetLeadingNonServiceArgs>): R; } export const ILogService = createDecorator('LogService'); export interface ILogService { - serviceBrand: any; + serviceBrand: undefined; debug(message: any, ...optionalParams: any[]): void; info(message: any, ...optionalParams: any[]): void; @@ -196,7 +168,7 @@ export interface ILogService { export const IOptionsService = createDecorator('OptionsService'); export interface IOptionsService { - serviceBrand: any; + serviceBrand: undefined; readonly options: ITerminalOptions; @@ -311,7 +283,7 @@ export interface ITheme { export const IUnicodeService = createDecorator('UnicodeService'); export interface IUnicodeService { - serviceBrand: any; + serviceBrand: undefined; /** Register an Unicode version provider. */ register(provider: IUnicodeVersionProvider): void; /** Registered Unicode versions. */ diff --git a/src/common/tsconfig.json b/src/common/tsconfig.json index 59050a0b..7f35c80c 100644 --- a/src/common/tsconfig.json +++ b/src/common/tsconfig.json @@ -10,5 +10,8 @@ ], "baseUrl": ".." }, - "include": [ "./**/*" ] + "include": [ + "./**/*", + "../../typings/xterm.d.ts" + ] } diff --git a/src/tsconfig-base.json b/src/tsconfig-base.json index 84d0c924..0cd951a7 100644 --- a/src/tsconfig-base.json +++ b/src/tsconfig-base.json @@ -8,6 +8,7 @@ "removeComments": true, "pretty": true, - "incremental": true + "incremental": true, + "experimentalDecorators": true } } diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 6829fd0a..75e00152 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -539,7 +539,7 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'norm '); assert.equal(await page.evaluate(`window.term.buffer.normal.getLine(0).translateToString()`), 'norm '); assert.equal(await page.evaluate(`window.term.buffer.alternate.getLine(0)`), undefined); - }) + }); }); it('dispose', async () => { diff --git a/test/benchmark/EscapeSequenceParser.benchmark.ts b/test/benchmark/EscapeSequenceParser.benchmark.ts index 68247715..e4591dde 100644 --- a/test/benchmark/EscapeSequenceParser.benchmark.ts +++ b/test/benchmark/EscapeSequenceParser.benchmark.ts @@ -19,9 +19,9 @@ function toUtf32(s: string): Uint32Array { } class DcsHandler implements IDcsHandler { - hook(params: IParams): void {} - put(data: Uint32Array, start: number, end: number): void {} - unhook(): void {} + public hook(params: IParams): void {} + public put(data: Uint32Array, start: number, end: number): void {} + public unhook(): void {} }