diff --git a/.eslintrc.json b/.eslintrc.json index 390e2c54..6031c195 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -47,6 +47,7 @@ "readonly": "generic" } ], + "@typescript-eslint/consistent-type-assertions": "warn", "@typescript-eslint/consistent-type-definitions": "warn", "@typescript-eslint/explicit-function-return-type": [ "warn", diff --git a/README.md b/README.md index b1f25995..0ac211ed 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,6 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**cPanel & WHM**](https://cpanel.com): The hosting platform of choice. - [**Nutanix**](https://github.com/nutanix): Nutanix Enterprise Cloud uses xterm in the webssh functionality within Nutanix Calm, and is also looking to move our old noserial (termjs) functionality to xterm.js. - [**SSH Web Client**](https://github.com/roke22/PHP-SSH2-Web-Client): SSH Web Client with PHP. -- [**Shellvault**](https://www.shellvault.io): The cloud-based SSH terminal you can access from anywhere. - [**Juno**](http://junolab.org/): A flexible Julia IDE, based on Atom. - [**webssh**](https://github.com/huashengdun/webssh): Web based ssh client. - [**info-beamer hosted**](https://info-beamer.com): Uses xterm.js to manage digital signage devices from the web dashboard. diff --git a/addons/xterm-addon-attach/src/AttachAddon.ts b/addons/xterm-addon-attach/src/AttachAddon.ts index 035807ae..9fbd796b 100644 --- a/addons/xterm-addon-attach/src/AttachAddon.ts +++ b/addons/xterm-addon-attach/src/AttachAddon.ts @@ -20,7 +20,7 @@ export class AttachAddon implements ITerminalAddon { this._socket = socket; // always set binary type to arraybuffer, we do not handle blobs this._socket.binaryType = 'arraybuffer'; - this._bidirectional = (options && options.bidirectional === false) ? false : true; + this._bidirectional = !(options && options.bidirectional === false); } public activate(terminal: Terminal): void { diff --git a/addons/xterm-addon-ligatures/package.json b/addons/xterm-addon-ligatures/package.json index 64d8eed7..ecbcc006 100644 --- a/addons/xterm-addon-ligatures/package.json +++ b/addons/xterm-addon-ligatures/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-ligatures", - "version": "0.5.0", + "version": "0.5.1", "description": "Add support for programming ligatures to xterm.js", "author": { "name": "The xterm.js authors", diff --git a/addons/xterm-addon-ligatures/src/font.ts b/addons/xterm-addon-ligatures/src/font.ts index 72872a0b..abdbbc04 100644 --- a/addons/xterm-addon-ligatures/src/font.ts +++ b/addons/xterm-addon-ligatures/src/font.ts @@ -37,7 +37,7 @@ export default async function load(fontFamily: string, cacheSize: number): Promi // Web environment that supports font access API if (typeof navigator !== 'undefined' && 'fonts' in navigator) { try { - const status = await (navigator as IFontAccessNavigator).permissions.request?.({ + const status = await (navigator as unknown as IFontAccessNavigator).permissions.request?.({ name: 'local-fonts' }); if (status && status.state !== 'granted') { @@ -53,7 +53,7 @@ export default async function load(fontFamily: string, cacheSize: number): Promi } const fonts: Record = {}; try { - const fontsIterator = await (navigator as IFontAccessNavigator).fonts.query(); + const fontsIterator = await (navigator as unknown as IFontAccessNavigator).fonts.query(); for (const metadata of fontsIterator) { if (!fonts.hasOwnProperty(metadata.family)) { fonts[metadata.family] = []; diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 64e89bb3..4a73d998 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -394,7 +394,7 @@ export class SearchAddon implements ITerminalAddon { // If it is not in the viewport then we scroll else it just gets selected if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) { let scroll = result.row - terminal.buffer.active.viewportY; - scroll = scroll - Math.floor(terminal.rows / 2); + scroll -= Math.floor(terminal.rows / 2); terminal.scrollLines(scroll); } return true; diff --git a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts index a7b3b816..c46b3815 100644 --- a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts +++ b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts @@ -487,9 +487,9 @@ function newArray(initial: T | ((index: number) => T), count: number): T[] { const array: T[] = new Array(count); for (let i = 0; i < array.length; i++) { if (typeof initial === 'function') { - array[i] = (<(index: number) => T>initial)(i); + array[i] = (initial as (index: number) => T)(i); } else { - array[i] = initial; + array[i] = initial as T; } } return array; diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 8be4c011..b09fe540 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -263,7 +263,7 @@ export class GlyphRenderer { // Get attributes from fg (excluding inverse) and resolve inverse by pullibng rgb colors // from bg. This is needed since the inverse fg color should be based on the original bg // color, not on the selection color - fg = (fg & ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE)); + fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE); switch (workCell.getBgColorMode()) { case Attributes.CM_P16: case Attributes.CM_P256: diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 91fa7968..ad2393d8 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -24,9 +24,9 @@ export class WebglAddon implements ITerminalAddon { throw new Error('Cannot activate WebglAddon before Terminal.open'); } this._terminal = terminal; - const renderService: IRenderService = (terminal)._core._renderService; - const characterJoinerService: ICharacterJoinerService = (terminal)._core._characterJoinerService; - const colors: IColorSet = (terminal)._core._colorManager.colors; + const renderService: IRenderService = (terminal as any)._core._renderService; + const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService; + const colors: IColorSet = (terminal as any)._core._colorManager.colors; this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, this._preserveDrawingBuffer); this._renderer.onContextLoss(() => this._onContextLoss.fire()); renderService.setRenderer(this._renderer); diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 3d25e5b0..9b75d1de 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -342,7 +342,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Flag combined chars with a bit mask so they're easily identifiable if (chars.length > 1) { - code = code | COMBINED_CHAR_BIT_MASK; + code |= COMBINED_CHAR_BIT_MASK; } // Cache the results in the model diff --git a/package.json b/package.json index 1b290c6e..bab93bf4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xterm", "description": "Full xterm terminal, in your browser", - "version": "4.12.0", + "version": "4.13.0", "main": "lib/xterm.js", "style": "css/xterm.css", "types": "typings/xterm.d.ts", diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index e5cbb372..c1ffc39a 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -112,7 +112,7 @@ export class AccessibilityManager extends Disposable { } private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void { - const boundaryElement = e.target; + const boundaryElement = e.target as HTMLElement; const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2]; // Don't scroll if the buffer top has reached the end in that direction diff --git a/src/browser/ColorManager.test.ts b/src/browser/ColorManager.test.ts index 766f4c12..926a7df3 100644 --- a/src/browser/ColorManager.test.ts +++ b/src/browser/ColorManager.test.ts @@ -17,7 +17,7 @@ describe('ColorManager', () => { dom = new jsdom.JSDOM(''); window = dom.window; document = window.document; - (window).HTMLCanvasElement.prototype.getContext = () => ({ + (window as any).HTMLCanvasElement.prototype.getContext = () => ({ createLinearGradient(): any { return null; }, @@ -36,7 +36,7 @@ describe('ColorManager', () => { for (const key of Object.keys(cm.colors)) { if (key !== 'ansi' && key !== 'contrastCache') { // A #rrggbb or rgba(...) - assert.ok((cm.colors)[key].css.length >= 7); + assert.ok((cm.colors as any)[key].css.length >= 7); } } assert.equal(cm.colors.ansi.length, 256); diff --git a/src/browser/Linkifier.test.ts b/src/browser/Linkifier.test.ts index 2567a669..a07f69ed 100644 --- a/src/browser/Linkifier.test.ts +++ b/src/browser/Linkifier.test.ts @@ -174,7 +174,7 @@ describe('Linkifier', () => { assert.equal(mouseZoneManager.zones[0].y1, 1); assert.equal(mouseZoneManager.zones[0].y2, 1); // Fires done() - mouseZoneManager.zones[0].clickCallback({}); + mouseZoneManager.zones[0].clickCallback({} as any); } }); linkifier.linkifyRows(); @@ -210,7 +210,7 @@ describe('Linkifier', () => { let count = 0; linkifier.registerLinkMatcher(/test/, () => assert.fail(), { validationCallback: (url, cb) => { - count += 1; + count++; if (count === 2) { done(); } diff --git a/src/browser/Linkifier.ts b/src/browser/Linkifier.ts index 6d25e730..b17d66a8 100644 --- a/src/browser/Linkifier.ts +++ b/src/browser/Linkifier.ts @@ -89,7 +89,9 @@ export class Linkifier implements ILinkifier { if (this._rowsTimeoutId) { clearTimeout(this._rowsTimeoutId); } - this._rowsTimeoutId = setTimeout(() => this._linkifyRows(), Linkifier._timeBeforeLatency); + + // Cannot use window.setTimeout since tests need to run in node + this._rowsTimeoutId = setTimeout(() => this._linkifyRows(), Linkifier._timeBeforeLatency) as any as number; } /** diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index 140ec6e5..84e6a87e 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -29,10 +29,10 @@ describe('Terminal', () => { beforeEach(() => { term = new TestTerminal(termOptions); term.refresh = () => { }; - (term).renderer = new MockRenderer(); + (term as any).renderer = new MockRenderer(); term.viewport = new MockViewport(); - (term)._compositionHelper = new MockCompositionHelper(); - (term).element = { + (term as any)._compositionHelper = new MockCompositionHelper(); + (term as any).element = { classList: { toggle: () => { }, remove: () => { } @@ -86,12 +86,12 @@ describe('Terminal', () => { assert.equal(e.domEvent instanceof Object, true); done(); }); - const evKeyPress = { + const evKeyPress = { preventDefault: () => { }, stopPropagation: () => { }, type: 'keypress', keyCode: 13 - }; + } as KeyboardEvent; term.keyPress(evKeyPress); }); it('should fire a key event after a keydown DOM event', (done) => { @@ -100,13 +100,13 @@ describe('Terminal', () => { assert.equal(e.domEvent instanceof Object, true); done(); }); - (term).textarea = { value: '' }; - const evKeyDown = { + (term as any).textarea = { value: '' }; + const evKeyDown = { preventDefault: () => { }, stopPropagation: () => { }, type: 'keydown', keyCode: 13 - }; + } as KeyboardEvent; term.keyDown(evKeyDown); }); it('should fire the onResize event', (done) => { @@ -140,18 +140,18 @@ describe('Terminal', () => { }); describe('attachCustomKeyEventHandler', () => { - const evKeyDown = { + const evKeyDown = { preventDefault: () => { }, stopPropagation: () => { }, type: 'keydown', keyCode: 77 - }; - const evKeyPress = { + } as KeyboardEvent; + const evKeyPress = { preventDefault: () => { }, stopPropagation: () => { }, type: 'keypress', keyCode: 77 - }; + } as KeyboardEvent; beforeEach(() => { term.clearSelection = () => { }; @@ -374,13 +374,13 @@ describe('Terminal', () => { describe('keyPress', () => { it('should scroll down, when a key is pressed and terminal is scrolled up', () => { - const event = { + const event = { type: 'keydown', key: 'a', keyCode: 65, preventDefault: () => { }, stopPropagation: () => { } - }; + } as KeyboardEvent; term.buffer.ydisp = 0; term.buffer.ybase = 40; @@ -403,7 +403,7 @@ describe('Terminal', () => { assert.equal(term.buffer.ydisp, startYDisp); term.scrollLines(-1); assert.equal(term.buffer.ydisp, startYDisp - 1); - term.keyPress({ keyCode: 0 }); + term.keyPress({ keyCode: 0 }); assert.equal(term.buffer.ydisp, startYDisp - 1); }); }); diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 9d3838a6..5aed701a 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -72,7 +72,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // private _visualBellTimer: number; - public browser: IBrowser = Browser; + public browser: IBrowser = Browser as any; // TODO: We should remove options once components adopt optionsService public get options(): IInitializedTerminalOptions { return this.optionsService.options; } @@ -601,7 +601,7 @@ export class Terminal extends CoreTerminal implements ITerminal { let but: CoreMouseButton; let action: CoreMouseAction | undefined; - switch ((ev).overrideType || ev.type) { + switch ((ev as any).overrideType || ev.type) { case 'mousemove': action = CoreMouseAction.MOVE; if (ev.buttons === undefined) { diff --git a/src/browser/Terminal2.test.ts b/src/browser/Terminal2.test.ts index 75832e93..4965e8f3 100644 --- a/src/browser/Terminal2.test.ts +++ b/src/browser/Terminal2.test.ts @@ -17,6 +17,7 @@ const ROWS = 25; const TESTFILES = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '../..')}); const SKIP_FILES = [ + 't0055-EL.in', // EL/ED handle cursor at cols differently (see #3362) 't0084-CBT.in', 't0101-NLM.in', 't0103-reverse_wrap.in', // not comparable, we deviate from xterm reverse wrap on purpose @@ -105,7 +106,7 @@ function formatError(input: string, output: string, expected: string): string { function addLineNumber(start: number, color: string): (s: string) => string { let counter = start || 0; return (s: string): string => { - counter += 1; + counter++; return '\x1b[33m' + (' ' + counter).slice(-2) + color + s; }; } diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index f9ad2a7d..9d5373c8 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -153,7 +153,7 @@ export class MockTerminal implements ITerminal { public textarea!: HTMLTextAreaElement; public rows!: number; public cols!: number; - public browser: IBrowser = Browser; + public browser: IBrowser = Browser as any; public writeBuffer!: string[]; public children!: HTMLElement[]; public cursorHidden!: boolean; diff --git a/src/browser/input/CompositionHelper.test.ts b/src/browser/input/CompositionHelper.test.ts index c722570b..e29111e1 100644 --- a/src/browser/input/CompositionHelper.test.ts +++ b/src/browser/input/CompositionHelper.test.ts @@ -49,7 +49,7 @@ describe('CompositionHelper', () => { it('Should insert simple characters', (done) => { // First character 'ㅇ' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'ㅇ' }); + compositionHelper.compositionupdate({ data: 'ㅇ' }); textarea.value = 'ㅇ'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -57,7 +57,7 @@ describe('CompositionHelper', () => { assert.equal(handledText, 'ㅇ'); // Second character 'ㅇ' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'ㅇ' }); + compositionHelper.compositionupdate({ data: 'ㅇ' }); textarea.value = 'ㅇㅇ'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -73,13 +73,13 @@ describe('CompositionHelper', () => { it('Should insert complex characters', (done) => { // First character '앙' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'ㅇ' }); + compositionHelper.compositionupdate({ data: 'ㅇ' }); textarea.value = 'ㅇ'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: '아' }); + compositionHelper.compositionupdate({ data: '아' }); textarea.value = '아'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: '앙' }); + compositionHelper.compositionupdate({ data: '앙' }); textarea.value = '앙'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -87,13 +87,13 @@ describe('CompositionHelper', () => { assert.equal(handledText, '앙'); // Second character '앙' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'ㅇ' }); + compositionHelper.compositionupdate({ data: 'ㅇ' }); textarea.value = '앙ㅇ'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: '아' }); + compositionHelper.compositionupdate({ data: '아' }); textarea.value = '앙아'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: '앙' }); + compositionHelper.compositionupdate({ data: '앙' }); textarea.value = '앙앙'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -113,19 +113,19 @@ describe('CompositionHelper', () => { it('Should insert complex characters that change with following character', (done) => { // First character '아' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'ㅇ' }); + compositionHelper.compositionupdate({ data: 'ㅇ' }); textarea.value = 'ㅇ'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: '아' }); + compositionHelper.compositionupdate({ data: '아' }); textarea.value = '아'; setTimeout(() => { // wait for any textarea updates // Start second character '아' in first character - compositionHelper.compositionupdate({ data: '앙' }); + compositionHelper.compositionupdate({ data: '앙' }); textarea.value = '앙'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: '아' }); + compositionHelper.compositionupdate({ data: '아' }); textarea.value = '아아'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -142,14 +142,14 @@ describe('CompositionHelper', () => { it('Should insert multi-characters compositions', (done) => { // First character 'だ' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'd' }); + compositionHelper.compositionupdate({ data: 'd' }); textarea.value = 'd'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: 'だ' }); + compositionHelper.compositionupdate({ data: 'だ' }); textarea.value = 'だ'; setTimeout(() => { // wait for any textarea updates // Second character 'あ' - compositionHelper.compositionupdate({ data: 'だあ' }); + compositionHelper.compositionupdate({ data: 'だあ' }); textarea.value = 'だあ'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -165,18 +165,18 @@ describe('CompositionHelper', () => { it('Should insert multi-character compositions that are converted to other characters with the same length', (done) => { // First character 'だ' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'd' }); + compositionHelper.compositionupdate({ data: 'd' }); textarea.value = 'd'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: 'だ' }); + compositionHelper.compositionupdate({ data: 'だ' }); textarea.value = 'だ'; setTimeout(() => { // wait for any textarea updates // Second character 'ー' - compositionHelper.compositionupdate({ data: 'だー' }); + compositionHelper.compositionupdate({ data: 'だー' }); textarea.value = 'だー'; setTimeout(() => { // wait for any textarea updates // Convert to katakana 'ダー' - compositionHelper.compositionupdate({ data: 'ダー' }); + compositionHelper.compositionupdate({ data: 'ダー' }); textarea.value = 'ダー'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -193,18 +193,18 @@ describe('CompositionHelper', () => { it('Should insert multi-character compositions that are converted to other characters with different lengths', (done) => { // First character 'い' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'い' }); + compositionHelper.compositionupdate({ data: 'い' }); textarea.value = 'い'; setTimeout(() => { // wait for any textarea updates // Second character 'ま' - compositionHelper.compositionupdate({ data: 'いm' }); + compositionHelper.compositionupdate({ data: 'いm' }); textarea.value = 'いm'; setTimeout(() => { // wait for any textarea updates - compositionHelper.compositionupdate({ data: 'いま' }); + compositionHelper.compositionupdate({ data: 'いま' }); textarea.value = 'いま'; setTimeout(() => { // wait for any textarea updates // Convert to kanji '今' - compositionHelper.compositionupdate({ data: '今' }); + compositionHelper.compositionupdate({ data: '今' }); textarea.value = '今'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); @@ -221,7 +221,7 @@ describe('CompositionHelper', () => { it('Should insert non-composition characters input immediately after composition characters', (done) => { // First character 'ㅇ' compositionHelper.compositionstart(); - compositionHelper.compositionupdate({ data: 'ㅇ' }); + compositionHelper.compositionupdate({ data: 'ㅇ' }); textarea.value = 'ㅇ'; setTimeout(() => { // wait for any textarea updates compositionHelper.compositionend(); diff --git a/src/browser/input/CompositionHelper.ts b/src/browser/input/CompositionHelper.ts index 8a204831..4e176725 100644 --- a/src/browser/input/CompositionHelper.ts +++ b/src/browser/input/CompositionHelper.ts @@ -69,7 +69,7 @@ export class CompositionHelper { * Handles the compositionupdate event, updating the composition view. * @param ev The event. */ - public compositionupdate(ev: CompositionEvent): void { + public compositionupdate(ev: Pick): void { this._compositionView.textContent = ev.data; this.updateCompositionElements(); setTimeout(() => { diff --git a/src/browser/public/AddonManager.ts b/src/browser/public/AddonManager.ts index 0261fd68..06c78121 100644 --- a/src/browser/public/AddonManager.ts +++ b/src/browser/public/AddonManager.ts @@ -31,7 +31,7 @@ export class AddonManager implements IDisposable { }; this._addons.push(loadedAddon); instance.dispose = () => this._wrappedAddonDispose(loadedAddon); - instance.activate(terminal); + instance.activate(terminal as any); } private _wrappedAddonDispose(loadedAddon: ILoadedAddon): void { diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 14606454..90153a6e 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -280,7 +280,7 @@ class BufferLineApiView implements IBufferLineApi { } if (cell) { - this._line.loadCell(x, cell); + this._line.loadCell(x, cell as ICellData); return cell; } return this._line.loadCell(x, new CellData()); diff --git a/src/browser/renderer/atlas/CharAtlasUtils.ts b/src/browser/renderer/atlas/CharAtlasUtils.ts index 20695d3c..b196b373 100644 --- a/src/browser/renderer/atlas/CharAtlasUtils.ts +++ b/src/browser/renderer/atlas/CharAtlasUtils.ts @@ -10,7 +10,7 @@ import { ITerminalOptions } from 'common/services/Services'; export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, options: ITerminalOptions, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter - const clonedColors = { + const clonedColors: IPartialColorSet = { foreground: colors.foreground, background: colors.background, cursor: undefined, diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index dccdb877..d08cf987 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -387,7 +387,7 @@ export class DomRenderer extends Disposable implements IRenderer { if (!row) { return; } - const span = row.children[x]; + const span = row.children[x] as HTMLElement; if (span) { span.style.textDecoration = enabled ? 'underline' : 'none'; } diff --git a/src/browser/services/CharacterJoinerService.test.ts b/src/browser/services/CharacterJoinerService.test.ts index 94abc4d5..6b5326d9 100644 --- a/src/browser/services/CharacterJoinerService.test.ts +++ b/src/browser/services/CharacterJoinerService.test.ts @@ -270,7 +270,7 @@ function lineData(data: IPartialLineData[]): IBufferLine { const tline = new BufferLine(0); for (let i = 0; i < data.length; ++i) { const line = data[i][0]; - const attr = (data[i][1] || 0); + const attr = (data[i][1] || 0) as number; const offset = tline.length; tline.resize(tline.length + line.split('').length, CellData.fromCharData([0, '', 0, 0])); line.split('').map((char, idx) => tline.setCell(idx + offset, CellData.fromCharData([attr, char, 1, char.charCodeAt(0)]))); diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 8e3b8809..c0547755 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -133,8 +133,8 @@ export class SelectionService extends Disposable implements ISelectionService { super(); // Init listeners - this._mouseMoveListener = event => this._onMouseMove(event); - this._mouseUpListener = event => this._onMouseUp(event); + this._mouseMoveListener = event => this._onMouseMove(event as MouseEvent); + this._mouseUpListener = event => this._onMouseUp(event as MouseEvent); this._coreService.onUserInput(() => { if (this.hasSelection) { this.clearSelection(); diff --git a/src/browser/services/SoundService.ts b/src/browser/services/SoundService.ts index 8d940c13..3880b42d 100644 --- a/src/browser/services/SoundService.ts +++ b/src/browser/services/SoundService.ts @@ -13,7 +13,7 @@ export class SoundService implements ISoundService { public static get audioContext(): AudioContext | null { if (!SoundService._audioContext) { - const audioContextCtor: typeof AudioContext = (window).AudioContext || (window).webkitAudioContext; + const audioContextCtor: typeof AudioContext = (window as any).AudioContext || (window as any).webkitAudioContext; if (!audioContextCtor) { console.warn('Web Audio API is not supported by this browser. Consider upgrading to the latest version'); return null; diff --git a/src/common/Clone.ts b/src/common/Clone.ts index 51c5abaa..37821fe0 100644 --- a/src/common/Clone.ts +++ b/src/common/Clone.ts @@ -16,7 +16,7 @@ export function clone(val: T, depth: number = 5): T { for (const key in val) { // Recursively clone eack item unless we're at the maximum depth - clonedObject[key] = depth <= 1 ? val[key] : (val[key] ? clone(val[key], depth - 1) : val[key]); + clonedObject[key] = depth <= 1 ? val[key] : (val[key] && clone(val[key], depth - 1)); } return clonedObject as T; diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 815326d6..e61a8e16 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -125,6 +125,10 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL }); this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); })); + this.register(this._inputHandler.onScroll(event => { + this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL }); + this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); + })); // Setup WriteBuffer this._writeBuffer = new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult)); diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index 25b602ea..92162d9a 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -77,7 +77,7 @@ describe('InputHandler', () => { optionsService.options.scrollback = 1; bufferService.reset(); }); - it('SL (scrollLeft)', async () => { + it('SL (scrollLeft)', () => { inputHandler.parseP('12345'.repeat(6)); inputHandler.parseP('\x1b[ @'); assert.deepEqual(getLines(bufferService, 6), ['12345', '2345', '2345', '2345', '2345', '2345']); @@ -86,7 +86,7 @@ describe('InputHandler', () => { inputHandler.parseP('\x1b[2 @'); assert.deepEqual(getLines(bufferService, 6), ['12345', '5', '5', '5', '5', '5']); }); - it('SR (scrollRight)', async () => { + it('SR (scrollRight)', () => { inputHandler.parseP('12345'.repeat(6)); inputHandler.parseP('\x1b[ A'); assert.deepEqual(getLines(bufferService, 6), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']); @@ -95,7 +95,7 @@ describe('InputHandler', () => { inputHandler.parseP('\x1b[2 A'); assert.deepEqual(getLines(bufferService, 6), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']); }); - it('insertColumns (DECIC)', async () => { + it('insertColumns (DECIC)', () => { inputHandler.parseP('12345'.repeat(6)); inputHandler.parseP('\x1b[3;3H'); inputHandler.parseP('\x1b[\'}'); @@ -111,7 +111,7 @@ describe('InputHandler', () => { inputHandler.parseP('\x1b[2\'}'); assert.deepEqual(getLines(bufferService, 6), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']); }); - it('deleteColumns (DECDC)', async () => { + it('deleteColumns (DECDC)', () => { inputHandler.parseP('12345'.repeat(6)); inputHandler.parseP('\x1b[3;3H'); inputHandler.parseP('\x1b[\'~'); @@ -137,7 +137,7 @@ describe('InputHandler', () => { bufferService.reset(); }); describe('reverseWraparound set', () => { - it('should not reverse outside of scroll margins', async () => { + it('should not reverse outside of scroll margins', () => { // prepare buffer content inputHandler.parseP('#####abcdefghijklmnopqrstuvwxy'); assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']); @@ -1919,6 +1919,92 @@ describe('InputHandler', () => { }); }); }); + + // issue #3362 and #2979 + describe('EL/ED cursor at buffer.cols', () => { + beforeEach(() => { + bufferService.resize(10, 5); + }); + describe('cursor should stay at cols / does not overflow', () => { + it('EL0', async () => { + await inputHandler.parseP('##########\x1b[0K'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']); + }); + it('EL1', async () => { + await inputHandler.parseP('##########\x1b[1K'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['', '', '', '', '']); + }); + it('EL2', async () => { + await inputHandler.parseP('##########\x1b[2K'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['', '', '', '', '']); + }); + it('ED0', async () => { + await inputHandler.parseP('##########\x1b[0J'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']); + }); + it('ED1', async () => { + await inputHandler.parseP('##########\x1b[1J'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['', '', '', '', '']); + }); + it('ED2', async () => { + await inputHandler.parseP('##########\x1b[2J'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['', '', '', '', '']); + }); + it('ED3', async () => { + await inputHandler.parseP('##########\x1b[3J'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']); + }); + }); + describe('following sequence keeps working', () => { + // sequences to test (cursor related ones) + const SEQ = [ + /* ICH */ '\x1b[10@', + /* SL */ '\x1b[10 @', + /* CUU */ '\x1b[10A', + /* SR */ '\x1b[10 A', + /* CUD */ '\x1b[10B', + /* CUF */ '\x1b[10C', + /* CUB */ '\x1b[10D', + /* CNL */ '\x1b[10E', + /* CPL */ '\x1b[10F', + /* CHA */ '\x1b[10G', + /* CUP */ '\x1b[10;10H', + /* CHT */ '\x1b[10I', + /* IL */ '\x1b[10L', + /* DL */ '\x1b[10M', + /* DCH */ '\x1b[10P', + /* SU */ '\x1b[10S', + /* SD */ '\x1b[10T', + /* ECH */ '\x1b[10X', + /* CBT */ '\x1b[10Z', + /* HPA */ '\x1b[10`', + /* HPR */ '\x1b[10a', + /* REP */ '\x1b[10b', + /* VPA */ '\x1b[10d', + /* VPR */ '\x1b[10e', + /* HVP */ '\x1b[10;10f', + /* TBC */ '\x1b[0g', + /* SCOSC */ '\x1b[s', + /* DECIC */ '\x1b[10\'}', + /* DECDC */ '\x1b[10\'~' + ]; + it('cursor never advances beyond cols', async () => { + for (const seq of SEQ) { + await inputHandler.parseP('##########\x1b[2J' + seq); + assert.equal(bufferService.buffer.x <= bufferService.cols, true); + inputHandler.reset(); + bufferService.reset(); + } + }); + }); + }); }); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 30c8fbe9..0b284d8e 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -1247,7 +1247,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #P[Protection attributes are not supported.] CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED." */ public eraseInDisplay(params: IParams): boolean { - this._restrictCursor(); + this._restrictCursor(this._bufferService.cols); let j; switch (params.params[0]) { case 0: @@ -1319,7 +1319,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #P[Protection attributes are not supported.] CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL." */ public eraseInLine(params: IParams): boolean { - this._restrictCursor(); + this._restrictCursor(this._bufferService.cols); switch (params.params[0]) { case 0: this._eraseInBufferLine(this._bufferService.buffer.y, this._bufferService.buffer.x, this._bufferService.cols); diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index dce0f570..52fe00d0 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -125,7 +125,7 @@ export class MockOptionsService implements IOptionsService { constructor(testOptions?: IPartialTerminalOptions) { if (testOptions) { for (const key of Object.keys(testOptions)) { - this.options[key] = (testOptions)[key]; + this.options[key] = (testOptions as any)[key]; } } } diff --git a/src/common/buffer/BufferRange.test.ts b/src/common/buffer/BufferRange.test.ts index d0f287dc..69aacd99 100644 --- a/src/common/buffer/BufferRange.test.ts +++ b/src/common/buffer/BufferRange.test.ts @@ -16,10 +16,10 @@ describe('BufferRange', () => { assert.throws(() => getRangeLength(createRange(1, 3, 1, 1), 0)); }); it('should get range multiple lines', () => { - assert.equal(getRangeLength(createRange(1, 1, 4, 5), 5), 23); + assert.equal(getRangeLength(createRange(1, 1, 4, 5), 5), 24); }); it('should get range for end line right after start line', () => { - assert.equal(getRangeLength(createRange(1, 1, 7, 2), 5), 11); + assert.equal(getRangeLength(createRange(1, 1, 7, 2), 5), 12); }); }); }); diff --git a/src/common/buffer/BufferRange.ts b/src/common/buffer/BufferRange.ts index 9091c68d..a49cf481 100644 --- a/src/common/buffer/BufferRange.ts +++ b/src/common/buffer/BufferRange.ts @@ -5,12 +5,9 @@ import { IBufferRange } from 'xterm'; -export function getRangeLength(range: IBufferRange, cols: number): number { - if (range.start.y === range.end.y) { - return range.end.x - range.start.x + 1; - } +export function getRangeLength(range: IBufferRange, bufferCols: number): number { if (range.start.y > range.end.y) { throw new Error(`Buffer range end (${range.end.x}, ${range.end.y}) cannot be before start (${range.start.x}, ${range.start.y})`); } - return cols * (range.end.y - range.start.y - 1) + cols - range.start.x + range.end.x; + return bufferCols * (range.end.y - range.start.y) + (range.end.x - range.start.x + 1); } diff --git a/yarn.lock b/yarn.lock index 712bdc47..ee0f0558 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2143,20 +2143,13 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob-parent@^5.0.0, glob-parent@^5.1.0: +glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" -glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== - dependencies: - is-glob "^4.0.1" - glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"