From 1067ec19a8f323c953c904e8c458ffbc07c10ce2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 1 Aug 2023 05:31:30 -0700 Subject: [PATCH] Implement smooth scroll for pages/top/bottom, add viewport request event --- src/browser/Terminal.test.ts | 19 +++++++++--------- src/browser/Terminal.ts | 9 +++------ src/browser/TestUtils.test.ts | 6 +++++- src/browser/Types.d.ts | 19 ++++++++++++++++++ src/browser/Viewport.ts | 11 +++++++---- src/common/CoreTerminal.ts | 28 +++++++++++---------------- src/common/services/BufferService.ts | 29 ---------------------------- src/common/services/Services.ts | 4 ---- 8 files changed, 55 insertions(+), 70 deletions(-) diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index 787d1670..6c4c9326 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -29,6 +29,7 @@ describe('Terminal', () => { term.refresh = () => { }; (term as any).renderer = new MockRenderer(); term.viewport = new MockViewport(); + term.viewport.onRequestScrollLines(e => term.scrollLines(e.amount, e.suppressScrollEvent, ScrollSource.VIEWPORT)); (term as any)._compositionHelper = new MockCompositionHelper(); (term as any).element = { classList: { @@ -258,27 +259,27 @@ describe('Terminal', () => { }); it('should scroll a single line', () => { assert.equal(term.buffer.ydisp, startYDisp); - term.scrollLines(-1, undefined, ScrollSource.VIEWPORT); + term.scrollLines(-1); assert.equal(term.buffer.ydisp, startYDisp - 1); - term.scrollLines(1, undefined, ScrollSource.VIEWPORT); + term.scrollLines(1); assert.equal(term.buffer.ydisp, startYDisp); }); it('should scroll multiple lines', () => { assert.equal(term.buffer.ydisp, startYDisp); - term.scrollLines(-5, undefined, ScrollSource.VIEWPORT); + term.scrollLines(-5); assert.equal(term.buffer.ydisp, startYDisp - 5); - term.scrollLines(5, undefined, ScrollSource.VIEWPORT); + term.scrollLines(5); assert.equal(term.buffer.ydisp, startYDisp); }); it('should not scroll beyond the bounds of the buffer', () => { assert.equal(term.buffer.ydisp, startYDisp); - term.scrollLines(1, undefined, ScrollSource.VIEWPORT); + term.scrollLines(1); assert.equal(term.buffer.ydisp, startYDisp); for (let i = 0; i < startYDisp; i++) { - term.scrollLines(-1, undefined, ScrollSource.VIEWPORT); + term.scrollLines(-1); } assert.equal(term.buffer.ydisp, 0); - term.scrollLines(-1, undefined, ScrollSource.VIEWPORT); + term.scrollLines(-1); assert.equal(term.buffer.ydisp, 0); }); }); @@ -329,7 +330,7 @@ describe('Terminal', () => { startYDisp = (term.rows * 2) + 1; }); it('should scroll to the bottom', () => { - term.scrollLines(-1, undefined, ScrollSource.VIEWPORT); + term.scrollLines(-1); term.scrollToBottom(); assert.equal(term.buffer.ydisp, startYDisp); term.scrollPages(-1); @@ -398,7 +399,7 @@ describe('Terminal', () => { }); assert.equal(term.buffer.ydisp, startYDisp); - term.scrollLines(-1, undefined, ScrollSource.VIEWPORT); + term.scrollLines(-1); assert.equal(term.buffer.ydisp, startYDisp - 1); term.keyPress({ keyCode: 0 }); assert.equal(term.buffer.ydisp, startYDisp - 1); diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 7db4771c..8f32c6cf 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -497,11 +497,8 @@ export class Terminal extends CoreTerminal implements ITerminal { this._mouseService = this._instantiationService.createInstance(MouseService); this._instantiationService.setService(IMouseService, this._mouseService); - this.viewport = this._instantiationService.createInstance(Viewport, - (amount: number, suppressScrollEvent: boolean) => this.scrollLines(amount, suppressScrollEvent, ScrollSource.VIEWPORT), - this._viewportElement, - this._viewportScrollArea - ); + this.viewport = this._instantiationService.createInstance(Viewport, this._viewportElement, this._viewportScrollArea); + this.viewport.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent, ScrollSource.VIEWPORT)), this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea())); this.register(this.viewport); @@ -1007,7 +1004,7 @@ export class Terminal extends CoreTerminal implements ITerminal { if (!shouldIgnoreComposition && !this._compositionHelper!.keydown(event)) { if (this.options.scrollOnUserInput && this.buffer.ybase !== this.buffer.ydisp) { - this._bufferService.scrollToBottom(); + this.scrollToBottom(); } return false; } diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index b27cef5a..c04c9af2 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -296,6 +296,8 @@ export class MockRenderer implements IRenderer { } export class MockViewport implements IViewport { + private readonly _onRequestScrollLines = new EventEmitter<{ amount: number, suppressScrollEvent: boolean }>(); + public readonly onRequestScrollLines = this._onRequestScrollLines.event; public dispose(): void { throw new Error('Method not implemented.'); } @@ -319,7 +321,9 @@ export class MockViewport implements IViewport { public getBufferElements(startLine: number, endLine?: number | undefined): { bufferElements: HTMLElement[], cursorElement?: HTMLElement | undefined } { throw new Error('Method not implemented.'); } - public scrollLines(disp: number): void { } + public scrollLines(disp: number): void { + this._onRequestScrollLines.fire({ amount: disp, suppressScrollEvent: false }); + } } export class MockCompositionHelper implements ICompositionHelper { diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 919c1d32..42d56c06 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -69,10 +69,28 @@ export interface IPublicTerminal extends IDisposable { selectAll(): void; selectLines(start: number, end: number): void; dispose(): void; + /** + * Scroll the display of the terminal + * @param amount The number of lines to scroll down (negative scroll up). + */ scrollLines(amount: number): void; + /** + * Scroll the display of the terminal by a number of pages. + * @param pageCount The number of pages to scroll (negative scrolls up). + */ scrollPages(pageCount: number): void; + /** + * Scrolls the display of the terminal to the top. + */ scrollToTop(): void; + /** + * Scrolls the display of the terminal to the bottom. + */ scrollToBottom(): void; + /** + * Scrolls to a line within the buffer. + * @param line The 0-based line index to scroll to. + */ scrollToLine(line: number): void; clear(): void; write(data: string | Uint8Array, callback?: () => void): void; @@ -142,6 +160,7 @@ export interface IPartialColorSet { export interface IViewport extends IDisposable { scrollBarWidth: number; + readonly onRequestScrollLines: IEvent<{ amount: number, suppressScrollEvent: boolean }>; syncScrollArea(immediate?: boolean): void; getLinesScrolled(ev: WheelEvent): number; getBufferElements(startLine: number, endLine?: number): { bufferElements: HTMLElement[], cursorElement?: HTMLElement }; diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 4be1323e..a30228a4 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -10,6 +10,7 @@ import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } import { IBufferService, IOptionsService } from 'common/services/Services'; import { IBuffer } from 'common/buffer/Types'; import { IRenderDimensions } from 'browser/renderer/shared/Types'; +import { EventEmitter } from 'common/EventEmitter'; const FALLBACK_SCROLL_BAR_WIDTH = 15; @@ -48,8 +49,10 @@ export class Viewport extends Disposable implements IViewport { target: -1 }; + private readonly _onRequestScrollLines = this.register(new EventEmitter<{ amount: number, suppressScrollEvent: boolean }>()); + public readonly onRequestScrollLines = this._onRequestScrollLines.event; + constructor( - private readonly _scrollLines: (amount: number, suppressScrollEvent: boolean) => void, private readonly _viewportElement: HTMLElement, private readonly _scrollArea: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, @@ -175,13 +178,13 @@ export class Viewport extends Disposable implements IViewport { if (this._ignoreNextScrollEvent) { this._ignoreNextScrollEvent = false; // Still trigger the scroll so lines get refreshed - this._scrollLines(0, true); + this._onRequestScrollLines.fire({ amount: 0, suppressScrollEvent: true }); return; } const newRow = Math.round(this._lastScrollTop / this._currentRowHeight); const diff = newRow - this._bufferService.buffer.ydisp; - this._scrollLines(diff, true); + this._onRequestScrollLines.fire({ amount: diff, suppressScrollEvent: true }); } private _smoothScroll(): void { @@ -265,7 +268,7 @@ export class Viewport extends Disposable implements IViewport { public scrollLines(disp: number): void { if (!this._optionsService.rawOptions.smoothScrollDuration) { - this._scrollLines(disp, false); + this._onRequestScrollLines.fire({ amount: disp, suppressScrollEvent: false }); } else { const amount = disp * this._currentRowHeight; this._smoothScrollState.startTime = Date.now(); diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 8ff9b988..89d9f5e5 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -195,38 +195,32 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { /** * 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. + * @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. + * @param source Which component the event came from. */ public scrollLines(disp: number, suppressScrollEvent?: boolean, source?: ScrollSource): void { this._bufferService.scrollLines(disp, suppressScrollEvent, source); } - /** - * 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._bufferService.scrollPages(pageCount); + this.scrollLines(pageCount * (this.rows - 1)); } - /** - * Scrolls the display of the terminal to the top. - */ public scrollToTop(): void { - this._bufferService.scrollToTop(); + this.scrollLines(-this._bufferService.buffer.ydisp); } - /** - * Scrolls the display of the terminal to the bottom. - */ public scrollToBottom(): void { - this._bufferService.scrollToBottom(); + this.scrollLines(this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp); } public scrollToLine(line: number): void { - this._bufferService.scrollToLine(line); + const scrollAmount = line - this._bufferService.buffer.ydisp; + if (scrollAmount !== 0) { + this.scrollLines(scrollAmount); + } } /** Add handler for ESC escape sequence. See xterm.d.ts for details. */ diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 528d2674..7b02cb7d 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -147,33 +147,4 @@ export class BufferService extends Disposable implements IBufferService { 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); - } - } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index b4021e45..3a1e5d8d 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -21,11 +21,7 @@ export interface IBufferService { onResize: IEvent<{ cols: number, rows: number }>; onScroll: IEvent; scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void; - scrollToBottom(): void; - scrollToTop(): void; - scrollToLine(line: number): void; scrollLines(disp: number, suppressScrollEvent?: boolean, source?: ScrollSource): void; - scrollPages(pageCount: number): void; resize(cols: number, rows: number): void; reset(): void; }