Get esbuild working and adopt vs scrollbar

This commit is contained in:
Daniel Imms
2024-07-09 11:19:15 -07:00
parent c30bffc60c
commit 4789c24e1e
9 changed files with 139 additions and 443 deletions
+8 -2
View File
@@ -35,7 +35,7 @@ const devOptions = {
const prodOptions = {
minify: true,
treeShaking: true,
logLevel: 'verbose',
logLevel: 'debug',
legalComments: 'none',
// TODO: Mangling private and protected properties will reduce bundle size quite a bit, we must
// make sure we don't cast privates to `any` in order to prevent regressions.
@@ -171,7 +171,13 @@ if (config.addon) {
};
outConfig = {
...outConfig,
entryPoints: ['src/**/*.ts'],
entryPoints: [
`src/browser/public/Terminal.ts`,
`src/headless/public/Terminal.ts`,
`src/browser/*.test.ts`,
`src/common/*.test.ts`,
`src/headless/*.test.ts`
],
outdir: 'out-esbuild/'
};
outTestConfig = {
-4
View File
@@ -112,10 +112,6 @@
top: 0;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm-char-measure-element {
display: inline-block;
visibility: hidden;
+29 -42
View File
@@ -26,7 +26,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle';
import { Linkifier } from './Linkifier';
import * as Strings from 'browser/LocalizableStrings';
import { OscLinkProvider } from 'browser/OscLinkProvider';
import { CharacterJoinerHandler, CustomKeyEventHandler, CustomWheelEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal, IViewport } from 'browser/Types';
import { CharacterJoinerHandler, CustomKeyEventHandler, CustomWheelEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal } from 'browser/Types';
import { Viewport } from 'browser/Viewport';
import { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRenderer';
import { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer';
@@ -65,7 +65,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
public screenElement: HTMLElement | undefined;
private _document: Document | undefined;
private _viewportScrollArea: HTMLElement | undefined;
private _viewportElement: HTMLElement | undefined;
private _helperContainer: HTMLElement | undefined;
private _compositionView: HTMLElement | undefined;
@@ -118,7 +117,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
*/
private _unprocessedDeadKey: boolean = false;
public viewport: IViewport | undefined;
private _compositionHelper: ICompositionHelper | undefined;
private _accessibilityManager: MutableDisposable<AccessibilityManager> = this.register(new MutableDisposable());
@@ -427,10 +425,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
this._viewportElement.classList.add('xterm-viewport');
fragment.appendChild(this._viewportElement);
this._viewportScrollArea = this._document.createElement('div');
this._viewportScrollArea.classList.add('xterm-scroll-area');
this._viewportElement.appendChild(this._viewportScrollArea);
this.screenElement = this._document.createElement('div');
this.screenElement.classList.add('xterm-screen');
this.register(addDisposableDomListener(this.screenElement, 'mousemove', (ev: MouseEvent) => this.updateCursorStyle(ev)));
@@ -503,11 +497,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
this._renderService.setRenderer(this._createRenderer());
}
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);
this.register(this.onCursorMove(() => {
this._renderService!.handleCursorMove();
this._syncTextArea();
@@ -515,7 +504,9 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
this.register(this.onResize(() => this._renderService!.handleResize(this.cols, this.rows)));
this.register(this.onBlur(() => this._renderService!.handleBlur()));
this.register(this.onFocus(() => this._renderService!.handleFocus()));
this.register(this._renderService.onDimensionsChange(() => this.viewport!.syncScrollArea()));
const viewport = this.register(this._instantiationService.createInstance(Viewport, this.element, this.screenElement));
this.register(viewport.onRequestScrollLines(e => this.scrollLines(e, false, ScrollSource.VIEWPORT)));
this._selectionService = this.register(this._instantiationService.createInstance(SelectionService,
this.element,
@@ -534,11 +525,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
this.textarea!.focus();
this.textarea!.select();
}));
this.register(this._onScroll.event(ev => {
this.viewport!.syncScrollArea();
this._selectionService!.refresh();
}));
this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService!.refresh()));
this.register(this._onScroll.event(() => this._selectionService!.refresh()));
this.register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement));
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.handleMouseDown(e)));
@@ -642,7 +629,9 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
if (self._customWheelEventHandler && self._customWheelEventHandler(ev as WheelEvent) === false) {
return false;
}
const amount = self.viewport!.getLinesScrolled(ev as WheelEvent);
// TODO: Implement
const amount = 0;
// const amount = self.viewport!.getLinesScrolled(ev as WheelEvent);
if (amount === 0) {
return false;
@@ -808,7 +797,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
if (!this.buffer.hasScrollback) {
// Convert wheel events into up/down events when the buffer does not have scrollback, this
// enables scrolling in apps hosted in the alt buffer such as vim or tmux.
const amount = this.viewport!.getLinesScrolled(ev);
// TODSO: Impl
const amount = 0; // this.viewport!.getLinesScrolled(ev);
// Do nothing if there's no vertical scroll
if (amount === 0) {
@@ -827,23 +817,24 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
// normal viewport scrolling
// conditionally stop event, if the viewport still had rows to scroll within
if (this.viewport!.handleWheel(ev)) {
return this.cancel(ev);
}
// if (this.viewport!.handleWheel(ev)) {
// return this.cancel(ev);
// }
}, { passive: false }));
this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => {
if (this.coreMouseService.areMouseEventsActive) return;
this.viewport!.handleTouchStart(ev);
return this.cancel(ev);
}, { passive: true }));
// TODO: Make sure this.coreMouseService.areMouseEventsActive still works
// this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => {
// if (this.coreMouseService.areMouseEventsActive) return;
// this.viewport!.handleTouchStart(ev);
// return this.cancel(ev);
// }, { passive: true }));
this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => {
if (this.coreMouseService.areMouseEventsActive) return;
if (!this.viewport!.handleTouchMove(ev)) {
return this.cancel(ev);
}
}, { passive: false }));
// this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => {
// if (this.coreMouseService.areMouseEventsActive) return;
// if (!this.viewport!.handleTouchMove(ev)) {
// return this.cancel(ev);
// }
// }, { passive: false }));
}
@@ -882,8 +873,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
if (source === ScrollSource.VIEWPORT) {
super.scrollLines(disp, suppressScrollEvent, source);
this.refresh(0, this.rows - 1);
} else {
this.viewport?.scrollLines(disp);
}
}
@@ -1212,10 +1201,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
private _afterResize(x: number, y: number): void {
this._charSizeService?.measure();
// Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an
// invalid location
this.viewport?.syncScrollArea(true);
}
/**
@@ -1238,7 +1223,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
// IMPORTANT: Fire scroll event before viewport is reset. This ensures embedders get the clear
// scroll event and that the viewport's state will be valid for immediate writes.
this._onScroll.fire({ position: this.buffer.ydisp, source: ScrollSource.TERMINAL });
this.viewport?.reset();
// TODO: Reset scrollable element?
// this.viewport?.reset();
this.refresh(0, this.rows - 1);
}
@@ -1263,7 +1249,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
super.reset();
this._selectionService?.reset();
this._decorationService.reset();
this.viewport?.reset();
// TODO: Reset scrollable element?
// this.viewport?.reset();
// reattach
this._customKeyEventHandler = customKeyEventHandler;
-2
View File
@@ -29,8 +29,6 @@ describe('Terminal', () => {
term = new TestTerminal(termOptions);
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: {
-1
View File
@@ -19,7 +19,6 @@ export interface ITerminal extends InternalPassthroughApis, ICoreTerminal {
browser: IBrowser;
buffer: IBuffer;
linkifier: ILinkifier2 | undefined;
viewport: IViewport | undefined;
options: Required<ITerminalOptions>;
onBlur: IEvent<void>;
+86 -376
View File
@@ -1,414 +1,124 @@
/**
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
* Copyright (c) 2024 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { addDisposableDomListener } from 'browser/Lifecycle';
import { IViewport, ReadonlyColorSet } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { IRenderService, IThemeService } from 'browser/services/Services';
import { EventEmitter } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { IBuffer } from 'common/buffer/Types';
import { IBufferService, IOptionsService } from 'common/services/Services';
import { DomScrollableElement } 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';
const FALLBACK_SCROLL_BAR_WIDTH = 15;
export class Viewport extends Disposable{
interface ISmoothScrollState {
startTime: number;
origin: number;
target: number;
}
/**
* Represents the viewport of a terminal, the visible area within the larger buffer of output.
* Logic for the virtual scroll bar is included in this object.
*/
export class Viewport extends Disposable implements IViewport {
public scrollBarWidth: number = 0;
private _currentRowHeight: number = 0;
private _currentDeviceCellHeight: number = 0;
private _lastRecordedBufferLength: number = 0;
private _lastRecordedViewportHeight: number = 0;
private _lastRecordedBufferHeight: number = 0;
private _lastTouchY: number = 0;
private _lastScrollTop: number = 0;
private _activeBuffer: IBuffer;
private _renderDimensions: IRenderDimensions;
private _smoothScrollAnimationFrame: number = 0;
// Stores a partial line amount when scrolling, this is used to keep track of how much of a line
// is scrolled so we can "scroll" over partial lines and feel natural on touchpads. This is a
// quick fix and could have a more robust solution in place that reset the value when needed.
private _wheelPartialScroll: number = 0;
private _refreshAnimationFrame: number | null = null;
private _ignoreNextScrollEvent: boolean = false;
private _smoothScrollState: ISmoothScrollState = {
startTime: 0,
origin: -1,
target: -1
};
private _ensureTimeout: number;
private readonly _onRequestScrollLines = this.register(new EventEmitter<{ amount: number, suppressScrollEvent: boolean }>());
protected _onRequestScrollLines = this.register(new EventEmitter<number>());
public readonly onRequestScrollLines = this._onRequestScrollLines.event;
private _scrollableElement: DomScrollableElement;
private _queuedAnimationFrame?: number;
private _latestYDisp?: number;
private _isSyncing: boolean = false;
private _isHandlingScroll: boolean = false;
private _suppressOnScrollHandler: boolean = false;
constructor(
private readonly _viewportElement: HTMLElement,
private readonly _scrollArea: HTMLElement,
element: HTMLElement,
screenElement: HTMLElement,
@IBufferService private readonly _bufferService: IBufferService,
@IThemeService themeService: IThemeService,
@IOptionsService private readonly _optionsService: IOptionsService,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@IRenderService private readonly _renderService: IRenderService,
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,
@IThemeService themeService: IThemeService
@IRenderService private readonly _renderService: IRenderService
) {
super();
// Measure the width of the scrollbar. If it is 0 we can assume it's an OSX overlay scrollbar.
// Unfortunately the overlay scrollbar would be hidden underneath the screen element in that
// case, therefore we account for a standard amount to make it visible
this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
this.register(addDisposableDomListener(this._viewportElement, 'scroll', this._handleScroll.bind(this)));
// TODO: Support smooth scroll
// TODO: Support fastScrollModifier?
// TODO: overviewRulerWidth should deprecated in favor of scrollBarWidth?
// Track properties used in performance critical code manually to avoid using slow getters
this._activeBuffer = this._bufferService.buffer;
this.register(this._bufferService.buffers.onBufferActivate(e => this._activeBuffer = e.activeBuffer));
this._renderDimensions = this._renderService.dimensions;
this.register(this._renderService.onDimensionsChange(e => this._renderDimensions = e));
this._scrollableElement = this.register(new DomScrollableElement(screenElement, {
vertical: ScrollbarVisibility.Auto,
horizontal: ScrollbarVisibility.Hidden,
useShadows: false,
mouseWheelSmoothScroll: true,
...this._getMutableOptions()
}));
this.register(this._optionsService.onMultipleOptionChange([
'scrollSensitivity',
'fastScrollSensitivity',
'overviewRulerWidth'
], () => this._scrollableElement.updateOptions(this._getMutableOptions())));
this._handleThemeChange(themeService.colors);
this.register(themeService.onChangeColors(e => this._handleThemeChange(e)));
this.register(this._optionsService.onSpecificOptionChange('scrollback', () => this.syncScrollArea()));
this._scrollableElement.setScrollDimensions({ height: 0, scrollHeight: 0 });
this._scrollableElement.getDomNode().style.backgroundColor = themeService.colors.background.css;
element.appendChild(this._scrollableElement.getDomNode());
// Perform this async to ensure the ICharSizeService is ready.
this._ensureTimeout = window.setTimeout(() => this.syncScrollArea());
this.register(this._bufferService.onResize(() => this._queueSync()));
this.register(this._bufferService.onScroll(ydisp => this._queueSync(ydisp)));
this.register(this._scrollableElement.onScroll(e => this._handleScroll(e)));
}
private _handleThemeChange(colors: ReadonlyColorSet): void {
this._viewportElement.style.backgroundColor = colors.background.css;
private _getMutableOptions(): ScrollableElementChangeOptions {
return {
mouseWheelScrollSensitivity: this._optionsService.rawOptions.scrollSensitivity,
fastScrollSensitivity: this._optionsService.rawOptions.fastScrollSensitivity,
verticalScrollbarSize: this._optionsService.rawOptions.overviewRulerWidth || 14
};
}
public reset(): void {
this._currentRowHeight = 0;
this._currentDeviceCellHeight = 0;
this._lastRecordedBufferLength = 0;
this._lastRecordedViewportHeight = 0;
this._lastRecordedBufferHeight = 0;
this._lastTouchY = 0;
this._lastScrollTop = 0;
// Sync on next animation frame to ensure the new terminal state is used
this._coreBrowserService.window.requestAnimationFrame(() => this.syncScrollArea());
private _queueSync(ydisp?: number): void {
// Update state
if (ydisp !== undefined) {
this._latestYDisp = ydisp;
}
// Don't queue more than one callback
if (this._queuedAnimationFrame !== undefined) {
return;
}
this._queuedAnimationFrame = this._renderService.addRefreshCallback(() => this._sync(this._latestYDisp));
this._latestYDisp = undefined;
this._queuedAnimationFrame = undefined;
}
/**
* Refreshes row height, setting line-height, viewport height and scroll area height if
* necessary.
*/
private _refresh(immediate: boolean): void {
if (immediate) {
this._innerRefresh();
if (this._refreshAnimationFrame !== null) {
this._coreBrowserService.window.cancelAnimationFrame(this._refreshAnimationFrame);
}
private _sync(ydisp: number = this._bufferService.buffer.ydisp): void {
if (!this._renderService || this._isSyncing) {
return;
}
if (this._refreshAnimationFrame === null) {
this._refreshAnimationFrame = this._coreBrowserService.window.requestAnimationFrame(() => this._innerRefresh());
}
this._isSyncing = true;
// Ignore any onScroll event that happens as a result of dimensions changing as this should
// never cause a scrollLines call, only setScrollPosition can do that.
this._suppressOnScrollHandler = true;
this._scrollableElement.setScrollDimensions({
height: this._renderService.dimensions.css.canvas.height,
scrollHeight: this._renderService.dimensions.css.cell.height * this._bufferService.buffer.lines.length
});
this._suppressOnScrollHandler = false;
this._scrollableElement.setScrollPosition({
scrollTop: ydisp * this._renderService.dimensions.css.cell.height
});
this._isSyncing = false;
}
private _innerRefresh(): void {
if (this._charSizeService.height > 0) {
this._currentRowHeight = this._renderDimensions.device.cell.height / this._coreBrowserService.dpr;
this._currentDeviceCellHeight = this._renderDimensions.device.cell.height;
this._lastRecordedViewportHeight = this._viewportElement.offsetHeight;
const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._renderDimensions.css.canvas.height);
if (this._lastRecordedBufferHeight !== newBufferHeight) {
this._lastRecordedBufferHeight = newBufferHeight;
this._scrollArea.style.height = this._lastRecordedBufferHeight + 'px';
}
}
// Sync scrollTop
const scrollTop = this._bufferService.buffer.ydisp * this._currentRowHeight;
if (this._viewportElement.scrollTop !== scrollTop) {
// Ignore the next scroll event which will be triggered by setting the scrollTop as we do not
// want this event to scroll the terminal
this._ignoreNextScrollEvent = true;
this._viewportElement.scrollTop = scrollTop;
}
this._refreshAnimationFrame = null;
}
/**
* Updates dimensions and synchronizes the scroll area if necessary.
*/
public syncScrollArea(immediate: boolean = false): void {
// If buffer height changed
if (this._lastRecordedBufferLength !== this._bufferService.buffer.lines.length) {
this._lastRecordedBufferLength = this._bufferService.buffer.lines.length;
this._refresh(immediate);
private _handleScroll(e: ScrollEvent): void {
if (!this._renderService) {
return;
}
// If viewport height changed
if (this._lastRecordedViewportHeight !== this._renderService.dimensions.css.canvas.height) {
this._refresh(immediate);
if (this._isHandlingScroll || this._suppressOnScrollHandler) {
return;
}
// If the buffer position doesn't match last scroll top
if (this._lastScrollTop !== this._activeBuffer.ydisp * this._currentRowHeight) {
this._refresh(immediate);
return;
}
// If row height changed
if (this._renderDimensions.device.cell.height !== this._currentDeviceCellHeight) {
this._refresh(immediate);
return;
}
}
/**
* Handles scroll events on the viewport, calculating the new viewport and requesting the
* terminal to scroll to it.
* @param ev The scroll event.
*/
private _handleScroll(ev: Event): void {
// Record current scroll top position
this._lastScrollTop = this._viewportElement.scrollTop;
// Don't attempt to scroll if the element is not visible, otherwise scrollTop will be corrupt
// which causes the terminal to scroll the buffer to the top
if (!this._viewportElement.offsetParent) {
return;
}
// Ignore the event if it was flagged to ignore (when the source of the event is from Viewport)
if (this._ignoreNextScrollEvent) {
this._ignoreNextScrollEvent = false;
// Still trigger the scroll so lines get refreshed
this._onRequestScrollLines.fire({ amount: 0, suppressScrollEvent: true });
return;
}
const newRow = Math.round(this._lastScrollTop / this._currentRowHeight);
this._isHandlingScroll = true;
const newRow = Math.round(e.scrollTop / this._renderService.dimensions.css.cell.height);
const diff = newRow - this._bufferService.buffer.ydisp;
this._onRequestScrollLines.fire({ amount: diff, suppressScrollEvent: true });
}
private _smoothScroll(): void {
// Check valid state
if (this._isDisposed || this._smoothScrollState.origin === -1 || this._smoothScrollState.target === -1) {
return;
if (diff !== 0) {
this._onRequestScrollLines.fire(diff);
}
// Calculate position complete
const percent = this._smoothScrollPercent();
this._viewportElement.scrollTop = this._smoothScrollState.origin + Math.round(percent * (this._smoothScrollState.target - this._smoothScrollState.origin));
// Continue or finish smooth scroll
if (percent < 1) {
if (!this._smoothScrollAnimationFrame) {
this._smoothScrollAnimationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {
this._smoothScrollAnimationFrame = 0;
this._smoothScroll();
});
}
} else {
this._clearSmoothScrollState();
}
}
private _smoothScrollPercent(): number {
if (!this._optionsService.rawOptions.smoothScrollDuration || !this._smoothScrollState.startTime) {
return 1;
}
return Math.max(Math.min((Date.now() - this._smoothScrollState.startTime) / this._optionsService.rawOptions.smoothScrollDuration, 1), 0);
}
private _clearSmoothScrollState(): void {
this._smoothScrollState.startTime = 0;
this._smoothScrollState.origin = -1;
this._smoothScrollState.target = -1;
}
/**
* Handles bubbling of scroll event in case the viewport has reached top or bottom
* @param ev The scroll event.
* @param amount The amount scrolled
*/
private _bubbleScroll(ev: Event, amount: number): boolean {
const scrollPosFromTop = this._viewportElement.scrollTop + this._lastRecordedViewportHeight;
if ((amount < 0 && this._viewportElement.scrollTop !== 0) ||
(amount > 0 && scrollPosFromTop < this._lastRecordedBufferHeight)) {
if (ev.cancelable) {
ev.preventDefault();
}
return false;
}
return true;
}
/**
* Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual
* scrolling to `onScroll`, this event needs to be attached manually by the consumer of
* `Viewport`.
* @param ev The mouse wheel event.
*/
public handleWheel(ev: WheelEvent): boolean {
const amount = this._getPixelsScrolled(ev);
if (amount === 0) {
return false;
}
if (!this._optionsService.rawOptions.smoothScrollDuration) {
this._viewportElement.scrollTop += amount;
} else {
this._smoothScrollState.startTime = Date.now();
if (this._smoothScrollPercent() < 1) {
this._smoothScrollState.origin = this._viewportElement.scrollTop;
if (this._smoothScrollState.target === -1) {
this._smoothScrollState.target = this._viewportElement.scrollTop + amount;
} else {
this._smoothScrollState.target += amount;
}
this._smoothScrollState.target = Math.max(Math.min(this._smoothScrollState.target, this._viewportElement.scrollHeight), 0);
this._smoothScroll();
} else {
this._clearSmoothScrollState();
}
}
return this._bubbleScroll(ev, amount);
}
public scrollLines(disp: number): void {
if (disp === 0) {
return;
}
if (!this._optionsService.rawOptions.smoothScrollDuration) {
this._onRequestScrollLines.fire({ amount: disp, suppressScrollEvent: false });
} else {
const amount = disp * this._currentRowHeight;
this._smoothScrollState.startTime = Date.now();
if (this._smoothScrollPercent() < 1) {
this._smoothScrollState.origin = this._viewportElement.scrollTop;
this._smoothScrollState.target = this._smoothScrollState.origin + amount;
this._smoothScrollState.target = Math.max(Math.min(this._smoothScrollState.target, this._viewportElement.scrollHeight), 0);
this._smoothScroll();
} else {
this._clearSmoothScrollState();
}
}
}
private _getPixelsScrolled(ev: WheelEvent): number {
// Do nothing if it's not a vertical scroll event
if (ev.deltaY === 0 || ev.shiftKey) {
return 0;
}
// Fallback to WheelEvent.DOM_DELTA_PIXEL
let amount = this._applyScrollModifier(ev.deltaY, ev);
if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
amount *= this._currentRowHeight;
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
amount *= this._currentRowHeight * this._bufferService.rows;
}
return amount;
}
public getBufferElements(startLine: number, endLine?: number): { bufferElements: HTMLElement[], cursorElement?: HTMLElement } {
let currentLine: string = '';
let cursorElement: HTMLElement | undefined;
const bufferElements: HTMLElement[] = [];
const end = endLine ?? this._bufferService.buffer.lines.length;
const lines = this._bufferService.buffer.lines;
for (let i = startLine; i < end; i++) {
const line = lines.get(i);
if (!line) {
continue;
}
const isWrapped = lines.get(i + 1)?.isWrapped;
currentLine += line.translateToString(!isWrapped);
if (!isWrapped || i === lines.length - 1) {
const div = document.createElement('div');
div.textContent = currentLine;
bufferElements.push(div);
if (currentLine.length > 0) {
cursorElement = div;
}
currentLine = '';
}
}
return { bufferElements, cursorElement };
}
/**
* Gets the number of pixels scrolled by the mouse event taking into account what type of delta
* is being used.
* @param ev The mouse wheel event.
*/
public getLinesScrolled(ev: WheelEvent): number {
// Do nothing if it's not a vertical scroll event
if (ev.deltaY === 0 || ev.shiftKey) {
return 0;
}
// Fallback to WheelEvent.DOM_DELTA_LINE
let amount = this._applyScrollModifier(ev.deltaY, ev);
if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
amount /= this._currentRowHeight + 0.0; // Prevent integer division
this._wheelPartialScroll += amount;
amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1);
this._wheelPartialScroll %= 1;
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
amount *= this._bufferService.rows;
}
return amount;
}
private _applyScrollModifier(amount: number, ev: WheelEvent): number {
const modifier = this._optionsService.rawOptions.fastScrollModifier;
// Multiply the scroll speed when the modifier is down
if ((modifier === 'alt' && ev.altKey) ||
(modifier === 'ctrl' && ev.ctrlKey) ||
(modifier === 'shift' && ev.shiftKey)) {
return amount * this._optionsService.rawOptions.fastScrollSensitivity * this._optionsService.rawOptions.scrollSensitivity;
}
return amount * this._optionsService.rawOptions.scrollSensitivity;
}
/**
* Handles the touchstart event, recording the touch occurred.
* @param ev The touch event.
*/
public handleTouchStart(ev: TouchEvent): void {
this._lastTouchY = ev.touches[0].pageY;
}
/**
* Handles the touchmove event, scrolling the viewport if the position shifted.
* @param ev The touch event.
*/
public handleTouchMove(ev: TouchEvent): boolean {
const deltaY = this._lastTouchY - ev.touches[0].pageY;
this._lastTouchY = ev.touches[0].pageY;
if (deltaY === 0) {
return false;
}
this._viewportElement.scrollTop += deltaY;
return this._bubbleScroll(ev, deltaY);
}
public dispose(): void {
clearTimeout(this._ensureTimeout);
this._isHandlingScroll = false;
}
}
@@ -118,8 +118,8 @@ export class OverviewRulerRenderer extends Disposable {
private _refreshDrawConstants(): void {
// width
const outerWidth = Math.floor(this._canvas.width / 3);
const innerWidth = Math.ceil(this._canvas.width / 3);
const outerWidth = Math.floor((this._canvas.width - 1) / 3);
const innerWidth = Math.ceil((this._canvas.width - 1) / 3);
drawWidth.full = this._canvas.width;
drawWidth.left = outerWidth;
drawWidth.center = innerWidth;
@@ -127,10 +127,10 @@ export class OverviewRulerRenderer extends Disposable {
// height
this._refreshDrawHeightConstants();
// x
drawX.full = 0;
drawX.left = 0;
drawX.center = drawWidth.left;
drawX.right = drawWidth.left + drawWidth.center;
drawX.full = 1;
drawX.left = 1;
drawX.center = 1 + drawWidth.left;
drawX.right = 1 + drawWidth.left + drawWidth.center;
}
private _refreshDrawHeightConstants(): void {
@@ -173,6 +173,7 @@ export class OverviewRulerRenderer extends Disposable {
this._colorZoneStore.addDecoration(decoration);
}
this._ctx.lineWidth = 1;
this._renderRulerOutline();
const zones = this._colorZoneStore.zones;
for (const zone of zones) {
if (zone.position !== 'full') {
@@ -188,6 +189,12 @@ export class OverviewRulerRenderer extends Disposable {
this._shouldUpdateAnchor = false;
}
private _renderRulerOutline(): void {
// TODO: Support customizing the color
this._ctx.fillStyle = '#000';
this._ctx.fillRect(0, 0, 1, this._canvas.height);
}
private _renderColorZone(zone: IColorZone): void {
this._ctx.fillStyle = zone.color;
this._ctx.fillRect(
+1 -7
View File
@@ -9,16 +9,10 @@
"types": [
"../../node_modules/@types/mocha"
],
"baseUrl": "..",
"paths": {
"vs/*": [ "./vs/*" ]
}
"baseUrl": ".."
},
"include": [
"./**/*",
"../../typings/xterm.d.ts"
],
"references": [
{ "path": "../vs" }
]
}
+2 -3
View File
@@ -64,8 +64,7 @@
"base/common/jsonc.js",
"base/common/performance.js",
"base/common/marked/marked.js",
"base/common/network.js",
"typings/vscode-globals-modules.d.ts"
"base/common/network.ts",
"base/browser/markdownRenderer.ts"
]
}