Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Buffer API #2074

Merged
merged 15 commits into from
May 12, 2019
52 changes: 50 additions & 2 deletions src/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { ICharset, IAttributeData, ICellData, IBufferLine, CharData } from './core/Types';
import { ICircularList } from './common/Types';
Expand Down Expand Up @@ -195,7 +195,7 @@ export interface ILinkifierEvent {
fg: number;
}

export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
screenElement: HTMLElement;
selectionManager: ISelectionManager;
charMeasure: ICharMeasure;
Expand All @@ -220,6 +220,54 @@ 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[];
onCursorMove: IEvent<void>;
onData: IEvent<string>;
onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>;
onLineFeed: IEvent<void>;
onScroll: IEvent<number>;
onSelectionChange: IEvent<void>;
onRender: IEvent<{ start: number, end: number }>;
onResize: IEvent<{ cols: number, rows: number }>;
onTitleChange: IEvent<string>;
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;
}
Expand Down
134 changes: 128 additions & 6 deletions src/public/Terminal.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any> {
Expand All @@ -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<any> {
Expand All @@ -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)`), '');
}
});

Expand Down Expand Up @@ -231,6 +231,128 @@ describe('API Integration Tests', () => {
assert.deepEqual(await page.evaluate(`window.calls`), ['foo']);
});
});

describe('buffer', () => {
it('cursorX, cursorY', async function(): Promise<any> {
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<any> {
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<any> {
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<any> {
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);
});

describe('getLine', () => {
it('invalid index', async function(): Promise<any> {
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<any> {
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<any> {
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<any> {
this.timeout(10000);
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文')`);
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);
});
});
});
});

async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
Expand Down
44 changes: 42 additions & 2 deletions src/public/Terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
* @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, IBuffer } from '../Types';
import { IBufferLine } from '../core/Types';
import { Terminal as TerminalCore } from '../Terminal';
import * as Strings from '../Strings';
import { IEvent } from '../common/EventEmitter2';
Expand All @@ -30,6 +31,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(): ReadonlyArray<IMarker> { return this._core.markers; }
public blur(): void {
this._core.blur();
Expand Down Expand Up @@ -169,3 +171,41 @@ 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 | undefined {
const line = this._buffer.lines.get(y);
if (!line) {
return undefined;
}
return new BufferLineApiView(line);
}
}

class BufferLineApiView implements IBufferLineApi {
constructor(private _line: IBufferLine) {}

public get isWrapped(): boolean { return this._line.isWrapped; }
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);
}
}

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); }
}
4 changes: 2 additions & 2 deletions src/renderer/dom/DomRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ export class DomRenderer extends Disposable 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 +=
Expand Down
Loading