diff --git a/bin/esbuild.mjs b/bin/esbuild.mjs index 3e792712..291ad406 100644 --- a/bin/esbuild.mjs +++ b/bin/esbuild.mjs @@ -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 = { diff --git a/css/xterm.css b/css/xterm.css index 51e9b39e..ef39dfea 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -112,10 +112,6 @@ top: 0; } -.xterm .xterm-scroll-area { - visibility: hidden; -} - .xterm-char-measure-element { display: inline-block; visibility: hidden; diff --git a/src/browser/CoreBrowserTerminal.ts b/src/browser/CoreBrowserTerminal.ts index f9eefaed..e06afdd4 100644 --- a/src/browser/CoreBrowserTerminal.ts +++ b/src/browser/CoreBrowserTerminal.ts @@ -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 = 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; diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index 11f676a3..85a3248e 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -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: { diff --git a/src/browser/Types.ts b/src/browser/Types.ts index 9ef9d3a8..fd0bd951 100644 --- a/src/browser/Types.ts +++ b/src/browser/Types.ts @@ -19,7 +19,6 @@ export interface ITerminal extends InternalPassthroughApis, ICoreTerminal { browser: IBrowser; buffer: IBuffer; linkifier: ILinkifier2 | undefined; - viewport: IViewport | undefined; options: Required; onBlur: IEvent; diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index cb0f35ea..211d7504 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -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()); 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; } } diff --git a/src/browser/decorations/OverviewRulerRenderer.ts b/src/browser/decorations/OverviewRulerRenderer.ts index 103d5d9a..ee000dfc 100644 --- a/src/browser/decorations/OverviewRulerRenderer.ts +++ b/src/browser/decorations/OverviewRulerRenderer.ts @@ -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( diff --git a/src/common/tsconfig.json b/src/common/tsconfig.json index e9b3673a..b8baa6c0 100644 --- a/src/common/tsconfig.json +++ b/src/common/tsconfig.json @@ -9,16 +9,10 @@ "types": [ "../../node_modules/@types/mocha" ], - "baseUrl": "..", - "paths": { - "vs/*": [ "./vs/*" ] - } + "baseUrl": ".." }, "include": [ "./**/*", "../../typings/xterm.d.ts" - ], - "references": [ - { "path": "../vs" } ] } diff --git a/src/vs/tsconfig.json b/src/vs/tsconfig.json index 134dd908..30f10c23 100644 --- a/src/vs/tsconfig.json +++ b/src/vs/tsconfig.json @@ -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" ] }