From efeca49705e12676ba3c9a85b615576f552cf882 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 7 Apr 2019 15:29:37 -0400 Subject: [PATCH 01/12] Mostly implemented buffer API Part of #1994 --- src/public/Terminal.ts | 32 +++++++++++++++-- typings/xterm.d.ts | 80 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 87fcfaef99..bc82d23529 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -3,8 +3,8 @@ * @license MIT */ -import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings } from 'xterm'; -import { ITerminal } from '../Types'; +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm'; +import { ITerminal, IBufferLine, IBuffer } from '../Types'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; @@ -19,6 +19,7 @@ export class Terminal implements ITerminalApi { public get textarea(): HTMLTextAreaElement { return this._core.textarea; } public get rows(): number { return this._core.rows; } public get cols(): number { return this._core.cols; } + public get buffer(): IBufferApi { return new BufferApiView(this._core.buffer); } public get markers(): IMarker[] { return this._core.markers; } public blur(): void { this._core.blur(); @@ -158,3 +159,30 @@ export class Terminal implements ITerminalApi { return Strings; } } + +class BufferApiView implements IBufferApi { + constructor(private _buffer: IBuffer) {} + + public get cursorY(): number { return this._buffer.y; } + public get cursorX(): number { return this._buffer.x; } + public get viewportY(): number { return this._buffer.ydisp; } + public get baseY(): number { return this._buffer.ybase; } + public get length(): number { return this._buffer.lines.length; } + public getLine(y: number): IBufferLineApi { return new BufferLineApiView(this._buffer.lines.get(y)); } +} + +class BufferLineApiView implements IBufferLineApi { + constructor(private _line: IBufferLine) {} + + public get isWrapped(): boolean { return this._line.isWrapped; } + public getCell(x: number): IBufferCellApi { return new BufferCellApiView(this._line, x); } + public translateToString(trimRight: boolean, startColumn: number, endColumn: number): string { + return this._line.translateToString(trimRight, startColumn, endColumn); + } +} + +class BufferCellApiView implements IBufferCellApi { + constructor(private _line: IBufferLine, private _x: number) {} + public get char(): string { return this._line.getString(this._x); } + public get width(): number { return this._line.getWidth(this._x); } +} diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index ec647a10f5..429ad7e382 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -347,6 +347,13 @@ declare module 'xterm' { */ readonly cols: number; + /** + * (EXPERIMENTAL) The terminal's current buffer, note that this might be + * either the normal buffer or the alt buffer depending on what's running in + * the terminal. + */ + readonly buffer: IBuffer; + /** * (EXPERIMENTAL) Get all markers registered against the buffer. If the alt * buffer is active this will always return []. @@ -772,4 +779,77 @@ declare module 'xterm' { */ static applyAddon(addon: any): void; } + + interface IBuffer { + /** + * The y position of the cursor. This ranges between `0` (when the + * cursor is at baseY) and `Terminal.rows - 1` (when the cursor is on the + * last row). + */ + readonly cursorY: number; + + /** + * The x position of the cursor. This ranges between `0` (left side) and + * `Terminal.cols - 1` (right side). + */ + readonly cursorX: number; + + /** + * The line within the buffer where the top of the viewport is. + */ + readonly viewportY: number; + + /** + * The line within the buffer where the top of the bottom page is (when + * fully scrolled down); + */ + readonly baseY: number; + + /** + * The amount of lines in the buffer. + */ + readonly length: number; + + /** + * Gets a line from the buffer. + * + * @param y The line index to get. + */ + getLine(y: number): IBufferLine; + } + + interface IBufferLine { + /** + * Whether the line is wrapped from the previous line. + */ + readonly isWrapped: boolean; + + /** + * Gets a cell from the line. + * + * @param x The character index to get. + */ + getCell(x: number): IBufferCell; + + /** + * Gets the line + */ + translateToString(trimRight: boolean, startColumn: number, endColumn: number): string; + } + + interface IBufferCell { + /** + * The character within the cell. + */ + readonly char: string; + + /** + * The width of the character. Some examples: + * + * - This is `1` for most cells. + * - This is `2` for wide character like CJK glyphs. + * - This is `0` for cells immediately following cells with a width of `2`. + */ + readonly width: number; + } } From b55bedc6af73f2eeeb0472f4b0699ceba3e59a90 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 9 Apr 2019 21:29:08 -0700 Subject: [PATCH 02/12] Add terminal API interface to Types.ts This is needed since the API/public buffer now differs from the TerminalCore's --- src/Types.ts | 43 +++++++++++++++++++++++++++++++-- src/renderer/dom/DomRenderer.ts | 4 +-- typings/xterm.d.ts | 2 +- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/Types.ts b/src/Types.ts index 6e0108aefc..1fa78824f1 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable } from 'xterm'; +import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker } from 'xterm'; import { IColorSet, IRenderer } from './renderer/Types'; import { IMouseZoneManager } from './ui/Types'; import { ICharset } from './core/Types'; @@ -202,7 +202,7 @@ export interface ILinkHoverEvent { fg: number; } -export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor { +export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor { screenElement: HTMLElement; selectionManager: ISelectionManager; charMeasure: ICharMeasure; @@ -231,6 +231,45 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce showCursor(): void; } +export interface IPublicTerminal extends IDisposable, IEventEmitter { + textarea: HTMLTextAreaElement; + rows: number; + cols: number; + buffer: IBuffer; + markers: IMarker[]; + blur(): void; + focus(): void; + resize(columns: number, rows: number): void; + writeln(data: string): void; + open(parent: HTMLElement): void; + attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; + addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable; + addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; + registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number; + deregisterLinkMatcher(matcherId: number): void; + registerCharacterJoiner(handler: (text: string) => [number, number][]): number; + deregisterCharacterJoiner(joinerId: number): void; + addMarker(cursorYOffset: number): IMarker; + hasSelection(): boolean; + getSelection(): string; + clearSelection(): void; + selectAll(): void; + selectLines(start: number, end: number): void; + dispose(): void; + destroy(): void; + scrollLines(amount: number): void; + scrollPages(pageCount: number): void; + scrollToTop(): void; + scrollToBottom(): void; + scrollToLine(line: number): void; + clear(): void; + write(data: string): void; + getOption(key: string): any; + setOption(key: string, value: any): void; + refresh(start: number, end: number): void; + reset(): void; +} + export interface IBufferAccessor { buffer: IBuffer; } diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 78ccc620e8..c4da6710c3 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -151,8 +151,8 @@ export class DomRenderer extends EventEmitter implements IRenderer { `${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` + ` color: ${this.colorManager.colors.foreground.css};` + ` background-color: ${this.colorManager.colors.background.css};` + - ` font-family: ${this._terminal.getOption('fontFamily')};` + - ` font-size: ${this._terminal.getOption('fontSize')}px;` + + ` font-family: ${this._terminal.options.fontFamily};` + + ` font-size: ${this._terminal.options.fontSize}px;` + `}`; // Text styles styles += diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 429ad7e382..3a656eab2d 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -275,7 +275,7 @@ declare module 'xterm' { * A callback that fires when the mouse leaves a link. Note that this can * happen even when tooltipCallback hasn't fired for the link yet. */ - leaveCallback?: (event: MouseEvent, uri: string) => boolean | void; + leaveCallback?: () => void; /** * The priority of the link matcher, this defines the order in which the link From 5362ea195ce81ce2707540473c5b7d9d7f4f0eba Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 10 Apr 2019 00:24:50 -0700 Subject: [PATCH 03/12] Add first API test --- src/Terminal.integration.ts | 30 ++++++++++++++++++++++++++++++ src/public/Terminal.ts | 2 +- typings/xterm.d.ts | 2 +- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index 100430066c..e0dad37793 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -13,6 +13,7 @@ import * as path from 'path'; import * as pty from 'node-pty'; import { assert } from 'chai'; import { Terminal } from './Terminal'; +import { Terminal as PublicTerminal } from './public/Terminal'; import { WHITESPACE_CELL_CHAR } from './Buffer'; import { IViewport } from './Types'; import { CellData } from './BufferLine'; @@ -78,6 +79,35 @@ function terminalToString(term: Terminal): string { return result; } +describe('API tests', () => { + let api: PublicTerminal; + let core: TestTerminal; + + // expect files need terminal at 80x25! + const INIT_COLS = 80; + const INIT_ROWS = 25; + + beforeEach(() => { + api = new PublicTerminal({ cols: INIT_COLS, rows: INIT_ROWS }); + core = (api as any)._core; + core.innerWrite = () => (core as any)._innerWrite(); + core.refresh = () => {}; + core.viewport = { + syncScrollArea: () => {} + }; + }); + + describe('buffer', () => { + it('IBufferLine.getLine', () => { + core.writeBuffer.push('abc\n\rdef'); + core.innerWrite(); + assert.equal(api.buffer.length, INIT_ROWS); + assert.equal(api.buffer.getLine(0).translateToString(true), 'abc'); + assert.equal(api.buffer.getLine(1).translateToString(true), 'def'); + }); + }); +}); + // Skip tests on Windows since pty.open isn't supported if (os.platform() !== 'win32') { const consoleLog = console.log; diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index bc82d23529..35423b80a2 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -176,7 +176,7 @@ class BufferLineApiView implements IBufferLineApi { public get isWrapped(): boolean { return this._line.isWrapped; } public getCell(x: number): IBufferCellApi { return new BufferCellApiView(this._line, x); } - public translateToString(trimRight: boolean, startColumn: number, endColumn: number): string { + public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string { return this._line.translateToString(trimRight, startColumn, endColumn); } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 3a656eab2d..7f87cb129f 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -834,7 +834,7 @@ declare module 'xterm' { /** * Gets the line */ - translateToString(trimRight: boolean, startColumn: number, endColumn: number): string; + translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string; } interface IBufferCell { From 16aaf6c58c5209e879343996de6d0130c6cd3043 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 10 May 2019 21:08:06 -0700 Subject: [PATCH 04/12] Fix build --- src/Types.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Types.ts b/src/Types.ts index a6451cfdd1..5c276bf2d5 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -227,6 +227,15 @@ export interface IPublicTerminal extends IDisposable, IEventEmitter { cols: number; buffer: IBuffer; markers: IMarker[]; + onCursorMove: IEvent; + onData: IEvent; + onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; + onLineFeed: IEvent; + onScroll: IEvent; + onSelectionChange: IEvent; + onRender: IEvent<{ start: number, end: number }>; + onResize: IEvent<{ cols: number, rows: number }>; + onTitleChange: IEvent; blur(): void; focus(): void; resize(columns: number, rows: number): void; From eb6deb67f9273e11ac36108d1df2ab4d0387aed8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 10:10:31 -0700 Subject: [PATCH 05/12] Fix build issues --- src/Terminal.integration.ts | 1 - src/public/Terminal.ts | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index 459d84aac7..5e12d2a308 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -13,7 +13,6 @@ import * as path from 'path'; import * as pty from 'node-pty'; import { assert } from 'chai'; import { Terminal } from './Terminal'; -import { Terminal as PublicTerminal } from './public/Terminal'; import { IViewport } from './Types'; import { CellData, WHITESPACE_CELL_CHAR } from './core/buffer/BufferLine'; diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index eb3809b171..ee623ca334 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -4,7 +4,8 @@ */ import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm'; -import { ITerminal, IBufferLine, IBuffer } from '../Types'; +import { ITerminal, IBuffer } from '../Types'; +import { IBufferLine } from '../core/Types'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; import { IEvent } from '../common/EventEmitter2'; From bce2eae66f771ae4b10815b3872f3c2b89b2c8c2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 10:15:29 -0700 Subject: [PATCH 06/12] Update yarn.lock --- yarn.lock | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 440aa4f826..e14ad3a1a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -69,6 +69,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.108.tgz#852e8496bcfc5e74cae83a5eb3b30e5661e9b7b9" integrity sha512-5q14jNJCPW+Iwk6Y1JxtA7T5ov1aVRS2VA2PvRgFMZtCjoIo8WT1WO56dSV0MSiHR7BEoe2QNuXigBQNqbWdAw== +"@types/puppeteer@^1.12.4": + version "1.12.4" + resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.12.4.tgz#8388efdb0b30a54a7e7c4831ca0d709191d77ff1" + integrity sha512-aaGbJaJ9TuF9vZfTeoh876sBa+rYJWPwtsmHmYr28pGr42ewJnkDTq2aeSKEmS39SqUdkwLj73y/d7rBSp7mDQ== + dependencies: + "@types/node" "*" + "@types/tapable@*": version "1.0.4" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370" @@ -314,6 +321,13 @@ acorn@^5.6.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.2.tgz#91fa871883485d06708800318404e72bfb26dcc5" integrity sha512-cJrKCNcr2kv8dlDnbw+JPUGjHZzo4myaxOLmpOX8a+rgX94YeTcTMv/LFJUSByRpc+i4GgVnnhLxvMu/2Y+rqw== +agent-base@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" + integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== + dependencies: + es6-promisify "^5.0.0" + ajv-keywords@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" @@ -1307,7 +1321,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0, concat-stream@^1.6.1: +concat-stream@1.6.2, concat-stream@^1.5.0, concat-stream@^1.6.1: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -1606,7 +1620,7 @@ debug@2.6.8: dependencies: ms "2.0.0" -debug@2.X, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: +debug@2.6.9, debug@2.X, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -1620,6 +1634,13 @@ debug@^3.1.0: dependencies: ms "2.0.0" +debug@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + debug@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" @@ -1938,6 +1959,18 @@ es6-promise@^3.0.2, es6-promise@^3.1.2: resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" integrity sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM= +es6-promise@^4.0.3: + version "4.2.6" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f" + integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + es6-symbol@^3.1.1, es6-symbol@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" @@ -2174,6 +2207,16 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extract-zip@^1.6.6: + version "1.6.7" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" + integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= + dependencies: + concat-stream "1.6.2" + debug "2.6.9" + mkdirp "0.5.1" + yauzl "2.4.1" + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -2213,6 +2256,13 @@ fast-levenshtein@~2.0.4: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fd-slicer@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" + integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU= + dependencies: + pend "~1.2.0" + figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -3042,6 +3092,14 @@ https-browserify@~0.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" integrity sha1-P5E2XKvmC3ftDruiS0VOPgnZWoI= +https-proxy-agent@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" + integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== + dependencies: + agent-base "^4.1.0" + debug "^3.1.0" + iconv-lite@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" @@ -4235,6 +4293,11 @@ mime@1.3.4: resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" integrity sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM= +mime@^2.0.3: + version "2.4.2" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.2.tgz#ce5229a5e99ffc313abac806b482c10e7ba6ac78" + integrity sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg== + mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -4394,6 +4457,11 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= +ms@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + multipipe@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" @@ -5067,6 +5135,11 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -5165,6 +5238,11 @@ process@^0.11.10, process@~0.11.0: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= +progress@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -5178,6 +5256,11 @@ proxy-addr@~1.0.10: forwarded "~0.1.0" ipaddr.js "1.0.5" +proxy-from-env@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" + integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -5243,6 +5326,20 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +puppeteer@^1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-1.15.0.tgz#1680fac13e51f609143149a5b7fa99eec392b34f" + integrity sha512-D2y5kwA9SsYkNUmcBzu9WZ4V1SGHiQTmgvDZSx6sRYFsgV25IebL4V6FaHjF6MbwLK9C6f3G3pmck9qmwM8H3w== + dependencies: + debug "^4.1.0" + extract-zip "^1.6.6" + https-proxy-agent "^2.2.1" + mime "^2.0.3" + progress "^2.0.1" + proxy-from-env "^1.0.0" + rimraf "^2.6.1" + ws "^6.1.0" + qs@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/qs/-/qs-4.0.0.tgz#c31d9b74ec27df75e543a86c78728ed8d4623607" @@ -7100,6 +7197,13 @@ ws@^4.0.0: async-limiter "~1.0.0" safe-buffer "~5.1.0" +ws@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + xdg-basedir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2" @@ -7222,6 +7326,13 @@ yargs@~3.10.0: decamelize "^1.0.0" window-size "0.1.0" +yauzl@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" + integrity sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU= + dependencies: + fd-slicer "~1.0.1" + zmodem.js@^0.1.5: version "0.1.7" resolved "https://registry.yarnpkg.com/zmodem.js/-/zmodem.js-0.1.7.tgz#247affb76d2b1e3042b3fc8b4a087b9d5db8d1ed" From d694eae6b5e4dd717aaa2fba0da998d4f0e41fbd Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 10:32:53 -0700 Subject: [PATCH 07/12] Add some buffer tests --- src/public/Terminal.api.ts | 68 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 380cc89c3a..daa160d3e1 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -231,6 +231,74 @@ describe('API Integration Tests', () => { assert.deepEqual(await page.evaluate(`window.calls`), ['foo']); }); }); + + describe('buffer', () => { + it('cursorX, cursorY', async function(): Promise { + this.timeout(10000); + await openTerminal({ rows: 5, cols: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0); + assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 0); + await page.evaluate(`window.term.write('foo')`); + assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 3); + assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 0); + await page.evaluate(`window.term.write('\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 3); + assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1); + await page.evaluate(`window.term.write('\\r')`); + assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0); + assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1); + await page.evaluate(`window.term.write('abcde')`); + assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 5); + assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1); + await page.evaluate(`window.term.write('\\n\\r\\n\\n\\n\\n\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0); + assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 4); + }); + + it('viewportY', async function(): Promise { + this.timeout(10000); + await openTerminal({ rows: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0); + await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0); + await page.evaluate(`window.term.write('\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 1); + await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 5); + await page.evaluate(`window.term.scrollLines(-1)`); + assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 4); + await page.evaluate(`window.term.scrollToTop()`); + assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0); + }); + + it('baseY', async function(): Promise { + this.timeout(10000); + await openTerminal({ rows: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.baseY`), 0); + await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.baseY`), 0); + await page.evaluate(`window.term.write('\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.baseY`), 1); + await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5); + await page.evaluate(`window.term.scrollLines(-1)`); + assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5); + await page.evaluate(`window.term.scrollToTop()`); + assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5); + }); + + it('length', async function(): Promise { + this.timeout(10000); + await openTerminal({ rows: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.length`), 5); + await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.length`), 5); + await page.evaluate(`window.term.write('\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.length`), 6); + await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); + assert.equal(await page.evaluate(`window.term.buffer.length`), 10); + }); + }); }); async function openTerminal(options: ITerminalOptions = {}): Promise { From 46995fdbc1d5b308a04d746df5417d9e5b4e8c1d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 10:46:00 -0700 Subject: [PATCH 08/12] Add tests for buffer.getLine --- src/public/Terminal.api.ts | 45 ++++++++++++++++++++++++++++++++++++++ typings/xterm.d.ts | 7 +++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index daa160d3e1..6d777ee171 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -298,6 +298,51 @@ describe('API Integration Tests', () => { await page.evaluate(`window.term.write('\\n\\n\\n\\n')`); assert.equal(await page.evaluate(`window.term.buffer.length`), 10); }); + + describe('getLine', () => { + it('isWrapped', async function(): Promise { + this.timeout(10000); + await openTerminal({ cols: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false); + assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), false); + await page.evaluate(`window.term.write('abcde')`); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false); + assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), false); + await page.evaluate(`window.term.write('f')`); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false); + assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), true); + }); + + it('translateToString', async function(): Promise { + this.timeout(10000); + await openTerminal({ cols: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), ' '); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), ''); + await page.evaluate(`window.term.write('foo')`); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), 'foo '); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); + await page.evaluate(`window.term.write('bar')`); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), 'fooba'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'fooba'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'r'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(false, 1)`), 'ooba'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(false, 1, 3)`), 'oo'); + }); + + it('getCell', async function(): Promise { + this.timeout(10000); + await openTerminal(); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).char`), ''); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).width`), 1); + await page.evaluate(`window.term.write('aĉ–‡')`); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).char`), 'a'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).width`), 1); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(1).char`), 'ĉ–‡'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(1).width`), 2); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(2).char`), ''); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(2).width`), 0); + }); + }); }); }); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 49bb6c3197..00e7b215e2 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -916,7 +916,12 @@ declare module 'xterm' { getCell(x: number): IBufferCell; /** - * Gets the line + * Gets the line as a string. Note that this is gets only the string for the line, not taking + * isWrapped into account. + * + * @param trimRight Whether to trim any whitespace at the right of the line. + * @param startColumn The column to start from (inclusive). + * @param endColumn The column to end at (exclusive). */ translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string; } From cd3f2609d6ecae53c1de4cb5835b759394b856b6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 12:12:53 -0700 Subject: [PATCH 09/12] Make tests use buffer API over _core --- src/public/Terminal.api.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 6d777ee171..935e160300 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -48,7 +48,7 @@ describe('API Integration Tests', () => { window.term.write('foo'); window.term.write('bar'); `); - assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(0, true)`), 'foobar'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar'); }); it('writeln', async function(): Promise { @@ -58,8 +58,8 @@ describe('API Integration Tests', () => { window.term.writeln('foo'); window.term.writeln('bar'); `); - assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(0, true)`), 'foo'); - assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(1, true)`), 'bar'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'bar'); }); it('clear', async function(): Promise { @@ -72,10 +72,10 @@ describe('API Integration Tests', () => { } `); await page.evaluate(`window.term.clear()`); - assert.equal(await page.evaluate(`window.term._core.buffer.lines.length`), '5'); - assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(0, true)`), 'test9'); + assert.equal(await page.evaluate(`window.term.buffer.length`), '5'); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'test9'); for (let i = 1; i < 5; i++) { - assert.equal(await page.evaluate(`window.term._core.buffer.translateBufferLineToString(${i}, true)`), ''); + assert.equal(await page.evaluate(`window.term.buffer.getLine(${i}).translateToString(true)`), ''); } }); From f41e00ed97f6798dbc957bdcb9c3c049c3b56166 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 20:46:55 -0700 Subject: [PATCH 10/12] Return undefined if cell or line doesn't exist --- src/public/Terminal.ts | 9 +++++++-- typings/xterm.d.ts | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index ee623ca334..dd07c73580 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -180,14 +180,19 @@ class BufferApiView implements IBufferApi { public get viewportY(): number { return this._buffer.ydisp; } public get baseY(): number { return this._buffer.ybase; } public get length(): number { return this._buffer.lines.length; } - public getLine(y: number): IBufferLineApi { return new BufferLineApiView(this._buffer.lines.get(y)); } + public getLine(y: number): IBufferLineApi | undefined { return new BufferLineApiView(this._buffer.lines.get(y)); } } class BufferLineApiView implements IBufferLineApi { constructor(private _line: IBufferLine) {} public get isWrapped(): boolean { return this._line.isWrapped; } - public getCell(x: number): IBufferCellApi { return new BufferCellApiView(this._line, x); } + public getCell(x: number): IBufferCellApi | undefined { + if (x < 0 && x >= this._line.length) { + return undefined; + } + return new BufferCellApiView(this._line, x); + } public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string { return this._line.translateToString(trimRight, startColumn, endColumn); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 00e7b215e2..e9643b6392 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -895,11 +895,11 @@ declare module 'xterm' { readonly length: number; /** - * Gets a line from the buffer. + * Gets a line from the buffer, or undefined if the line index does not exist. * * @param y The line index to get. */ - getLine(y: number): IBufferLine; + getLine(y: number): IBufferLine | undefined; } interface IBufferLine { @@ -909,7 +909,7 @@ declare module 'xterm' { readonly isWrapped: boolean; /** - * Gets a cell from the line. + * Gets a cell from the line, or undefined if the line index does not exist. * * @param x The character index to get. */ From 49574be510ce7ac5a9b02e1dc96cbff80f710504 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 20:49:48 -0700 Subject: [PATCH 11/12] Add warnings to getCell and getLine --- typings/xterm.d.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index e9643b6392..0714f55f0a 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -356,9 +356,9 @@ declare module 'xterm' { readonly cols: number; /** - * (EXPERIMENTAL) The terminal's current buffer, note that this might be - * either the normal buffer or the alt buffer depending on what's running in - * the terminal. + * (EXPERIMENTAL) The terminal's current buffer, this might be either the + * normal buffer or the alt buffer depending on what's running in the + * terminal. */ readonly buffer: IBuffer; @@ -897,6 +897,9 @@ declare module 'xterm' { /** * Gets a line from the buffer, or undefined if the line index does not exist. * + * Note that the result of this function should be used immediately after calling as when the + * terminal updates it could lead to unexpected behavior. + * * @param y The line index to get. */ getLine(y: number): IBufferLine | undefined; @@ -911,6 +914,9 @@ declare module 'xterm' { /** * Gets a cell from the line, or undefined if the line index does not exist. * + * Note that the result of this function should be used immediately after calling as when the + * terminal updates it could lead to unexpected behavior. + * * @param x The character index to get. */ getCell(x: number): IBufferCell; From 70ba349a858d2fcb810e60db53a5ba142b8f365c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 11 May 2019 20:58:51 -0700 Subject: [PATCH 12/12] Fix invalid index check, add api tests --- src/public/Terminal.api.ts | 11 ++++++++++- src/public/Terminal.ts | 10 ++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 935e160300..031e03d54a 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -300,6 +300,13 @@ describe('API Integration Tests', () => { }); describe('getLine', () => { + it('invalid index', async function(): Promise { + this.timeout(10000); + await openTerminal({ rows: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.getLine(-1)`), undefined); + assert.equal(await page.evaluate(`window.term.buffer.getLine(5)`), undefined); + }); + it('isWrapped', async function(): Promise { this.timeout(10000); await openTerminal({ cols: 5 }); @@ -331,7 +338,9 @@ describe('API Integration Tests', () => { it('getCell', async function(): Promise { this.timeout(10000); - await openTerminal(); + await openTerminal({ cols: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(-1)`), undefined); + assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(5)`), undefined); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).char`), ''); assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).width`), 1); await page.evaluate(`window.term.write('aĉ–‡')`); diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index dd07c73580..0c6a77457e 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -180,7 +180,13 @@ class BufferApiView implements IBufferApi { public get viewportY(): number { return this._buffer.ydisp; } public get baseY(): number { return this._buffer.ybase; } public get length(): number { return this._buffer.lines.length; } - public getLine(y: number): IBufferLineApi | undefined { return new BufferLineApiView(this._buffer.lines.get(y)); } + public getLine(y: number): IBufferLineApi | undefined { + const line = this._buffer.lines.get(y); + if (!line) { + return undefined; + } + return new BufferLineApiView(line); + } } class BufferLineApiView implements IBufferLineApi { @@ -188,7 +194,7 @@ class BufferLineApiView implements IBufferLineApi { public get isWrapped(): boolean { return this._line.isWrapped; } public getCell(x: number): IBufferCellApi | undefined { - if (x < 0 && x >= this._line.length) { + if (x < 0 || x >= this._line.length) { return undefined; } return new BufferCellApiView(this._line, x);