diff --git a/addons/xterm-addon-attach/src/AttachAddon.api.ts b/addons/xterm-addon-attach/src/AttachAddon.api.ts index 6816a5f11f..af2c36c7ef 100644 --- a/addons/xterm-addon-attach/src/AttachAddon.api.ts +++ b/addons/xterm-addon-attach/src/AttachAddon.api.ts @@ -36,7 +36,7 @@ describe('AttachAddon', () => { const server = new WebSocket.Server({ port }); server.on('connection', socket => socket.send('foo')); await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`); - await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); + await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo'); server.close(); }); @@ -47,7 +47,7 @@ describe('AttachAddon', () => { const data = new Uint8Array([102, 111, 111]); server.on('connection', socket => socket.send(data)); await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`); - await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); + await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo'); server.close(); }); }); diff --git a/addons/xterm-addon-search/src/SearchAddon.api.ts b/addons/xterm-addon-search/src/SearchAddon.api.ts index 125e3dcb2a..85e5059c4b 100644 --- a/addons/xterm-addon-search/src/SearchAddon.api.ts +++ b/addons/xterm-addon-search/src/SearchAddon.api.ts @@ -60,7 +60,7 @@ describe('Search Tests', function(): void { await page.evaluate(`window.term.writeln('package.jsonc\\n')`); await writeSync(page, 'package.json pack package.lock'); await page.evaluate(`window.search.findPrevious('pack', {incremental: true})`); - let line: string = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`); + let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`); let selectionPosition: { startColumn: number, startRow: number, endColumn: number, endRow: number } = await page.evaluate(`window.term.getSelectionPosition()`); // We look further ahead in the line to ensure that pack was selected from package.lock assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 8), 'package.lock'); @@ -69,7 +69,7 @@ describe('Search Tests', function(): void { assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 3), 'package.json'); await page.evaluate(`window.search.findPrevious('package.jsonc', {incremental: true})`); // We have to reevaluate line because it should have switched starting rows at this point - line = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`); + line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc'); }); @@ -77,7 +77,7 @@ describe('Search Tests', function(): void { await page.evaluate(`window.term.writeln('package.lock pack package.json package.ups\\n')`); await writeSync(page, 'package.jsonc'); await page.evaluate(`window.search.findNext('pack', {incremental: true})`); - let line: string = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`); + let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`); let selectionPosition: { startColumn: number, startRow: number, endColumn: number, endRow: number } = await page.evaluate(`window.term.getSelectionPosition()`); // We look further ahead in the line to ensure that pack was selected from package.lock assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 8), 'package.lock'); @@ -86,7 +86,7 @@ describe('Search Tests', function(): void { assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 3), 'package.json'); await page.evaluate(`window.search.findNext('package.jsonc', {incremental: true})`); // We have to reevaluate line because it should have switched starting rows at this point - line = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`); + line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc'); }); diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 183687ac11..ba8dbc0728 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -87,7 +87,7 @@ export class SearchAddon implements ITerminalAddon { // Search from startRow + 1 to end if (!result) { - for (let y = startRow + 1; y < this._terminal.buffer.baseY + this._terminal.rows; y++) { + for (let y = startRow + 1; y < this._terminal.buffer.active.baseY + this._terminal.rows; y++) { searchPosition.startRow = y; searchPosition.startCol = 0; // If the current line is wrapped line, increase index of column to ignore the previous scan @@ -135,7 +135,7 @@ export class SearchAddon implements ITerminalAddon { } const isReverseSearch = true; - let startRow = this._terminal.buffer.baseY + this._terminal.rows; + let startRow = this._terminal.buffer.active.baseY + this._terminal.rows; let startCol = this._terminal.cols; let result: ISearchResult | undefined; const incremental = searchOptions ? searchOptions.incremental : false; @@ -174,8 +174,8 @@ export class SearchAddon implements ITerminalAddon { } } // If we hit the top and didn't search from the very bottom wrap back down - if (!result && startRow !== (this._terminal.buffer.baseY + this._terminal.rows)) { - for (let y = (this._terminal.buffer.baseY + this._terminal.rows); y > startRow; y--) { + if (!result && startRow !== (this._terminal.buffer.active.baseY + this._terminal.rows)) { + for (let y = (this._terminal.buffer.active.baseY + this._terminal.rows); y > startRow; y--) { searchPosition.startRow = y; result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); if (result) { @@ -197,7 +197,7 @@ export class SearchAddon implements ITerminalAddon { private _initLinesCache(): void { const terminal = this._terminal!; if (!this._linesCache) { - this._linesCache = new Array(terminal.buffer.length); + this._linesCache = new Array(terminal.buffer.active.length); this._cursorMoveListener = terminal.onCursorMove(() => this._destroyLinesCache()); this._resizeListener = terminal.onResize(() => this._destroyLinesCache()); } @@ -249,7 +249,7 @@ export class SearchAddon implements ITerminalAddon { const col = searchPosition.startCol; // Ignore wrapped lines, only consider on unwrapped line (first row of command string). - const firstLine = terminal.buffer.getLine(row); + const firstLine = terminal.buffer.active.getLine(row); if (firstLine && firstLine.isWrapped) { if (isReverseSearch) { searchPosition.startCol += terminal.cols; @@ -311,7 +311,7 @@ export class SearchAddon implements ITerminalAddon { return; } - const line = terminal.buffer.getLine(row); + const line = terminal.buffer.active.getLine(row); if (line) { for (let i = 0; i < resultIndex; i++) { @@ -354,9 +354,9 @@ export class SearchAddon implements ITerminalAddon { let lineWrapsToNext: boolean; do { - const nextLine = terminal.buffer.getLine(lineIndex + 1); + const nextLine = terminal.buffer.active.getLine(lineIndex + 1); lineWrapsToNext = nextLine ? nextLine.isWrapped : false; - const line = terminal.buffer.getLine(lineIndex); + const line = terminal.buffer.active.getLine(lineIndex); if (!line) { break; } @@ -380,8 +380,8 @@ export class SearchAddon implements ITerminalAddon { } terminal.select(result.col, result.row, result.term.length); // If it is not in the viewport then we scroll else it just gets selected - if (result.row >= (terminal.buffer.viewportY + terminal.rows) || result.row < terminal.buffer.viewportY) { - let scroll = result.row - terminal.buffer.viewportY; + 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); terminal.scrollLines(scroll); } diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index 1d011726c9..445c486027 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -175,8 +175,8 @@ export class SerializeAddon implements ITerminalAddon { throw new Error('Cannot use addon until it has been loaded'); } - const maxRows = this._terminal.buffer.length; - const handler = new StringSerializeHandler(this._terminal.buffer); + const maxRows = this._terminal.buffer.active.length; + const handler = new StringSerializeHandler(this._terminal.buffer.active); rows = (rows === undefined) ? maxRows : constrain(rows, 0, maxRows); diff --git a/addons/xterm-addon-web-links/src/WebLinkProvider.ts b/addons/xterm-addon-web-links/src/WebLinkProvider.ts index ea43aae644..23dc8560fb 100644 --- a/addons/xterm-addon-web-links/src/WebLinkProvider.ts +++ b/addons/xterm-addon-web-links/src/WebLinkProvider.ts @@ -84,7 +84,7 @@ export class LinkComputer { let prevLinesToWrap: boolean; do { - const line = terminal.buffer.getLine(lineIndex); + const line = terminal.buffer.active.getLine(lineIndex); if (!line) { break; } @@ -99,9 +99,9 @@ export class LinkComputer { const startLineIndex = lineIndex; do { - const nextLine = terminal.buffer.getLine(lineIndex + 1); + const nextLine = terminal.buffer.active.getLine(lineIndex + 1); lineWrapsToNext = nextLine ? nextLine.isWrapped : false; - const line = terminal.buffer.getLine(lineIndex); + const line = terminal.buffer.active.getLine(lineIndex); if (!line) { break; } diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 6c67febb53..f8eed3ae8d 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -250,7 +250,7 @@ export class GlyphRenderer { private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number): void { const terminal = this._terminal; - const row = y + terminal.buffer.viewportY; + const row = y + terminal.buffer.active.viewportY; let line: IBufferLine | undefined; for (let x = startCol; x < endCol; x++) { const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL; @@ -281,7 +281,7 @@ export class GlyphRenderer { } if (code & COMBINED_CHAR_BIT_MASK) { if (!line) { - line = terminal.buffer.getLine(row); + line = terminal.buffer.active.getLine(row); } const chars = line!.getCell(x)!.getChars(); this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg, chars); diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 6ae52c4c2b..b06ff3c383 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -302,8 +302,8 @@ export class WebglRenderer extends Disposable implements IRenderer { } // Translate from buffer position to viewport position - const viewportStartRow = start[1] - terminal.buffer.viewportY; - const viewportEndRow = end[1] - terminal.buffer.viewportY; + const viewportStartRow = start[1] - terminal.buffer.active.viewportY; + const viewportEndRow = end[1] - terminal.buffer.active.viewportY; const viewportCappedStartRow = Math.max(viewportStartRow, 0); const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index 310236b63e..75201f47df 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -76,14 +76,14 @@ export class CursorRenderLayer extends BaseRenderLayer { if (this._cursorBlinkStateManager) { this._cursorBlinkStateManager.pause(); } - this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY }); + this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY }); } public onFocus(terminal: Terminal): void { if (this._cursorBlinkStateManager) { this._cursorBlinkStateManager.resume(terminal); } else { - this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY }); + this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY }); } } @@ -100,7 +100,7 @@ export class CursorRenderLayer extends BaseRenderLayer { } // Request a refresh from the terminal as management of rendering is being // moved back to the terminal - this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY }); + this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY }); } public onCursorMove(terminal: Terminal): void { @@ -125,11 +125,11 @@ export class CursorRenderLayer extends BaseRenderLayer { return; } - const cursorY = terminal.buffer.baseY + terminal.buffer.cursorY; - const viewportRelativeCursorY = cursorY - terminal.buffer.viewportY; + const cursorY = terminal.buffer.active.baseY + terminal.buffer.active.cursorY; + const viewportRelativeCursorY = cursorY - terminal.buffer.active.viewportY; // in case cursor.x == cols adjust visual cursor to cols - 1 - const cursorX = Math.min(terminal.buffer.cursorX, terminal.cols - 1); + const cursorX = Math.min(terminal.buffer.active.cursorX, terminal.cols - 1); // Don't draw the cursor if it's off-screen if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= terminal.rows) { diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index da03c127d7..61334f08a1 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -3,14 +3,14 @@ * @license MIT */ -import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier, ILinkProvider, IUnicodeHandling, IUnicodeVersionProvider } from 'xterm'; +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier, ILinkProvider, IUnicodeHandling, IUnicodeVersionProvider } from 'xterm'; import { ITerminal } from '../Types'; import { IBufferLine, ICellData } from 'common/Types'; -import { IBuffer } from 'common/buffer/Types'; +import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { CellData } from 'common/buffer/CellData'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../browser/LocalizableStrings'; -import { IEvent } from 'common/EventEmitter'; +import { IEvent, EventEmitter } from 'common/EventEmitter'; import { AddonManager } from './AddonManager'; import { IParams } from 'common/parser/Types'; @@ -48,7 +48,7 @@ export class Terminal implements ITerminalApi { public get textarea(): HTMLTextAreaElement | undefined { 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 buffer(): IBufferNamespaceApi { return new BufferNamespaceApi(this._core.buffers); } public get markers(): ReadonlyArray { return this._core.markers; } public blur(): void { this._core.blur(); @@ -193,7 +193,15 @@ export class Terminal implements ITerminalApi { } class BufferApiView implements IBufferApi { - constructor(private _buffer: IBuffer) { } + constructor( + private _buffer: IBuffer, + public readonly type: 'normal' | 'alternate' + ) { } + + public init(buffer: IBuffer): BufferApiView { + this._buffer = buffer; + return this; + } public get cursorY(): number { return this._buffer.y; } public get cursorX(): number { return this._buffer.x; } @@ -210,6 +218,30 @@ class BufferApiView implements IBufferApi { public getNullCell(): IBufferCellApi { return new CellData(); } } +class BufferNamespaceApi implements IBufferNamespaceApi { + private _normal: BufferApiView; + private _alternate: BufferApiView; + private _onBufferChange = new EventEmitter(); + public get onBufferChange(): IEvent { return this._onBufferChange.event; } + + constructor(private _buffers: IBufferSet) { + this._normal = new BufferApiView(this._buffers.normal, 'normal'); + this._alternate = new BufferApiView(this._buffers.alt, 'alternate'); + this._buffers.onBufferActivate(() => this._onBufferChange.fire(this.active)); + } + public get active(): IBufferApi { + if (this._buffers.active === this._buffers.normal) { return this.normal; } + if (this._buffers.active === this._buffers.alt) { return this.alternate; } + throw new Error('Active buffer is neither normal nor alternate'); + } + public get normal(): IBufferApi { + return this._normal.init(this._buffers.normal); + } + public get alternate(): IBufferApi { + return this._alternate.init(this._buffers.alt); + } +} + class BufferLineApiView implements IBufferLineApi { constructor(private _line: IBufferLine) { } diff --git a/test/api/CharWidth.api.ts b/test/api/CharWidth.api.ts index 705dde7cbb..a1c3808426 100644 --- a/test/api/CharWidth.api.ts +++ b/test/api/CharWidth.api.ts @@ -78,7 +78,7 @@ async function sumWidths(start: number, end: number, sentinel: string): Promise< await page.evaluate(` (function() { window.result = 0; - const buffer = window.term.buffer; + const buffer = window.term.buffer.active; for (let i = ${start}; i < ${end}; i++) { const line = buffer.getLine(i); let j = 0; diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index 01df89a910..bc845f1129 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -164,7 +164,7 @@ describe('InputHandler Integration Tests', function(): void { window.term.reset() window.term.write('1\\n2\\n3\\n4\\n5${fixture}\x1b[3J') `); - await pollFor(page, () => page.evaluate(`window.term.buffer.length`), 5); + await pollFor(page, () => page.evaluate(`window.term.buffer.active.length`), 5); await pollFor(page, () => getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']); }); @@ -193,7 +193,7 @@ describe('InputHandler Integration Tests', function(): void { window.term.reset() window.term.write('1\\n2\\n3\\n4\\n5${fixture}\x1b[?3J') `); - await pollFor(page, () => page.evaluate(`window.term.buffer.length`), 5); + await pollFor(page, () => page.evaluate(`window.term.buffer.active.length`), 5); await pollFor(page, () => getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']); }); @@ -239,7 +239,7 @@ describe('InputHandler Integration Tests', function(): void { it('Report Cursor Position (CPR) - CSI 6 n', async function(): Promise { await page.evaluate(`window.term.write('\\n\\nfoo')`); await pollFor(page, () => page.evaluate(` - [window.term.buffer.cursorY, window.term.buffer.cursorX] + [window.term.buffer.active.cursorY, window.term.buffer.active.cursorX] `), [2, 3]); await page.evaluate(` window.term.onData(e => window.result = e); @@ -251,7 +251,7 @@ describe('InputHandler Integration Tests', function(): void { it('Report Cursor Position (DECXCPR) - CSI ? 6 n', async function(): Promise { await page.evaluate(`window.term.write('\\n\\nfoo')`); await pollFor(page, () => page.evaluate(` - [window.term.buffer.cursorY, window.term.buffer.cursorX] + [window.term.buffer.active.cursorY, window.term.buffer.active.cursorX] `), [2, 3]); await page.evaluate(` window.term.onData(e => window.result = e); @@ -409,7 +409,7 @@ describe('InputHandler Integration Tests', function(): void { async function getLinesAsArray(count: number, start: number = 0): Promise { let text = ''; for (let i = start; i < start + count; i++) { - text += `window.term.buffer.getLine(${i}).translateToString(true), `; + text += `window.term.buffer.active.getLine(${i}).translateToString(true),`; } return await page.evaluate(`[${text}]`); } @@ -429,10 +429,10 @@ async function simulatePaste(text: string): Promise { async function getCursor(): Promise<{ col: number, row: number }> { return page.evaluate(` - (function() { - return { col: term.buffer.cursorX, row: term.buffer.cursorY }; - })(); - `); + (function() { + return {col: term.buffer.active.cursorX, row: term.buffer.active.cursorY}; + })(); + `); } async function getDimensions(): Promise { diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 2c26bb0859..6829fd0aab 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -40,7 +40,7 @@ describe('API Integration Tests', function(): void { window.term.write('bar'); window.term.write('文'); `); - await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文'); + await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foobar文'); }); it('write with callback', async () => { @@ -50,7 +50,7 @@ describe('API Integration Tests', function(): void { window.term.write('bar', () => { window.__x += 'b'; }); window.term.write('文', () => { window.__x += 'c'; }); `); - await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文'); + await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foobar文'); await pollFor(page, `window.__x`, 'abc'); }); @@ -64,7 +64,7 @@ describe('API Integration Tests', function(): void { // 文 window.term.write(new Uint8Array([230, 150, 135])); `); - await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文'); + await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foobar文'); }); it('write - bytes (UTF8) with callback', async () => { @@ -77,7 +77,7 @@ describe('API Integration Tests', function(): void { // 文 window.term.write(new Uint8Array([230, 150, 135]), () => { window.__x += 'C'; }); `); - await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文'); + await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foobar文'); await pollFor(page, `window.__x`, 'ABC'); }); @@ -88,9 +88,9 @@ describe('API Integration Tests', function(): void { window.term.writeln('bar'); window.term.writeln('文'); `); - await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); - await pollFor(page, `window.term.buffer.getLine(1).translateToString(true)`, 'bar'); - await pollFor(page, `window.term.buffer.getLine(2).translateToString(true)`, '文'); + await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo'); + await pollFor(page, `window.term.buffer.active.getLine(1).translateToString(true)`, 'bar'); + await pollFor(page, `window.term.buffer.active.getLine(2).translateToString(true)`, '文'); }); it('writeln with callback', async () => { @@ -100,9 +100,9 @@ describe('API Integration Tests', function(): void { window.term.writeln('bar', () => { window.__x += '2'; }); window.term.writeln('文', () => { window.__x += '3'; }); `); - await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); - await pollFor(page, `window.term.buffer.getLine(1).translateToString(true)`, 'bar'); - await pollFor(page, `window.term.buffer.getLine(2).translateToString(true)`, '文'); + await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo'); + await pollFor(page, `window.term.buffer.active.getLine(1).translateToString(true)`, 'bar'); + await pollFor(page, `window.term.buffer.active.getLine(2).translateToString(true)`, '文'); await pollFor(page, `window.__x`, '123'); }); @@ -113,9 +113,9 @@ describe('API Integration Tests', function(): void { window.term.writeln(new Uint8Array([98, 97, 114])); window.term.writeln(new Uint8Array([230, 150, 135])); `); - await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo'); - await pollFor(page, `window.term.buffer.getLine(1).translateToString(true)`, 'bar'); - await pollFor(page, `window.term.buffer.getLine(2).translateToString(true)`, '文'); + await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo'); + await pollFor(page, `window.term.buffer.active.getLine(1).translateToString(true)`, 'bar'); + await pollFor(page, `window.term.buffer.active.getLine(2).translateToString(true)`, '文'); }); it('paste', async () => { @@ -143,10 +143,10 @@ describe('API Integration Tests', function(): void { `); await pollFor(page, `window.parsed`, 9); await page.evaluate(`window.term.clear()`); - await pollFor(page, `window.term.buffer.length`, 5); - await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'test9'); + await pollFor(page, `window.term.buffer.active.length`, 5); + await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'test9'); for (let i = 1; i < 5; i++) { - await pollFor(page, `window.term.buffer.getLine(${i}).translateToString(true)`, ''); + await pollFor(page, `window.term.buffer.active.getLine(${i}).translateToString(true)`, ''); } }); @@ -399,115 +399,147 @@ describe('API Integration Tests', function(): void { describe('buffer', () => { it('cursorX, cursorY', async () => { await openTerminal(page, { rows: 5, cols: 5 }); - assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0); - assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 0); + assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 0); + assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 0); await writeSync(page, 'foo'); - assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 3); - assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 0); + assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 3); + assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 0); await writeSync(page, '\\n'); - assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 3); - assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1); + assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 3); + assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 1); await writeSync(page, '\\r'); - assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0); - assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1); + assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 0); + assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 1); await writeSync(page, 'abcde'); - assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 5); - assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1); + assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 5); + assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 1); await writeSync(page, '\\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); + assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 0); + assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 4); }); it('viewportY', async () => { await openTerminal(page, { rows: 5 }); - assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0); + assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 0); await writeSync(page, '\\n\\n\\n\\n'); - assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0); + assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 0); await writeSync(page, '\\n'); - assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 1); + assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 1); await writeSync(page, '\\n\\n\\n\\n'); - assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 5); + assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 5); await page.evaluate(`window.term.scrollLines(-1)`); - assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 4); + assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 4); await page.evaluate(`window.term.scrollToTop()`); - assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0); + assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 0); }); it('baseY', async () => { await openTerminal(page, { rows: 5 }); - assert.equal(await page.evaluate(`window.term.buffer.baseY`), 0); + assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 0); await writeSync(page, '\\n\\n\\n\\n'); - assert.equal(await page.evaluate(`window.term.buffer.baseY`), 0); + assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 0); await writeSync(page, '\\n'); - assert.equal(await page.evaluate(`window.term.buffer.baseY`), 1); + assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 1); await writeSync(page, '\\n\\n\\n\\n'); - assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5); + assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 5); await page.evaluate(`window.term.scrollLines(-1)`); - assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5); + assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 5); await page.evaluate(`window.term.scrollToTop()`); - assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5); + assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 5); }); it('length', async () => { await openTerminal(page, { rows: 5 }); - assert.equal(await page.evaluate(`window.term.buffer.length`), 5); + assert.equal(await page.evaluate(`window.term.buffer.active.length`), 5); await writeSync(page, '\\n\\n\\n\\n'); - assert.equal(await page.evaluate(`window.term.buffer.length`), 5); + assert.equal(await page.evaluate(`window.term.buffer.active.length`), 5); await writeSync(page, '\\n'); - assert.equal(await page.evaluate(`window.term.buffer.length`), 6); + assert.equal(await page.evaluate(`window.term.buffer.active.length`), 6); await writeSync(page, '\\n\\n\\n\\n'); - assert.equal(await page.evaluate(`window.term.buffer.length`), 10); + assert.equal(await page.evaluate(`window.term.buffer.active.length`), 10); }); describe('getLine', () => { it('invalid index', async () => { await openTerminal(page, { rows: 5 }); - assert.equal(await page.evaluate(`window.term.buffer.getLine(-1)`), undefined); - assert.equal(await page.evaluate(`window.term.buffer.getLine(5)`), undefined); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(-1)`), undefined); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(5)`), undefined); }); it('isWrapped', async () => { await openTerminal(page, { 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); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).isWrapped`), false); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(1).isWrapped`), false); await writeSync(page, '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); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).isWrapped`), false); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(1).isWrapped`), false); await writeSync(page, '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); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).isWrapped`), false); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(1).isWrapped`), true); }); it('translateToString', async () => { await openTerminal(page, { 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)`), ''); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), ' '); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString(true)`), ''); await writeSync(page, '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'); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'foo '); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString(true)`), 'foo'); await writeSync(page, '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'); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'fooba'); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString(true)`), 'fooba'); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(1).translateToString(true)`), 'r'); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString(false, 1)`), 'ooba'); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString(false, 1, 3)`), 'oo'); }); it('getCell', async () => { await openTerminal(page, { 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).getChars()`), ''); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).getWidth()`), 1); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(-1)`), undefined); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(5)`), undefined); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(0).getChars()`), ''); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(0).getWidth()`), 1); await writeSync(page, 'a文'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).getChars()`), 'a'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).getWidth()`), 1); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(1).getChars()`), '文'); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(1).getWidth()`), 2); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(2).getChars()`), ''); - assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(2).getWidth()`), 0); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(0).getChars()`), 'a'); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(0).getWidth()`), 1); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(1).getChars()`), '文'); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(1).getWidth()`), 2); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(2).getChars()`), ''); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(2).getWidth()`), 0); }); }); + + it('active, normal, alternate', async () => { + await openTerminal(page, { cols: 5 }); + assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'normal'); + assert.equal(await page.evaluate(`window.term.buffer.normal.type`), 'normal'); + assert.equal(await page.evaluate(`window.term.buffer.alternate.type`), 'alternate'); + + await writeSync(page, 'norm '); + 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); + + await writeSync(page, '\\x1b[?47h\\r'); // use alternate screen buffer + assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'alternate'); + assert.equal(await page.evaluate(`window.term.buffer.normal.type`), 'normal'); + assert.equal(await page.evaluate(`window.term.buffer.alternate.type`), 'alternate'); + + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), ' '); + await writeSync(page, 'alt '); + assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'alt '); + assert.equal(await page.evaluate(`window.term.buffer.normal.getLine(0).translateToString()`), 'norm '); + assert.equal(await page.evaluate(`window.term.buffer.alternate.getLine(0).translateToString()`), 'alt '); + + await writeSync(page, '\\x1b[?47l\\r'); // use normal screen buffer + assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'normal'); + assert.equal(await page.evaluate(`window.term.buffer.normal.type`), 'normal'); + assert.equal(await page.evaluate(`window.term.buffer.alternate.type`), 'alternate'); + + 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 () => { @@ -633,7 +665,7 @@ describe('API Integration Tests', function(): void { window.calls.push('provide ' + position.x + ',' + position.y); cb({ range: { start: position, end: position }, - text: window.term.buffer.getLine(position.y - 1).getCell(position.x - 1).getChars(), + text: window.term.buffer.active.getLine(position.y - 1).getCell(position.x - 1).getChars(), activate: (_, text) => window.calls.push('activate ' + text), hover: () => window.calls.push('hover'), leave: () => window.calls.push('leave') diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 9af0279e33..9445d07548 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -574,7 +574,7 @@ declare module 'xterm' { * normal buffer or the alt buffer depending on what's running in the * terminal. */ - readonly buffer: IBuffer; + readonly buffer: IBufferNamespace; /** * (EXPERIMENTAL) Get all markers registered against the buffer. If the alt @@ -1168,6 +1168,11 @@ declare module 'xterm' { * Represents a terminal buffer. */ interface IBuffer { + /** + * The type of the buffer. + */ + readonly type: 'normal' | 'alternate'; + /** * The y position of the cursor. This ranges between `0` (when the * cursor is at baseY) and `Terminal.rows - 1` (when the cursor is on the @@ -1217,6 +1222,33 @@ declare module 'xterm' { getNullCell(): IBufferCell; } + /** + * Represents the terminal's set of buffers. + */ + interface IBufferNamespace { + /** + * The active buffer, this will either be the normal or alternate buffers. + */ + readonly active: IBuffer; + + /** + * The normal buffer. + */ + readonly normal: IBuffer; + + /** + * The alternate buffer, this becomes the active buffer when an application + * enters this mode via DECSET (`CSI ? 4 7 h`) + */ + readonly alternate: IBuffer; + + /** + * Adds an event listener for when the active buffer changes. + * @returns an `IDisposable` to stop listening. + */ + onBufferChange: IEvent; + } + /** * Represents a line in the terminal's buffer. */