move stuff to inputHandler and move scroll to bufferService
This commit is contained in:
+935
-1310
File diff suppressed because it is too large
Load Diff
@@ -147,7 +147,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.register(this._inputHandler.onRequestBell(() => this.bell()));
|
||||
this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)));
|
||||
this.register(this._inputHandler.onRequestReset(() => this.reset()));
|
||||
this.register(this._inputHandler.onRequestScroll((eraseAttr, isWrapped) => this.scroll(eraseAttr, isWrapped || undefined)));
|
||||
this.register(this._inputHandler.onRequestScroll((eraseAttr, isWrapped) => this._bufferService.scroll(eraseAttr, isWrapped || undefined)));
|
||||
this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type)));
|
||||
this.register(this._inputHandler.onAnsiColorChange((event) => this._changeAnsiColor(event)));
|
||||
this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove));
|
||||
@@ -1010,7 +1010,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
|
||||
if (!this._compositionHelper!.keydown(event)) {
|
||||
if (this.buffer.ybase !== this.buffer.ydisp) {
|
||||
this.scrollToBottom();
|
||||
this._bufferService.scrollToBottom();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
+26
-11
@@ -58,8 +58,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||
protected _inputHandler: InputHandler;
|
||||
private _writeBuffer: WriteBuffer;
|
||||
private _windowsMode: IDisposable | undefined;
|
||||
/** An IBufferline to clone/copy from for new blank lines */
|
||||
private _cachedBlankLine: IBufferLine | undefined;
|
||||
|
||||
|
||||
private _onBinary = new EventEmitter<string>();
|
||||
public get onBinary(): IEvent<string> { return this._onBinary.event; }
|
||||
@@ -98,21 +97,20 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||
this._instantiationService = new InstantiationService();
|
||||
this.optionsService = new OptionsService(options);
|
||||
this._instantiationService.setService(IOptionsService, this.optionsService);
|
||||
this._bufferService = this.register(this._instantiationService.createInstance(BufferService));
|
||||
this._instantiationService.setService(IBufferService, this._bufferService);
|
||||
this._logService = this._instantiationService.createInstance(LogService);
|
||||
this._instantiationService.setService(ILogService, this._logService);
|
||||
this._coreService = this.register(this._instantiationService.createInstance(CoreService, () => this.scrollToBottom()));
|
||||
this._instantiationService.setService(ICoreService, this._coreService);
|
||||
this._coreMouseService = this._instantiationService.createInstance(CoreMouseService);
|
||||
this._instantiationService.setService(ICoreMouseService, this._coreMouseService);
|
||||
this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService);
|
||||
this._instantiationService.setService(IDirtyRowService, this._dirtyRowService);
|
||||
this.unicodeService = this._instantiationService.createInstance(UnicodeService);
|
||||
this._instantiationService.setService(IUnicodeService, this.unicodeService);
|
||||
this._charsetService = this._instantiationService.createInstance(CharsetService);
|
||||
this._instantiationService.setService(ICharsetService, this._charsetService);
|
||||
|
||||
this._bufferService = this.register(this._instantiationService.createInstance(BufferService));
|
||||
this._instantiationService.setService(IBufferService, this._bufferService);
|
||||
this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService);
|
||||
this._instantiationService.setService(IDirtyRowService, this._dirtyRowService);
|
||||
this._coreService = this.register(this._instantiationService.createInstance(CoreService, () => this._bufferService.scrollToBottom()));
|
||||
this._instantiationService.setService(ICoreService, this._coreService);
|
||||
this._coreMouseService = this._instantiationService.createInstance(CoreMouseService);
|
||||
this._instantiationService.setService(ICoreMouseService, this._coreMouseService);
|
||||
// Register input handler and handle/forward events
|
||||
this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService);
|
||||
this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed));
|
||||
@@ -137,6 +135,23 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||
this._windowsMode = undefined;
|
||||
}
|
||||
|
||||
public scrollLines(disp: number, suppressScrollEvent?: boolean): void {
|
||||
this._bufferService.scrollLines(disp, suppressScrollEvent);
|
||||
}
|
||||
|
||||
public scrollPages(pageCount: number): void {
|
||||
this._bufferService.scrollPages(pageCount);
|
||||
}
|
||||
public scrollToTop(): void {
|
||||
this._bufferService.scrollToTop();
|
||||
}
|
||||
public scrollToBottom(): void {
|
||||
this._bufferService.scrollToBottom();
|
||||
}
|
||||
public scrollToLine(line: number): void {
|
||||
this._bufferService.scrollToLine(line);
|
||||
}
|
||||
|
||||
public write(data: string | Uint8Array, callback?: () => void): void {
|
||||
this._writeBuffer.write(data, callback);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { clone } from 'common/Clone';
|
||||
import { BufferService } from 'common/services/BufferService';
|
||||
import { CoreService } from 'common/services/CoreService';
|
||||
import { OscHandler } from 'common/parser/OscParser';
|
||||
import { DirtyRowService } from 'common/services/DirtyRowService';
|
||||
|
||||
function getCursor(bufferService: IBufferService): number[] {
|
||||
return [
|
||||
@@ -66,11 +67,119 @@ describe('InputHandler', () => {
|
||||
optionsService = new MockOptionsService();
|
||||
bufferService = new BufferService(optionsService);
|
||||
bufferService.resize(80, 30);
|
||||
coreService = new CoreService(() => {}, bufferService, new MockLogService(), optionsService);
|
||||
coreService = new CoreService(() => { }, bufferService, new MockLogService(), optionsService);
|
||||
|
||||
inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService());
|
||||
});
|
||||
|
||||
describe('Terminal InputHandler integration', () => {
|
||||
function getLines(limit: number): string[] {
|
||||
const res: string[] = [];
|
||||
for (let i = 0; i < limit; ++i) {
|
||||
res.push(bufferService.buffers.active.lines.get(i)!.translateToString(true));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// This suite cannot live in InputHandler unless Terminal.scroll moved into IBufferService
|
||||
describe('SL/SR/DECIC/DECDC', () => {
|
||||
|
||||
it('SL (scrollLeft)', async () => {
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
assert.deepEqual(getLines(5), ['12345', '2345', '2345', '2345', '2345', '2345']);
|
||||
inputHandler.parseP('\x1b[0 @');
|
||||
assert.deepEqual(getLines(5), ['12345', '345', '345', '345', '345', '345']);
|
||||
inputHandler.parseP('\x1b[2 @');
|
||||
assert.deepEqual(getLines(5), ['12345', '5', '5', '5', '5', '5']);
|
||||
});
|
||||
it('SR (scrollRight)', async () => {
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
inputHandler.parseP('\x1b[ A');
|
||||
assert.deepEqual(getLines(5), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']);
|
||||
inputHandler.parseP('\x1b[0 A');
|
||||
assert.deepEqual(getLines(5), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']);
|
||||
inputHandler.parseP('\x1b[2 A');
|
||||
assert.deepEqual(getLines(5), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']);
|
||||
});
|
||||
it('insertColumns (DECIC)', async () => {
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
inputHandler.parseP('\x1b[3;3H');
|
||||
inputHandler.parseP('\x1b[\'}');
|
||||
assert.deepEqual(getLines(5), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
|
||||
inputHandler.reset();
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
inputHandler.parseP('\x1b[3;3H');
|
||||
inputHandler.parseP('\x1b[1\'}');
|
||||
assert.deepEqual(getLines(5), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
|
||||
inputHandler.reset();
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
inputHandler.parseP('\x1b[3;3H');
|
||||
inputHandler.parseP('\x1b[2\'}');
|
||||
assert.deepEqual(getLines(5), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']);
|
||||
});
|
||||
it('deleteColumns (DECDC)', async () => {
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
inputHandler.parseP('\x1b[3;3H');
|
||||
inputHandler.parseP('\x1b[\'~');
|
||||
assert.deepEqual(getLines(5), ['12345', '1245', '1245', '1245', '1245', '1245']);
|
||||
inputHandler.reset();
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
inputHandler.parseP('\x1b[3;3H');
|
||||
inputHandler.parseP('\x1b[1\'~');
|
||||
assert.deepEqual(getLines(5), ['12345', '1245', '1245', '1245', '1245', '1245']);
|
||||
inputHandler.reset();
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
inputHandler.parseP('\x1b[3;3H');
|
||||
inputHandler.parseP('\x1b[2\'~');
|
||||
assert.deepEqual(getLines(5), ['12345', '125', '125', '125', '125', '125']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BS with reverseWraparound set/unset', () => {
|
||||
const ttyBS = '\x08 \x08'; // tty ICANON sends <BS SP BS> on pressing BS
|
||||
|
||||
describe('reverseWraparound set', () => {
|
||||
it('should not reverse outside of scroll margins', async () => {
|
||||
// prepare buffer content
|
||||
inputHandler.parseP('#####abcdefghijklmnopqrstuvwxy');
|
||||
assert.deepEqual(getLines(5), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']);
|
||||
assert.equal(bufferService.buffers.active.ydisp, 1);
|
||||
assert.equal(bufferService.buffers.active.x, 5);
|
||||
assert.equal(bufferService.buffers.active.y, 4);
|
||||
inputHandler.parseP(ttyBS.repeat(100));
|
||||
assert.deepEqual(getLines(5), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' y']);
|
||||
|
||||
inputHandler.parseP('\x1b[?45h');
|
||||
inputHandler.parseP('uvwxy');
|
||||
|
||||
// set top/bottom to 1/3 (0-based)
|
||||
inputHandler.parseP('\x1b[2;4r');
|
||||
// place cursor below scroll bottom
|
||||
bufferService.buffers.active.x = 5;
|
||||
bufferService.buffers.active.y = 4;
|
||||
inputHandler.parseP(ttyBS.repeat(100));
|
||||
assert.deepEqual(getLines(5), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' ']);
|
||||
|
||||
inputHandler.parseP('uvwxy');
|
||||
// place cursor within scroll margins
|
||||
bufferService.buffers.active.x = 5;
|
||||
bufferService.buffers.active.y = 3;
|
||||
inputHandler.parseP(ttyBS.repeat(100));
|
||||
assert.deepEqual(getLines(5), ['#####', 'abcde', ' ', ' ', ' ', 'uvwxy']);
|
||||
assert.equal(bufferService.buffers.active.x, 0);
|
||||
assert.equal(bufferService.buffers.active.y, bufferService.buffers.active.scrollTop); // stops at 0, scrollTop
|
||||
|
||||
inputHandler.parseP('fghijklmnopqrst');
|
||||
// place cursor above scroll top
|
||||
bufferService.buffers.active.x = 5;
|
||||
bufferService.buffers.active.y = 0;
|
||||
inputHandler.parseP(ttyBS.repeat(100));
|
||||
assert.deepEqual(getLines(5), ['#####', ' ', 'fghij', 'klmno', 'pqrst', 'uvwxy']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('save and restore cursor', () => {
|
||||
bufferService.buffer.x = 1;
|
||||
bufferService.buffer.y = 2;
|
||||
@@ -140,7 +249,7 @@ describe('InputHandler', () => {
|
||||
assert.equal(coreService.decPrivateModes.bracketedPasteMode, false);
|
||||
});
|
||||
});
|
||||
describe('regression tests', function(): void {
|
||||
describe('regression tests', function (): void {
|
||||
function termContent(bufferService: IBufferService, trim: boolean): string[] {
|
||||
const result = [];
|
||||
for (let i = 0; i < bufferService.rows; ++i) result.push(bufferService.buffer.lines.get(i)!.translateToString(trim));
|
||||
@@ -430,6 +539,68 @@ describe('InputHandler', () => {
|
||||
await inputHandler.parseP('¥¥¥');
|
||||
assert.deepEqual(getLines(bufferService, 2), ['¥¥', '¥']);
|
||||
});
|
||||
|
||||
|
||||
// This suite cannot live in InputHandler unless Terminal.scroll moved into IBufferService
|
||||
describe('SL/SR/DECIC/DECDC', () => {
|
||||
it('SL (scrollLeft)', async () => {
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
inputHandler.parseP('\x1b[ @');
|
||||
assert.deepEqual(getLines(bufferService, 5), ['12345', '2345', '2345', '2345', '2345', '2345']);
|
||||
inputHandler.parseP('\x1b[0 @');
|
||||
assert.deepEqual(getLines(bufferService, 5), ['12345', '345', '345', '345', '345', '345']);
|
||||
inputHandler.parseP('\x1b[2 @');
|
||||
assert.deepEqual(getLines(bufferService, 5), ['12345', '5', '5', '5', '5', '5']);
|
||||
});
|
||||
it('SR (scrollRight)', async () => {
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
inputHandler.parseP('\x1b[ A');
|
||||
assert.deepEqual(getLines(bufferService, 5), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']);
|
||||
inputHandler.parseP('\x1b[0 A');
|
||||
assert.deepEqual(getLines(bufferService, 5), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']);
|
||||
inputHandler.parseP('\x1b[2 A');
|
||||
assert.deepEqual(getLines(bufferService, 5), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']);
|
||||
});
|
||||
it('insertColumns (DECIC)', async () => {
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
inputHandler.parseP('\x1b[3;3H');
|
||||
inputHandler.parseP('\x1b[\'}');
|
||||
assert.deepEqual(getLines(bufferService, 5), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
|
||||
inputHandler.reset();
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
inputHandler.parseP('\x1b[3;3H');
|
||||
inputHandler.parseP('\x1b[1\'}');
|
||||
assert.deepEqual(getLines(bufferService, 5), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
|
||||
inputHandler.reset();
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
inputHandler.parseP('\x1b[3;3H');
|
||||
inputHandler.parseP('\x1b[2\'}');
|
||||
assert.deepEqual(getLines(bufferService, 5), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']);
|
||||
});
|
||||
it('deleteColumns (DECDC)', async () => {
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
inputHandler.parseP('\x1b[3;3H');
|
||||
inputHandler.parseP('\x1b[\'~');
|
||||
assert.deepEqual(getLines(bufferService, 5), ['12345', '1245', '1245', '1245', '1245', '1245']);
|
||||
inputHandler.reset();
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
inputHandler.parseP('\x1b[3;3H');
|
||||
inputHandler.parseP('\x1b[1\'~');
|
||||
assert.deepEqual(getLines(bufferService, 5), ['12345', '1245', '1245', '1245', '1245', '1245']);
|
||||
inputHandler.reset();
|
||||
inputHandler.parseP('12345'.repeat(6));
|
||||
inputHandler.parseP('\x1b[3;3H');
|
||||
inputHandler.parseP('\x1b[2\'~');
|
||||
assert.deepEqual(getLines(bufferService, 5), ['12345', '125', '125', '125', '125', '125']);
|
||||
});
|
||||
});
|
||||
it('should fire the onScroll event', (done) => {
|
||||
bufferService.onScroll(e => {
|
||||
assert.equal(typeof e, 'number');
|
||||
done();
|
||||
});
|
||||
bufferService.scroll(DEFAULT_ATTR_DATA.clone());
|
||||
});
|
||||
});
|
||||
|
||||
describe('alt screen', () => {
|
||||
@@ -1240,7 +1411,7 @@ describe('InputHandler', () => {
|
||||
await inputHandler.parseP('\x1b[6H\x1b[2Mm');
|
||||
assert.deepEqual(getLines(bufferService), ['0', '1', '2', '3', '4', 'm', '6', '7', '8', '9']);
|
||||
await inputHandler.parseP('\x1b[3H\x1b[2Mn');
|
||||
assert.deepEqual(getLines(bufferService), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']);
|
||||
assert.deepEqual(getLines(bufferService), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']);
|
||||
});
|
||||
});
|
||||
it('should parse big chunks in smaller subchunks', async () => {
|
||||
@@ -1814,13 +1985,14 @@ describe('InputHandler - async handlers', () => {
|
||||
let bufferService: IBufferService;
|
||||
let coreService: ICoreService;
|
||||
let optionsService: MockOptionsService;
|
||||
let dirtyRowService: MockDirtyRowService;
|
||||
let inputHandler: TestInputHandler;
|
||||
|
||||
beforeEach(() => {
|
||||
optionsService = new MockOptionsService();
|
||||
bufferService = new BufferService(optionsService);
|
||||
bufferService.resize(80, 30);
|
||||
coreService = new CoreService(() => {}, bufferService, new MockLogService(), optionsService);
|
||||
coreService = new CoreService(() => { }, bufferService, new MockLogService(), optionsService);
|
||||
coreService.onData(data => { console.log(data); });
|
||||
|
||||
inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService());
|
||||
@@ -1829,7 +2001,7 @@ describe('InputHandler - async handlers', () => {
|
||||
it('async CUP with CPR check', async () => {
|
||||
const cup: number[][] = [];
|
||||
const cpr: number[][] = [];
|
||||
inputHandler.registerCsiHandler({final: 'H'}, async params => {
|
||||
inputHandler.registerCsiHandler({ final: 'H' }, async params => {
|
||||
cup.push(params.toArray() as number[]);
|
||||
await new Promise(res => setTimeout(res, 50));
|
||||
// late call of real repositioning
|
||||
@@ -1855,7 +2027,7 @@ describe('InputHandler - async handlers', () => {
|
||||
assert.deepEqual(getLines(bufferService, 2), ['hello world!', 'second line']);
|
||||
});
|
||||
it('async DCS between', async () => {
|
||||
inputHandler.registerDcsHandler({final: 'a'}, async (data, params) => {
|
||||
inputHandler.registerDcsHandler({ final: 'a' }, async (data, params) => {
|
||||
await new Promise(res => setTimeout(res, 50));
|
||||
assert.deepEqual(getLines(bufferService, 2), ['hello world!', '']);
|
||||
assert.equal(data, 'some data');
|
||||
|
||||
@@ -9,7 +9,7 @@ import { clone } from 'common/Clone';
|
||||
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
|
||||
import { IBufferSet, IBuffer } from 'common/buffer/Types';
|
||||
import { BufferSet } from 'common/buffer/BufferSet';
|
||||
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes } from 'common/Types';
|
||||
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes, IAttributeData } from 'common/Types';
|
||||
import { UnicodeV6 } from 'common/input/UnicodeV6';
|
||||
|
||||
export class MockBufferService implements IBufferService {
|
||||
@@ -17,6 +17,7 @@ export class MockBufferService implements IBufferService {
|
||||
public get buffer(): IBuffer { return this.buffers.active; }
|
||||
public buffers: IBufferSet = {} as any;
|
||||
public onResize: IEvent<{ cols: number, rows: number }> = new EventEmitter<{ cols: number, rows: number }>().event;
|
||||
public onScroll: IEvent<number> = new EventEmitter<number>().event;
|
||||
public isUserScrolling: boolean = false;
|
||||
constructor(
|
||||
public cols: number,
|
||||
@@ -25,23 +26,41 @@ export class MockBufferService implements IBufferService {
|
||||
) {
|
||||
this.buffers = new BufferSet(optionsService, this);
|
||||
}
|
||||
public scrollPages(pageCount: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public scrollToTop(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public scrollToLine(line: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public scroll(eraseAttr: IAttributeData, isWrapped: boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public scrollToBottom(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public scrollLines(disp: number, suppressScrollEvent?: boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public resize(cols: number, rows: number): void {
|
||||
this.cols = cols;
|
||||
this.rows = rows;
|
||||
}
|
||||
public reset(): void {}
|
||||
public reset(): void { }
|
||||
}
|
||||
|
||||
export class MockCoreMouseService implements ICoreMouseService {
|
||||
public areMouseEventsActive: boolean = false;
|
||||
public activeEncoding: string = '';
|
||||
public activeProtocol: string = '';
|
||||
public addEncoding(name: string): void {}
|
||||
public addProtocol(name: string): void {}
|
||||
public reset(): void {}
|
||||
public addEncoding(name: string): void { }
|
||||
public addProtocol(name: string): void { }
|
||||
public reset(): void { }
|
||||
public triggerMouseEvent(event: ICoreMouseEvent): boolean { return false; }
|
||||
public onProtocolChange: IEvent<CoreMouseEventType> = new EventEmitter<CoreMouseEventType>().event;
|
||||
public explainEvents(events: CoreMouseEventType): {[event: string]: boolean} {
|
||||
public explainEvents(events: CoreMouseEventType): { [event: string]: boolean } {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
@@ -50,9 +69,9 @@ export class MockCharsetService implements ICharsetService {
|
||||
public serviceBrand: any;
|
||||
public charset: ICharset | undefined;
|
||||
public glevel: number = 0;
|
||||
public reset(): void {}
|
||||
public setgLevel(g: number): void {}
|
||||
public setgCharset(g: number, charset: ICharset): void {}
|
||||
public reset(): void { }
|
||||
public setgLevel(g: number): void { }
|
||||
public setgCharset(g: number, charset: ICharset): void { }
|
||||
}
|
||||
|
||||
export class MockCoreService implements ICoreService {
|
||||
@@ -75,28 +94,28 @@ export class MockCoreService implements ICoreService {
|
||||
public onData: IEvent<string> = new EventEmitter<string>().event;
|
||||
public onUserInput: IEvent<void> = new EventEmitter<void>().event;
|
||||
public onBinary: IEvent<string> = new EventEmitter<string>().event;
|
||||
public reset(): void {}
|
||||
public triggerDataEvent(data: string, wasUserInput?: boolean): void {}
|
||||
public triggerBinaryEvent(data: string): void {}
|
||||
public reset(): void { }
|
||||
public triggerDataEvent(data: string, wasUserInput?: boolean): void { }
|
||||
public triggerBinaryEvent(data: string): void { }
|
||||
}
|
||||
|
||||
export class MockDirtyRowService implements IDirtyRowService {
|
||||
public serviceBrand: any;
|
||||
public start: number = 0;
|
||||
public end: number = 0;
|
||||
public clearRange(): void {}
|
||||
public markDirty(y: number): void {}
|
||||
public markRangeDirty(y1: number, y2: number): void {}
|
||||
public markAllDirty(): void {}
|
||||
public clearRange(): void { }
|
||||
public markDirty(y: number): void { }
|
||||
public markRangeDirty(y1: number, y2: number): void { }
|
||||
public markAllDirty(): void { }
|
||||
}
|
||||
|
||||
export class MockLogService implements ILogService {
|
||||
public serviceBrand: any;
|
||||
public logLevel = LogLevelEnum.DEBUG;
|
||||
public debug(message: any, ...optionalParams: any[]): void {}
|
||||
public info(message: any, ...optionalParams: any[]): void {}
|
||||
public warn(message: any, ...optionalParams: any[]): void {}
|
||||
public error(message: any, ...optionalParams: any[]): void {}
|
||||
public debug(message: any, ...optionalParams: any[]): void { }
|
||||
public info(message: any, ...optionalParams: any[]): void { }
|
||||
public warn(message: any, ...optionalParams: any[]): void { }
|
||||
public error(message: any, ...optionalParams: any[]): void { }
|
||||
}
|
||||
|
||||
export class MockOptionsService implements IOptionsService {
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { IBufferService, IDirtyRowService, IInstantiationService, IOptionsService } from 'common/services/Services';
|
||||
import { BufferSet } from 'common/buffer/BufferSet';
|
||||
import { IBufferSet, IBuffer } from 'common/buffer/Types';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IAttributeData, IBufferLine } from 'common/Types';
|
||||
import { DirtyRowService } from 'common/services/DirtyRowService';
|
||||
|
||||
export const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars
|
||||
export const MINIMUM_ROWS = 1;
|
||||
@@ -23,9 +25,16 @@ export class BufferService extends Disposable implements IBufferService {
|
||||
|
||||
private _onResize = new EventEmitter<{ cols: number, rows: number }>();
|
||||
public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; }
|
||||
private _onScroll = new EventEmitter<number>();
|
||||
public get onScroll(): IEvent<number> { return this._onScroll.event; }
|
||||
|
||||
public get buffer(): IBuffer { return this.buffers.active; }
|
||||
|
||||
/** An IBufferline to clone/copy from for new blank lines */
|
||||
private _cachedBlankLine: IBufferLine | undefined;
|
||||
|
||||
private _dirtyRowService: IDirtyRowService | undefined;
|
||||
|
||||
constructor(
|
||||
@IOptionsService private _optionsService: IOptionsService
|
||||
) {
|
||||
@@ -52,4 +61,134 @@ export class BufferService extends Disposable implements IBufferService {
|
||||
this.buffers.reset();
|
||||
this.isUserScrolling = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the terminal down 1 row, creating a blank line.
|
||||
* @param isWrapped Whether the new line is wrapped from the previous line.
|
||||
*/
|
||||
public scroll(eraseAttr: IAttributeData, isWrapped: boolean = false): void {
|
||||
const buffer = this.buffer;
|
||||
|
||||
let newLine: IBufferLine | undefined;
|
||||
newLine = this._cachedBlankLine;
|
||||
if (!newLine || newLine.length !== this.cols || newLine.getFg(0) !== eraseAttr.fg || newLine.getBg(0) !== eraseAttr.bg) {
|
||||
newLine = buffer.getBlankLine(eraseAttr, isWrapped);
|
||||
this._cachedBlankLine = newLine;
|
||||
}
|
||||
newLine.isWrapped = isWrapped;
|
||||
|
||||
const topRow = buffer.ybase + buffer.scrollTop;
|
||||
const bottomRow = buffer.ybase + buffer.scrollBottom;
|
||||
|
||||
if (buffer.scrollTop === 0) {
|
||||
// Determine whether the buffer is going to be trimmed after insertion.
|
||||
const willBufferBeTrimmed = buffer.lines.isFull;
|
||||
|
||||
// Insert the line using the fastest method
|
||||
if (bottomRow === buffer.lines.length - 1) {
|
||||
if (willBufferBeTrimmed) {
|
||||
buffer.lines.recycle().copyFrom(newLine);
|
||||
} else {
|
||||
buffer.lines.push(newLine.clone());
|
||||
}
|
||||
} else {
|
||||
buffer.lines.splice(bottomRow + 1, 0, newLine.clone());
|
||||
}
|
||||
|
||||
// Only adjust ybase and ydisp when the buffer is not trimmed
|
||||
if (!willBufferBeTrimmed) {
|
||||
buffer.ybase++;
|
||||
// Only scroll the ydisp with ybase if the user has not scrolled up
|
||||
if (!this.isUserScrolling) {
|
||||
buffer.ydisp++;
|
||||
}
|
||||
} else {
|
||||
// When the buffer is full and the user has scrolled up, keep the text
|
||||
// stable unless ydisp is right at the top
|
||||
if (this.isUserScrolling) {
|
||||
buffer.ydisp = Math.max(buffer.ydisp - 1, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// scrollTop is non-zero which means no line will be going to the
|
||||
// scrollback, instead we can just shift them in-place.
|
||||
const scrollRegionHeight = bottomRow - topRow + 1 /* as it's zero-based */;
|
||||
buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1);
|
||||
buffer.lines.set(bottomRow, newLine.clone());
|
||||
}
|
||||
|
||||
// Move the viewport to the bottom of the buffer unless the user is
|
||||
// scrolling.
|
||||
if (!this.isUserScrolling) {
|
||||
buffer.ydisp = buffer.ybase;
|
||||
}
|
||||
|
||||
// Flag rows that need updating
|
||||
if (!this._dirtyRowService) {
|
||||
this._dirtyRowService = new DirtyRowService(this);
|
||||
}
|
||||
this._dirtyRowService?.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
|
||||
|
||||
this._onScroll.fire(buffer.ydisp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the display of the terminal
|
||||
* @param disp The number of lines to scroll down (negative scroll up).
|
||||
* @param suppressScrollEvent Don't emit the scroll event as scrollLines. This is used
|
||||
* to avoid unwanted events being handled by the viewport when the event was triggered from the
|
||||
* viewport originally.
|
||||
*/
|
||||
public scrollLines(disp: number, suppressScrollEvent?: boolean): void {
|
||||
const buffer = this.buffer;
|
||||
if (disp < 0) {
|
||||
if (buffer.ydisp === 0) {
|
||||
return;
|
||||
}
|
||||
this.isUserScrolling = true;
|
||||
} else if (disp + buffer.ydisp >= buffer.ybase) {
|
||||
this.isUserScrolling = false;
|
||||
}
|
||||
|
||||
const oldYdisp = buffer.ydisp;
|
||||
buffer.ydisp = Math.max(Math.min(buffer.ydisp + disp, buffer.ybase), 0);
|
||||
|
||||
// No change occurred, don't trigger scroll/refresh
|
||||
if (oldYdisp === buffer.ydisp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!suppressScrollEvent) {
|
||||
this._onScroll.fire(buffer.ydisp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the display of the terminal by a number of pages.
|
||||
* @param pageCount The number of pages to scroll (negative scrolls up).
|
||||
*/
|
||||
public scrollPages(pageCount: number): void {
|
||||
this.scrollLines(pageCount * (this.rows - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the display of the terminal to the top.
|
||||
*/
|
||||
public scrollToTop(): void {
|
||||
this.scrollLines(-this.buffer.ydisp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the display of the terminal to the bottom.
|
||||
*/
|
||||
public scrollToBottom(): void {
|
||||
this.scrollLines(this.buffer.ybase - this.buffer.ydisp);
|
||||
}
|
||||
|
||||
public scrollToLine(line: number): void {
|
||||
const scrollAmount = line - this.buffer.ydisp;
|
||||
if (scrollAmount !== 0) {
|
||||
this.scrollLines(scrollAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { IEvent } from 'common/EventEmitter';
|
||||
import { IBuffer, IBufferSet } from 'common/buffer/Types';
|
||||
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes } from 'common/Types';
|
||||
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData } from 'common/Types';
|
||||
import { createDecorator } from 'common/services/ServiceRegistry';
|
||||
|
||||
export const IBufferService = createDecorator<IBufferService>('BufferService');
|
||||
@@ -17,9 +17,14 @@ export interface IBufferService {
|
||||
readonly buffer: IBuffer;
|
||||
readonly buffers: IBufferSet;
|
||||
isUserScrolling: boolean;
|
||||
|
||||
onResize: IEvent<{ cols: number, rows: number }>;
|
||||
|
||||
onScroll: IEvent<number>;
|
||||
scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void;
|
||||
scrollToBottom(): void;
|
||||
scrollToTop(): void;
|
||||
scrollToLine(line: number): void;
|
||||
scrollLines(disp: number, suppressScrollEvent?: boolean): void;
|
||||
scrollPages(pageCount: number): void;
|
||||
resize(cols: number, rows: number): void;
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user