diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index f95013cd..1ab207c7 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -39,7 +39,7 @@ import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; -import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, IAnsiColorChangeEvent } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IAnsiColorChangeEvent } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; @@ -470,7 +470,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this._instantiationService.setService(IMouseService, this._mouseService); this.viewport = this._instantiationService.createInstance(Viewport, - (amount: number, suppressEvent: boolean) => this.scrollLines(amount, suppressEvent), + (amount: number) => this.scrollLines(amount, false, ScrollSource.VIEWPORT), this._viewportElement, this._viewportScrollArea ); @@ -504,8 +504,10 @@ export class Terminal extends CoreTerminal implements ITerminal { this.textarea!.focus(); this.textarea!.select(); })); - this.register(this.onScroll(() => { - this.viewport!.syncScrollArea(); + this.register(this._onScroll.event(ev => { + if (ev.source !== ScrollSource.VIEWPORT) { + this.viewport!.syncScrollArea(); + } this._selectionService!.refresh(); })); this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService!.refresh())); @@ -860,8 +862,8 @@ export class Terminal extends CoreTerminal implements ITerminal { } } - public scrollLines(disp: number, suppressScrollEvent?: boolean): void { - super.scrollLines(disp, suppressScrollEvent); + public scrollLines(disp: number, suppressScrollEvent?: boolean, source = ScrollSource.TERMINAL): void { + super.scrollLines(disp, suppressScrollEvent, source); this.refresh(0, this.rows - 1); } @@ -1192,7 +1194,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR_DATA)); } this.refresh(0, this.rows - 1); - this._onScroll.fire(this.buffer.ydisp); + this._onScroll.fire({ position: this.buffer.ydisp, source: ScrollSource.TERMINAL }); } /** diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 29edce6f..02f74ce8 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -33,7 +33,7 @@ export class Viewport extends Disposable implements IViewport { private _ignoreNextScrollEvent: boolean = false; constructor( - private readonly _scrollLines: (amount: number, suppressEvent: boolean) => void, + private readonly _scrollLines: (amount: number) => void, private readonly _viewportElement: HTMLElement, private readonly _scrollArea: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, @@ -156,7 +156,7 @@ export class Viewport extends Disposable implements IViewport { const newRow = Math.round(this._lastScrollTop / this._currentRowHeight); const diff = newRow - this._bufferService.buffer.ydisp; - this._scrollLines(diff, true); + this._scrollLines(diff); } /** diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 384691ed..936aa0c8 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -27,7 +27,7 @@ import { InstantiationService } from 'common/services/InstantiationService'; import { LogService } from 'common/services/LogService'; import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; import { OptionsService } from 'common/services/OptionsService'; -import { ITerminalOptions, IDisposable, IBufferLine, IAttributeData, ICoreTerminal } from 'common/Types'; +import { ITerminalOptions, IDisposable, IBufferLine, IAttributeData, ICoreTerminal, IKeyboardEvent, IScrollEvent, ScrollSource } from 'common/Types'; import { CoreService } from 'common/services/CoreService'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; @@ -69,8 +69,21 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onResize = new EventEmitter<{ cols: number, rows: number }>(); public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } - protected _onScroll = new EventEmitter(); - public get onScroll(): IEvent { return this._onScroll.event; } + protected _onScroll = new EventEmitter(); + /** + * Internally we track the source of the scroll but this is meaningless outside the library so + * it's filtered out. + */ + protected _onScrollApi?: EventEmitter; + public get onScroll(): IEvent { + if (!this._onScrollApi) { + this._onScrollApi = new EventEmitter(); + this.register(this._onScroll.event(ev => { + this._onScrollApi?.fire(ev.position); + })); + } + return this._onScrollApi.event; + } public get cols(): number { return this._bufferService.cols; } public get rows(): number { return this._bufferService.rows; } @@ -220,17 +233,17 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { // Flag rows that need updating this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); - this._onScroll.fire(buffer.ydisp); + this._onScroll.fire({ position: buffer.ydisp, source: ScrollSource.TERMINAL }); } /** * 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 an onScroll event. + * @param source The source of the scroll action. Emitted as part of the onScroll event + * to avoid cyclic invocations if the event originated from the Viewport. */ - public scrollLines(disp: number, suppressScrollEvent?: boolean): void { + public scrollLines(disp: number, suppressScrollEvent = false, source = ScrollSource.TERMINAL): void { const buffer = this._bufferService.buffer; if (disp < 0) { if (buffer.ydisp === 0) { @@ -250,7 +263,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { } if (!suppressScrollEvent) { - this._onScroll.fire(buffer.ydisp); + this._onScroll.fire({ position: buffer.ydisp, source }); } } diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 108d81ac..2dd3f4b8 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -42,6 +42,16 @@ export interface IKeyboardEvent { type: string; } +export interface IScrollEvent { + position: number; + source: ScrollSource; +} + +export const enum ScrollSource { + TERMINAL, + VIEWPORT, +} + export interface ICircularList { length: number; maxLength: number;