mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Support smooth scroll
This commit is contained in:
@@ -71,6 +71,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
|
||||
public linkifier: ILinkifier2 | undefined;
|
||||
private _overviewRulerRenderer: OverviewRulerRenderer | undefined;
|
||||
private _viewport: Viewport | undefined;
|
||||
|
||||
public browser: IBrowser = Browser as any;
|
||||
|
||||
@@ -505,8 +506,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
this.register(this.onBlur(() => this._renderService!.handleBlur()));
|
||||
this.register(this.onFocus(() => this._renderService!.handleFocus()));
|
||||
|
||||
const viewport = this.register(this._instantiationService.createInstance(Viewport, this.element, this.screenElement));
|
||||
this.register(viewport.onRequestScrollLines(e => this.scrollLines(e, false)));
|
||||
this._viewport = this.register(this._instantiationService.createInstance(Viewport, this.element, this.screenElement));
|
||||
this.register(this._viewport.onRequestScrollLines(e => super.scrollLines(e, false)));
|
||||
|
||||
this._selectionService = this.register(this._instantiationService.createInstance(SelectionService,
|
||||
this.element,
|
||||
@@ -870,10 +871,34 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
}
|
||||
|
||||
public scrollLines(disp: number, suppressScrollEvent?: boolean): void {
|
||||
super.scrollLines(disp, suppressScrollEvent);
|
||||
// All scrollLines methods need to go via the viewport in order to support smooth scroll
|
||||
if (this._viewport) {
|
||||
this._viewport.scrollLines(disp);
|
||||
} else {
|
||||
super.scrollLines(disp, suppressScrollEvent);
|
||||
}
|
||||
this.refresh(0, this.rows - 1);
|
||||
}
|
||||
|
||||
public scrollPages(pageCount: number): void {
|
||||
this.scrollLines(pageCount * (this.rows - 1));
|
||||
}
|
||||
|
||||
public scrollToTop(): void {
|
||||
this.scrollLines(-this._bufferService.buffer.ydisp);
|
||||
}
|
||||
|
||||
public scrollToBottom(): void {
|
||||
this.scrollLines(this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp);
|
||||
}
|
||||
|
||||
public scrollToLine(line: number): void {
|
||||
const scrollAmount = line - this._bufferService.buffer.ydisp;
|
||||
if (scrollAmount !== 0) {
|
||||
this.scrollLines(scrollAmount);
|
||||
}
|
||||
}
|
||||
|
||||
public paste(data: string): void {
|
||||
paste(data, this.textarea!, this.coreService, this.optionsService);
|
||||
}
|
||||
|
||||
+43
-9
@@ -7,9 +7,10 @@ import { ICoreBrowserService, IRenderService, IThemeService } from 'browser/serv
|
||||
import { EventEmitter, runAndSubscribe } from 'common/EventEmitter';
|
||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
|
||||
import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import type { ScrollableElementChangeOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
|
||||
import { ScrollbarVisibility, type ScrollEvent } from 'vs/base/common/scrollable';
|
||||
import { Scrollable, ScrollbarVisibility, type ScrollEvent } from 'vs/base/common/scrollable';
|
||||
|
||||
const enum Constants {
|
||||
DEFAULT_SCROLL_BAR_WIDTH = 14
|
||||
@@ -20,7 +21,7 @@ export class Viewport extends Disposable {
|
||||
protected _onRequestScrollLines = this.register(new EventEmitter<number>());
|
||||
public readonly onRequestScrollLines = this._onRequestScrollLines.event;
|
||||
|
||||
private _scrollableElement: DomScrollableElement;
|
||||
private _scrollableElement: SmoothScrollableElement;
|
||||
private _styleElement: HTMLStyleElement;
|
||||
|
||||
private _queuedAnimationFrame?: number;
|
||||
@@ -42,13 +43,23 @@ export class Viewport extends Disposable {
|
||||
|
||||
// TODO: Support smooth scroll
|
||||
|
||||
this._scrollableElement = this.register(new DomScrollableElement(screenElement, {
|
||||
const scrollable = this.register(new Scrollable({
|
||||
forceIntegerValues: false,
|
||||
smoothScrollDuration: this._optionsService.rawOptions.smoothScrollDuration,
|
||||
// This is used over `IRenderService.addRefreshCallback` since it can be canceled
|
||||
scheduleAtNextAnimationFrame: cb => scheduleAtNextAnimationFrame(coreBrowserService.window, cb)
|
||||
}));
|
||||
this.register(this._optionsService.onSpecificOptionChange('smoothScrollDuration', () => {
|
||||
scrollable.setSmoothScrollDuration(this._optionsService.rawOptions.smoothScrollDuration);
|
||||
}));
|
||||
|
||||
this._scrollableElement = this.register(new SmoothScrollableElement(screenElement, {
|
||||
vertical: ScrollbarVisibility.Auto,
|
||||
horizontal: ScrollbarVisibility.Hidden,
|
||||
useShadows: false,
|
||||
mouseWheelSmoothScroll: true,
|
||||
...this._getMutableOptions()
|
||||
}));
|
||||
}, scrollable));
|
||||
this.register(this._optionsService.onMultipleOptionChange([
|
||||
'scrollSensitivity',
|
||||
'fastScrollSensitivity',
|
||||
@@ -80,11 +91,29 @@ export class Viewport extends Disposable {
|
||||
}));
|
||||
|
||||
this.register(this._bufferService.onResize(() => this._queueSync()));
|
||||
this.register(this._bufferService.onScroll(ydisp => this._queueSync(ydisp)));
|
||||
this.register(this._bufferService.onScroll(() => this._sync()));
|
||||
|
||||
this.register(this._scrollableElement.onScroll(e => this._handleScroll(e)));
|
||||
}
|
||||
|
||||
public scrollLines(disp: number): void {
|
||||
const pos = this._scrollableElement.getScrollPosition();
|
||||
this._scrollableElement.setScrollPosition({
|
||||
reuseAnimation: true,
|
||||
scrollTop: pos.scrollTop + disp * this._renderService.dimensions.css.cell.height
|
||||
});
|
||||
}
|
||||
|
||||
public scrollToLine(line: number, disableSmoothScroll?: boolean): void {
|
||||
if (!disableSmoothScroll) {
|
||||
this._latestYDisp = line;
|
||||
}
|
||||
this._scrollableElement.setScrollPosition({
|
||||
reuseAnimation: !disableSmoothScroll,
|
||||
scrollTop: line * this._renderService.dimensions.css.cell.height
|
||||
});
|
||||
}
|
||||
|
||||
private _getMutableOptions(): ScrollableElementChangeOptions {
|
||||
return {
|
||||
mouseWheelScrollSensitivity: this._optionsService.rawOptions.scrollSensitivity,
|
||||
@@ -123,9 +152,13 @@ export class Viewport extends Disposable {
|
||||
});
|
||||
this._suppressOnScrollHandler = false;
|
||||
|
||||
this._scrollableElement.setScrollPosition({
|
||||
scrollTop: ydisp * this._renderService.dimensions.css.cell.height
|
||||
});
|
||||
// If ydisp has been changed by some other copmonent (input/buffer), then stop animating smooth
|
||||
// scroll and scroll there immediately.
|
||||
if (ydisp !== this._latestYDisp) {
|
||||
this._scrollableElement.setScrollPosition({
|
||||
scrollTop: ydisp * this._renderService.dimensions.css.cell.height
|
||||
});
|
||||
}
|
||||
|
||||
this._isSyncing = false;
|
||||
}
|
||||
@@ -141,6 +174,7 @@ export class Viewport extends Disposable {
|
||||
const newRow = Math.round(e.scrollTop / this._renderService.dimensions.css.cell.height);
|
||||
const diff = newRow - this._bufferService.buffer.ydisp;
|
||||
if (diff !== 0) {
|
||||
this._latestYDisp = newRow;
|
||||
this._onRequestScrollLines.fire(diff);
|
||||
}
|
||||
this._isHandlingScroll = false;
|
||||
|
||||
@@ -133,15 +133,10 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||
this.register(this.coreService.onRequestScrollToBottom(() => this.scrollToBottom()));
|
||||
this.register(this.coreService.onUserInput(() => this._writeBuffer.handleUserInput()));
|
||||
this.register(this.optionsService.onMultipleOptionChange(['windowsMode', 'windowsPty'], () => this._handleWindowsPtyOptionChange()));
|
||||
this.register(this._bufferService.onScroll(event => {
|
||||
this.register(this._bufferService.onScroll(() => {
|
||||
this._onScroll.fire({ position: this._bufferService.buffer.ydisp });
|
||||
this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom);
|
||||
}));
|
||||
this.register(this._inputHandler.onScroll(event => {
|
||||
this._onScroll.fire({ position: this._bufferService.buffer.ydisp });
|
||||
this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom);
|
||||
}));
|
||||
|
||||
// Setup WriteBuffer
|
||||
this._writeBuffer = this.register(new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult)));
|
||||
this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed));
|
||||
|
||||
Reference in New Issue
Block a user