diff --git a/src/browser/scrollable/abstractScrollbar.ts b/src/browser/scrollable/abstractScrollbar.ts index 528ebfee..4d3dd0a8 100644 --- a/src/browser/scrollable/abstractScrollbar.ts +++ b/src/browser/scrollable/abstractScrollbar.ts @@ -11,7 +11,7 @@ import { ScrollbarArrow, ScrollbarArrowOptions } from './scrollbarArrow'; import { ScrollbarState } from './scrollbarState'; import { ScrollbarVisibilityController } from './scrollbarVisibilityController'; import { Widget } from './widget'; -import * as platform from './platform'; +import * as platform from 'common/Platform'; import { INewScrollPosition, Scrollable, ScrollbarVisibility } from './scrollable'; /** @@ -31,7 +31,7 @@ export interface ScrollbarHost { onDragEnd(): void; } -export interface AbstractScrollbarOptions { +interface AbstractScrollbarOptions { lazyRender: boolean; host: ScrollbarHost; scrollbarState: ScrollbarState; diff --git a/src/browser/scrollable/arrays.ts b/src/browser/scrollable/arrays.ts index 4a643fff..41f911b5 100644 --- a/src/browser/scrollable/arrays.ts +++ b/src/browser/scrollable/arrays.ts @@ -6,11 +6,3 @@ export function tail(array: ArrayLike, n: number = 0): T | undefined { return array[array.length - (1 + n)]; } - -export function tail2(arr: T[]): [T[], T] { - if (arr.length === 0) { - throw new Error('Invalid tail call'); - } - - return [arr.slice(0, arr.length - 1), arr[arr.length - 1]]; -} diff --git a/src/browser/scrollable/async.ts b/src/browser/scrollable/async.ts deleted file mode 100644 index 8fb5f9c7..00000000 --- a/src/browser/scrollable/async.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright (c) 2026 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IDisposable } from './lifecycle'; - -export class TimeoutTimer implements IDisposable { - private _token: any = -1; - private _isDisposed = false; - - dispose(): void { - this.cancel(); - this._isDisposed = true; - } - - cancel(): void { - if (this._token !== -1) { - clearTimeout(this._token); - this._token = -1; - } - } - - cancelAndSet(runner: () => void, timeout: number): void { - if (this._isDisposed) { - throw new Error('Calling cancelAndSet on a disposed TimeoutTimer'); - } - this.cancel(); - this._token = setTimeout(() => { - this._token = -1; - runner(); - }, timeout); - } - - setIfNotSet(runner: () => void, timeout: number): void { - if (this._isDisposed) { - throw new Error('Calling setIfNotSet on a disposed TimeoutTimer'); - } - if (this._token !== -1) { - return; - } - this._token = setTimeout(() => { - this._token = -1; - runner(); - }, timeout); - } -} - -export class IntervalTimer implements IDisposable { - private _disposable: IDisposable | undefined; - private _isDisposed = false; - - cancel(): void { - this._disposable?.dispose(); - this._disposable = undefined; - } - - cancelAndSet(runner: () => void, interval: number, context: Window | typeof globalThis = globalThis): void { - if (this._isDisposed) { - throw new Error('Calling cancelAndSet on a disposed IntervalTimer'); - } - this.cancel(); - const handle = context.setInterval(() => { - runner(); - }, interval); - this._disposable = { - dispose: () => { - context.clearInterval(handle as any); - this._disposable = undefined; - } - }; - } - - dispose(): void { - this.cancel(); - this._isDisposed = true; - } -} diff --git a/src/browser/scrollable/browser.ts b/src/browser/scrollable/browser.ts index 495ee92c..93031758 100644 --- a/src/browser/scrollable/browser.ts +++ b/src/browser/scrollable/browser.ts @@ -3,139 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CodeWindow, mainWindow } from './window'; -import { Emitter } from './event'; - -class WindowManager { - - static readonly INSTANCE = new WindowManager(); - - // --- Zoom Level - - private readonly mapWindowIdToZoomLevel = new Map(); - - private readonly _onDidChangeZoomLevel = new Emitter(); - readonly onDidChangeZoomLevel = this._onDidChangeZoomLevel.event; - - getZoomLevel(targetWindow: Window): number { - return this.mapWindowIdToZoomLevel.get(this.getWindowId(targetWindow)) ?? 0; - } - setZoomLevel(zoomLevel: number, targetWindow: Window): void { - if (this.getZoomLevel(targetWindow) === zoomLevel) { - return; - } - - const targetWindowId = this.getWindowId(targetWindow); - this.mapWindowIdToZoomLevel.set(targetWindowId, zoomLevel); - this._onDidChangeZoomLevel.fire(targetWindowId); - } - - // --- Zoom Factor - - private readonly mapWindowIdToZoomFactor = new Map(); - - getZoomFactor(targetWindow: Window): number { - return this.mapWindowIdToZoomFactor.get(this.getWindowId(targetWindow)) ?? 1; - } - setZoomFactor(zoomFactor: number, targetWindow: Window): void { - this.mapWindowIdToZoomFactor.set(this.getWindowId(targetWindow), zoomFactor); - } - - // --- Fullscreen - - private readonly _onDidChangeFullscreen = new Emitter(); - readonly onDidChangeFullscreen = this._onDidChangeFullscreen.event; - - private readonly mapWindowIdToFullScreen = new Map(); - - setFullscreen(fullscreen: boolean, targetWindow: Window): void { - if (this.isFullscreen(targetWindow) === fullscreen) { - return; - } - - const windowId = this.getWindowId(targetWindow); - this.mapWindowIdToFullScreen.set(windowId, fullscreen); - this._onDidChangeFullscreen.fire(windowId); - } - isFullscreen(targetWindow: Window): boolean { - return !!this.mapWindowIdToFullScreen.get(this.getWindowId(targetWindow)); - } - - private getWindowId(targetWindow: Window): number { - return (targetWindow as CodeWindow).vscodeWindowId; - } -} - -export function addMatchMediaChangeListener(targetWindow: Window, query: string | MediaQueryList, callback: (this: MediaQueryList, ev: MediaQueryListEvent) => any): void { - if (typeof query === 'string') { - query = targetWindow.matchMedia(query); - } - query.addEventListener('change', callback); -} - -/** A zoom index, e.g. 1, 2, 3 */ -export function setZoomLevel(zoomLevel: number, targetWindow: Window): void { - WindowManager.INSTANCE.setZoomLevel(zoomLevel, targetWindow); -} -export function getZoomLevel(targetWindow: Window): number { - return WindowManager.INSTANCE.getZoomLevel(targetWindow); -} -export const onDidChangeZoomLevel = WindowManager.INSTANCE.onDidChangeZoomLevel; - -/** The zoom scale for an index, e.g. 1, 1.2, 1.4 */ -export function getZoomFactor(targetWindow: Window): number { - return WindowManager.INSTANCE.getZoomFactor(targetWindow); -} -export function setZoomFactor(zoomFactor: number, targetWindow: Window): void { - WindowManager.INSTANCE.setZoomFactor(zoomFactor, targetWindow); -} - -export function setFullscreen(fullscreen: boolean, targetWindow: Window): void { - WindowManager.INSTANCE.setFullscreen(fullscreen, targetWindow); -} -export function isFullscreen(targetWindow: Window): boolean { - return WindowManager.INSTANCE.isFullscreen(targetWindow); -} -export const onDidChangeFullscreen = WindowManager.INSTANCE.onDidChangeFullscreen; - const userAgent = typeof navigator === 'object' ? navigator.userAgent : ''; export const isFirefox = (userAgent.indexOf('Firefox') >= 0); -export const isWebKit = (userAgent.indexOf('AppleWebKit') >= 0); export const isChrome = (userAgent.indexOf('Chrome') >= 0); export const isSafari = (!isChrome && (userAgent.indexOf('Safari') >= 0)); -export const isWebkitWebView = (!isChrome && !isSafari && isWebKit); -export const isElectron = (userAgent.indexOf('Electron/') >= 0); -export const isAndroid = (userAgent.indexOf('Android') >= 0); -let standalone = false; -if (typeof mainWindow.matchMedia === 'function') { - const standaloneMatchMedia = mainWindow.matchMedia('(display-mode: standalone) or (display-mode: window-controls-overlay)'); - const fullScreenMatchMedia = mainWindow.matchMedia('(display-mode: fullscreen)'); - standalone = standaloneMatchMedia.matches; - addMatchMediaChangeListener(mainWindow, standaloneMatchMedia, ({ matches }) => { - // entering fullscreen would change standaloneMatchMedia.matches to false - // if standalone is true (running as PWA) and entering fullscreen, skip this change - if (standalone && fullScreenMatchMedia.matches) { - return; - } - // otherwise update standalone (browser to PWA or PWA to browser) - standalone = matches; - }); -} -export function isStandalone(): boolean { - return standalone; -} - -// Visible means that the feature is enabled, not necessarily being rendered -// e.g. visible is true even in fullscreen mode where the controls are hidden -// See docs at https://developer.mozilla.org/en-US/docs/Web/API/WindowControlsOverlay/visible -export function isWCOEnabled(): boolean { - return (navigator as any)?.windowControlsOverlay?.visible; -} - -// Returns the bounding rect of the titlebar area if it is supported and defined -// See docs at https://developer.mozilla.org/en-US/docs/Web/API/WindowControlsOverlay/getTitlebarAreaRect -export function getWCOBoundingRect(): DOMRect | undefined { - return (navigator as any)?.windowControlsOverlay?.getTitlebarAreaRect(); +export function getZoomFactor(_targetWindow: Window): number { + return 1; } diff --git a/src/browser/scrollable/collections.ts b/src/browser/scrollable/collections.ts deleted file mode 100644 index 3a1505d9..00000000 --- a/src/browser/scrollable/collections.ts +++ /dev/null @@ -1,140 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * An interface for a JavaScript object that - * acts a dictionary. The keys are strings. - */ -export type IStringDictionary = Record; - -/** - * An interface for a JavaScript object that - * acts a dictionary. The keys are numbers. - */ -export type INumberDictionary = Record; - -/** - * Groups the collection into a dictionary based on the provided - * group function. - */ -export function groupBy(data: V[], groupFn: (element: V) => K): Record { - const result: Record = Object.create(null); - for (const element of data) { - const key = groupFn(element); - let target = result[key]; - if (!target) { - target = result[key] = []; - } - target.push(element); - } - return result; -} - -export function diffSets(before: Set, after: Set): { removed: T[], added: T[] } { - const removed: T[] = []; - const added: T[] = []; - for (const element of before) { - if (!after.has(element)) { - removed.push(element); - } - } - for (const element of after) { - if (!before.has(element)) { - added.push(element); - } - } - return { removed, added }; -} - -export function diffMaps(before: Map, after: Map): { removed: V[], added: V[] } { - const removed: V[] = []; - const added: V[] = []; - for (const [index, value] of before) { - if (!after.has(index)) { - removed.push(value); - } - } - for (const [index, value] of after) { - if (!before.has(index)) { - added.push(value); - } - } - return { removed, added }; -} - -/** - * Computes the intersection of two sets. - * - * @param setA - The first set. - * @param setB - The second iterable. - * @returns A new set containing the elements that are in both `setA` and `setB`. - */ -export function intersection(setA: Set, setB: Iterable): Set { - const result = new Set(); - for (const elem of setB) { - if (setA.has(elem)) { - result.add(elem); - } - } - return result; -} - -export class SetWithKey implements Set { - private _map = new Map(); - - constructor(values: T[], private toKey: (t: T) => any) { - for (const value of values) { - this.add(value); - } - } - - get size(): number { - return this._map.size; - } - - add(value: T): this { - const key = this.toKey(value); - this._map.set(key, value); - return this; - } - - delete(value: T): boolean { - return this._map.delete(this.toKey(value)); - } - - has(value: T): boolean { - return this._map.has(this.toKey(value)); - } - - *entries(): IterableIterator<[T, T]> { - for (const entry of this._map.values()) { - yield [entry, entry]; - } - } - - keys(): IterableIterator { - return this.values(); - } - - *values(): IterableIterator { - for (const entry of this._map.values()) { - yield entry; - } - } - - clear(): void { - this._map.clear(); - } - - forEach(callbackfn: (value: T, value2: T, set: Set) => void, thisArg?: any): void { - this._map.forEach(entry => callbackfn.call(thisArg, entry, entry, this)); - } - - [Symbol.iterator](): IterableIterator { - return this.values(); - } - - [Symbol.toStringTag]: string = 'SetWithKey'; -} diff --git a/src/browser/scrollable/decorators.ts b/src/browser/scrollable/decorators.ts index ae9c760a..134008f0 100644 --- a/src/browser/scrollable/decorators.ts +++ b/src/browser/scrollable/decorators.ts @@ -3,27 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -function createDecorator(mapFn: (fn: Function, key: string) => Function): Function { - return (target: any, key: string, descriptor: any) => { - let fnKey: string | null = null; - let fn: Function | null = null; - - if (typeof descriptor.value === 'function') { - fnKey = 'value'; - fn = descriptor.value; - } else if (typeof descriptor.get === 'function') { - fnKey = 'get'; - fn = descriptor.get; - } - - if (!fn) { - throw new Error('not supported'); - } - - descriptor[fnKey!] = mapFn(fn, key); - }; -} - export function memoize(_target: any, key: string, descriptor: any) { let fnKey: string | null = null; let fn: Function | null = null; @@ -59,72 +38,3 @@ export function memoize(_target: any, key: string, descriptor: any) { }; } -export interface IDebounceReducer { - (previousValue: T, ...args: any[]): T; -} - -export function debounce(delay: number, reducer?: IDebounceReducer, initialValueProvider?: () => T): Function { - return createDecorator((fn, key) => { - const timerKey = `$debounce$${key}`; - const resultKey = `$debounce$result$${key}`; - - return function (this: any, ...args: any[]) { - if (!this[resultKey]) { - this[resultKey] = initialValueProvider ? initialValueProvider() : undefined; - } - - clearTimeout(this[timerKey]); - - if (reducer) { - this[resultKey] = reducer(this[resultKey], ...args); - args = [this[resultKey]]; - } - - this[timerKey] = setTimeout(() => { - fn.apply(this, args); - this[resultKey] = initialValueProvider ? initialValueProvider() : undefined; - }, delay); - }; - }); -} - -export function throttle(delay: number, reducer?: IDebounceReducer, initialValueProvider?: () => T): Function { - return createDecorator((fn, key) => { - const timerKey = `$throttle$timer$${key}`; - const resultKey = `$throttle$result$${key}`; - const lastRunKey = `$throttle$lastRun$${key}`; - const pendingKey = `$throttle$pending$${key}`; - - return function (this: any, ...args: any[]) { - if (!this[resultKey]) { - this[resultKey] = initialValueProvider ? initialValueProvider() : undefined; - } - if (this[lastRunKey] === null || this[lastRunKey] === undefined) { - this[lastRunKey] = -Number.MAX_VALUE; - } - - if (reducer) { - this[resultKey] = reducer(this[resultKey], ...args); - } - - if (this[pendingKey]) { - return; - } - - const nextTime = this[lastRunKey] + delay; - if (nextTime <= Date.now()) { - this[lastRunKey] = Date.now(); - fn.apply(this, [this[resultKey]]); - this[resultKey] = initialValueProvider ? initialValueProvider() : undefined; - } else { - this[pendingKey] = true; - this[timerKey] = setTimeout(() => { - this[pendingKey] = false; - this[lastRunKey] = Date.now(); - fn.apply(this, [this[resultKey]]); - this[resultKey] = initialValueProvider ? initialValueProvider() : undefined; - }, nextTime - Date.now()); - } - }; - }); -} diff --git a/src/browser/scrollable/dom.ts b/src/browser/scrollable/dom.ts index 257c2e64..fa56cd09 100644 --- a/src/browser/scrollable/dom.ts +++ b/src/browser/scrollable/dom.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { IntervalTimer } from './async'; -import { Emitter, Event } from './event'; -import { DisposableStore, IDisposable } from './lifecycle'; +import { IntervalTimer } from 'common/Async'; +import { Emitter, IEvent } from 'common/Event'; +import { DisposableStore, IDisposable } from 'common/Lifecycle'; export interface IRegisteredWindow { readonly window: Window; @@ -13,7 +13,7 @@ export interface IRegisteredWindow { } const _onDidRegisterWindow = new Emitter(); -export const onDidRegisterWindow: Event = _onDidRegisterWindow.event; +export const onDidRegisterWindow: IEvent = _onDidRegisterWindow.event; export function registerWindow(window: Window): IDisposable { const disposables = new DisposableStore(); @@ -88,14 +88,7 @@ export const EventType = { WHEEL: 'wheel' } as const; -export interface IDomNodePagePosition { - left: number; - top: number; - width: number; - height: number; -} - -export function getDomNodePagePosition(domNode: HTMLElement): IDomNodePagePosition { +export function getDomNodePagePosition(domNode: HTMLElement): { left: number, top: number, width: number, height: number } { const bb = domNode.getBoundingClientRect(); const win = getWindow(domNode); return { diff --git a/src/browser/scrollable/event.ts b/src/browser/scrollable/event.ts deleted file mode 100644 index 20fb94d9..00000000 --- a/src/browser/scrollable/event.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (c) 2026 The xterm.js authors. All rights reserved. - * @license MIT - * - * Minimal event utilities for scrollable components. - */ - -import { Disposable, DisposableStore, IDisposable, toDisposable } from './lifecycle'; - -export interface Event { - (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable; -} - -export class Emitter { - private _listeners: { fn: (e: T) => any, thisArgs: any }[] = []; - private _disposed = false; - private _event: Event | undefined; - - public get event(): Event { - if (this._event) { - return this._event; - } - this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => { - if (this._disposed) { - return Disposable.None; - } - - const entry = { fn: listener, thisArgs }; - this._listeners.push(entry); - - const result = toDisposable(() => { - const idx = this._listeners.indexOf(entry); - if (idx !== -1) { - this._listeners.splice(idx, 1); - } - }); - - if (disposables) { - if (Array.isArray(disposables)) { - disposables.push(result); - } else { - disposables.add(result); - } - } - - return result; - }; - return this._event; - } - - public fire(event: T): void { - if (this._disposed || this._listeners.length === 0) { - return; - } - if (this._listeners.length === 1) { - const { fn, thisArgs } = this._listeners[0]; - fn.call(thisArgs, event); - return; - } - - const listeners = this._listeners.slice(); - for (const { fn, thisArgs } of listeners) { - fn.call(thisArgs, event); - } - } - - public dispose(): void { - if (this._disposed) { - return; - } - this._disposed = true; - this._listeners.length = 0; - } -} - -export namespace Event { - export const None: Event = () => Disposable.None; - - export function runAndSubscribe(event: Event, handler: (e: T) => void, initial: T): IDisposable; - export function runAndSubscribe(event: Event, handler: (e: T | undefined) => void): IDisposable; - export function runAndSubscribe(event: Event, handler: (e: T | undefined) => void, initial?: T): IDisposable { - handler(initial); - return event(e => handler(e)); - } -} diff --git a/src/browser/scrollable/functional.ts b/src/browser/scrollable/functional.ts deleted file mode 100644 index 803f61c5..00000000 --- a/src/browser/scrollable/functional.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * Given a function, returns a function that is only calling that function once. - */ -export function createSingleCallFunction(this: unknown, fn: T, fnDidRunCallback?: () => void): T { - const _this = this; - let didCall = false; - let result: unknown; - - return function () { - if (didCall) { - return result; - } - - didCall = true; - if (fnDidRunCallback) { - try { - result = fn.apply(_this, arguments); - } finally { - fnDidRunCallback(); - } - } else { - result = fn.apply(_this, arguments); - } - - return result; - } as unknown as T; -} diff --git a/src/browser/scrollable/globalPointerMoveMonitor.ts b/src/browser/scrollable/globalPointerMoveMonitor.ts index fdc024c1..cf692571 100644 --- a/src/browser/scrollable/globalPointerMoveMonitor.ts +++ b/src/browser/scrollable/globalPointerMoveMonitor.ts @@ -4,21 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from './dom'; -import { DisposableStore, IDisposable, toDisposable } from './lifecycle'; +import { DisposableStore, IDisposable, toDisposable } from 'common/Lifecycle'; -export interface IPointerMoveCallback { - (event: PointerEvent): void; -} - -export interface IOnStopCallback { - (browserEvent?: PointerEvent | KeyboardEvent): void; -} +type PointerMoveCallback = (event: PointerEvent) => void; +type OnStopCallback = (browserEvent?: PointerEvent | KeyboardEvent) => void; export class GlobalPointerMoveMonitor implements IDisposable { private readonly _hooks = new DisposableStore(); - private _pointerMoveCallback: IPointerMoveCallback | null = null; - private _onStopCallback: IOnStopCallback | null = null; + private _pointerMoveCallback: PointerMoveCallback | null = null; + private _onStopCallback: OnStopCallback | null = null; public dispose(): void { this.stopMonitoring(false); @@ -48,8 +43,8 @@ export class GlobalPointerMoveMonitor implements IDisposable { initialElement: Element, pointerId: number, initialButtons: number, - pointerMoveCallback: IPointerMoveCallback, - onStopCallback: IOnStopCallback + pointerMoveCallback: PointerMoveCallback, + onStopCallback: OnStopCallback ): void { if (this.isMonitoring()) { this.stopMonitoring(false); diff --git a/src/browser/scrollable/iframe.ts b/src/browser/scrollable/iframe.ts index f595b385..a6259398 100644 --- a/src/browser/scrollable/iframe.ts +++ b/src/browser/scrollable/iframe.ts @@ -114,22 +114,3 @@ export class IframeUtils { /** * Returns a sha-256 composed of `parentOrigin` and `salt` converted to base 32 */ -export async function parentOriginHash(parentOrigin: string, salt: string): Promise { - // This same code is also inlined at `src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html` - if (!crypto.subtle) { - throw new Error(`'crypto.subtle' is not available so webviews will not work. This is likely because the editor is not running in a secure context (https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).`); - } - - const strData = JSON.stringify({ parentOrigin, salt }); - const encoder = new TextEncoder(); - const arrData = encoder.encode(strData); - const hash = await crypto.subtle.digest('sha-256', arrData); - return sha256AsBase32(hash); -} - -function sha256AsBase32(bytes: ArrayBuffer): string { - const array = Array.from(new Uint8Array(bytes)); - const hexArray = array.map(b => b.toString(16).padStart(2, '0')).join(''); - // sha256 has 256 bits, so we need at most ceil(lg(2^256-1)/lg(32)) = 52 chars to represent it in base 32 - return BigInt(`0x${hexArray}`).toString(32).padStart(52, '0'); -} diff --git a/src/browser/scrollable/iterator.ts b/src/browser/scrollable/iterator.ts deleted file mode 100644 index 5430bac4..00000000 --- a/src/browser/scrollable/iterator.ts +++ /dev/null @@ -1,159 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export namespace Iterable { - - export function is(thing: any): thing is Iterable { - return thing && typeof thing === 'object' && typeof thing[Symbol.iterator] === 'function'; - } - - const _empty: Iterable = Object.freeze([]); - export function empty(): Iterable { - return _empty; - } - - export function* single(element: T): Iterable { - yield element; - } - - export function wrap(iterableOrElement: Iterable | T): Iterable { - if (is(iterableOrElement)) { - return iterableOrElement; - } - return single(iterableOrElement); - - } - - export function from(iterable: Iterable | undefined | null): Iterable { - return iterable || _empty; - } - - export function* reverse(array: T[]): Iterable { - for (let i = array.length - 1; i >= 0; i--) { - yield array[i]; - } - } - - export function isEmpty(iterable: Iterable | undefined | null): boolean { - return !iterable || iterable[Symbol.iterator]().next().done === true; - } - - export function first(iterable: Iterable): T | undefined { - return iterable[Symbol.iterator]().next().value; - } - - export function some(iterable: Iterable, predicate: (t: T, i: number) => unknown): boolean { - let i = 0; - for (const element of iterable) { - if (predicate(element, i++)) { - return true; - } - } - return false; - } - - export function find(iterable: Iterable, predicate: (t: T) => t is R): R | undefined; - export function find(iterable: Iterable, predicate: (t: T) => boolean): T | undefined; - export function find(iterable: Iterable, predicate: (t: T) => boolean): T | undefined { - for (const element of iterable) { - if (predicate(element)) { - return element; - } - } - - return undefined; - } - - export function filter(iterable: Iterable, predicate: (t: T) => t is R): Iterable; - export function filter(iterable: Iterable, predicate: (t: T) => boolean): Iterable; - export function* filter(iterable: Iterable, predicate: (t: T) => boolean): Iterable { - for (const element of iterable) { - if (predicate(element)) { - yield element; - } - } - } - - export function* map(iterable: Iterable, fn: (t: T, index: number) => R): Iterable { - let index = 0; - for (const element of iterable) { - yield fn(element, index++); - } - } - - export function* flatMap(iterable: Iterable, fn: (t: T, index: number) => Iterable): Iterable { - let index = 0; - for (const element of iterable) { - yield* fn(element, index++); - } - } - - export function* concat(...iterables: Iterable[]): Iterable { - for (const iterable of iterables) { - yield* iterable; - } - } - - export function reduce(iterable: Iterable, reducer: (previousValue: R, currentValue: T) => R, initialValue: R): R { - let value = initialValue; - for (const element of iterable) { - value = reducer(value, element); - } - return value; - } - - /** - * Returns an iterable slice of the array, with the same semantics as `array.slice()`. - */ - export function* slice(arr: ReadonlyArray, from: number, to = arr.length): Iterable { - if (from < 0) { - from += arr.length; - } - - if (to < 0) { - to += arr.length; - } else if (to > arr.length) { - to = arr.length; - } - - for (; from < to; from++) { - yield arr[from]; - } - } - - /** - * Consumes `atMost` elements from iterable and returns the consumed elements, - * and an iterable for the rest of the elements. - */ - export function consume(iterable: Iterable, atMost: number = Number.POSITIVE_INFINITY): [T[], Iterable] { - const consumed: T[] = []; - - if (atMost === 0) { - return [consumed, iterable]; - } - - const iterator = iterable[Symbol.iterator](); - - for (let i = 0; i < atMost; i++) { - const next = iterator.next(); - - if (next.done) { - return [consumed, Iterable.empty()]; - } - - consumed.push(next.value); - } - - return [consumed, { [Symbol.iterator]() { return iterator; } }]; - } - - export async function asyncToArray(iterable: AsyncIterable): Promise { - const result: T[] = []; - for await (const item of iterable) { - result.push(item); - } - return Promise.resolve(result); - } -} diff --git a/src/browser/scrollable/lifecycle.ts b/src/browser/scrollable/lifecycle.ts deleted file mode 100644 index e848334f..00000000 --- a/src/browser/scrollable/lifecycle.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright (c) 2026 The xterm.js authors. All rights reserved. - * @license MIT - * - * Minimal lifecycle utilities for scrollable components. - */ - -export interface IDisposable { - dispose(): void; -} - -export function toDisposable(fn: () => void): IDisposable { - return { dispose: fn }; -} - -export function dispose(disposable: T): T; -export function dispose(disposable: T | undefined): T | undefined; -export function dispose(disposables: T[]): T[]; -export function dispose(arg: T | T[] | undefined): T | T[] | undefined { - if (!arg) { - return arg; - } - if (Array.isArray(arg)) { - for (const d of arg) { - d.dispose(); - } - return []; - } - arg.dispose(); - return arg; -} - -export function combinedDisposable(...disposables: IDisposable[]): IDisposable { - return toDisposable(() => dispose(disposables)); -} - -export class DisposableStore implements IDisposable { - private readonly _disposables = new Set(); - private _isDisposed = false; - - public add(o: T): T { - if (this._isDisposed) { - o.dispose(); - } else { - this._disposables.add(o); - } - return o; - } - - public dispose(): void { - if (this._isDisposed) { - return; - } - this._isDisposed = true; - for (const d of this._disposables) { - d.dispose(); - } - this._disposables.clear(); - } - - public clear(): void { - for (const d of this._disposables) { - d.dispose(); - } - this._disposables.clear(); - } -} - -export abstract class Disposable implements IDisposable { - static readonly None: IDisposable = Object.freeze({ dispose() { } }); - - protected readonly _store = new DisposableStore(); - - public dispose(): void { - this._store.dispose(); - } - - protected _register(o: T): T { - return this._store.add(o); - } -} - -export function markAsSingleton(singleton: T): T { - return singleton; -} - -export class MutableDisposable implements IDisposable { - private _value: T | undefined; - private _isDisposed = false; - - public get value(): T | undefined { - return this._isDisposed ? undefined : this._value; - } - - public set value(value: T | undefined) { - if (this._isDisposed || value === this._value) { - return; - } - this._value?.dispose(); - this._value = value; - } - - public clear(): void { - this.value = undefined; - } - - public dispose(): void { - this._isDisposed = true; - this._value?.dispose(); - this._value = undefined; - } -} diff --git a/src/browser/scrollable/map.ts b/src/browser/scrollable/map.ts deleted file mode 100644 index 900df9c6..00000000 --- a/src/browser/scrollable/map.ts +++ /dev/null @@ -1,202 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export function getOrSet(map: Map, key: K, value: V): V { - let result = map.get(key); - if (result === undefined) { - result = value; - map.set(key, result); - } - - return result; -} - -export function mapToString(map: Map): string { - const entries: string[] = []; - map.forEach((value, key) => { - entries.push(`${key} => ${value}`); - }); - - return `Map(${map.size}) {${entries.join(', ')}}`; -} - -export function setToString(set: Set): string { - const entries: K[] = []; - set.forEach(value => { - entries.push(value); - }); - - return `Set(${set.size}) {${entries.join(', ')}}`; -} - -export const enum Touch { - None = 0, - AsOld = 1, - AsNew = 2 -} - -export class CounterSet { - - private map = new Map(); - - add(value: T): CounterSet { - this.map.set(value, (this.map.get(value) || 0) + 1); - return this; - } - - delete(value: T): boolean { - let counter = this.map.get(value) || 0; - - if (counter === 0) { - return false; - } - - counter--; - - if (counter === 0) { - this.map.delete(value); - } else { - this.map.set(value, counter); - } - - return true; - } - - has(value: T): boolean { - return this.map.has(value); - } -} - -/** - * A map that allows access both by keys and values. - * **NOTE**: values need to be unique. - */ -export class BidirectionalMap { - - private readonly _m1 = new Map(); - private readonly _m2 = new Map(); - - constructor(entries?: ReadonlyArray) { - if (entries) { - for (const [key, value] of entries) { - this.set(key, value); - } - } - } - - clear(): void { - this._m1.clear(); - this._m2.clear(); - } - - set(key: K, value: V): void { - this._m1.set(key, value); - this._m2.set(value, key); - } - - get(key: K): V | undefined { - return this._m1.get(key); - } - - getKey(value: V): K | undefined { - return this._m2.get(value); - } - - delete(key: K): boolean { - const value = this._m1.get(key); - if (value === undefined) { - return false; - } - this._m1.delete(key); - this._m2.delete(value); - return true; - } - - forEach(callbackfn: (value: V, key: K, map: BidirectionalMap) => void, thisArg?: any): void { - this._m1.forEach((value, key) => { - callbackfn.call(thisArg, value, key, this); - }); - } - - keys(): IterableIterator { - return this._m1.keys(); - } - - values(): IterableIterator { - return this._m1.values(); - } -} - -export class SetMap { - - private map = new Map>(); - - add(key: K, value: V): void { - let values = this.map.get(key); - - if (!values) { - values = new Set(); - this.map.set(key, values); - } - - values.add(value); - } - - delete(key: K, value: V): void { - const values = this.map.get(key); - - if (!values) { - return; - } - - values.delete(value); - - if (values.size === 0) { - this.map.delete(key); - } - } - - forEach(key: K, fn: (value: V) => void): void { - const values = this.map.get(key); - - if (!values) { - return; - } - - values.forEach(fn); - } - - get(key: K): ReadonlySet { - const values = this.map.get(key); - if (!values) { - return new Set(); - } - return values; - } -} - -export function mapsStrictEqualIgnoreOrder(a: Map, b: Map): boolean { - if (a === b) { - return true; - } - - if (a.size !== b.size) { - return false; - } - - for (const [key, value] of a) { - if (!b.has(key) || b.get(key) !== value) { - return false; - } - } - - for (const [key] of b) { - if (!a.has(key)) { - return false; - } - } - - return true; -} diff --git a/src/browser/scrollable/mouseEvent.ts b/src/browser/scrollable/mouseEvent.ts index b4ec846a..550d665d 100644 --- a/src/browser/scrollable/mouseEvent.ts +++ b/src/browser/scrollable/mouseEvent.ts @@ -5,7 +5,7 @@ import * as browser from './browser'; import { IframeUtils } from './iframe'; -import * as platform from './platform'; +import * as platform from 'common/Platform'; export interface IMouseEvent { readonly browserEvent: MouseEvent; @@ -148,7 +148,7 @@ export class StandardWheelEvent { const ev = e as unknown as WheelEvent; if (ev.deltaMode === ev.DOM_DELTA_LINE) { - if (browser.isFirefox && !platform.isMacintosh) { + if (browser.isFirefox && !platform.isMac) { this.deltaY = -e.deltaY / 3; } else { this.deltaY = -e.deltaY; @@ -172,7 +172,7 @@ export class StandardWheelEvent { const ev = e as unknown as WheelEvent; if (ev.deltaMode === ev.DOM_DELTA_LINE) { - if (browser.isFirefox && !platform.isMacintosh) { + if (browser.isFirefox && !platform.isMac) { this.deltaX = -e.deltaX / 3; } else { this.deltaX = -e.deltaX; diff --git a/src/browser/scrollable/nls.ts b/src/browser/scrollable/nls.ts deleted file mode 100644 index 1661dfb0..00000000 --- a/src/browser/scrollable/nls.ts +++ /dev/null @@ -1,90 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export interface ILocalizeInfo { - key: string; - comment: string[]; -} - -export function localize(info: ILocalizeInfo | string, message: string, ...args: (string | number | boolean | undefined | null)[]): string { - return message; -} - -export interface INLSLanguagePackConfiguration { - - /** - * The path to the translations config file that contains pointers to - * all message bundles for `main` and extensions. - */ - readonly translationsConfigFile: string; - - /** - * The path to the file containing the translations for this language - * pack as flat string array. - */ - readonly messagesFile: string; - - /** - * The path to the file that can be used to signal a corrupt language - * pack, for example when reading the `messagesFile` fails. This will - * instruct the application to re-create the cache on next startup. - */ - readonly corruptMarkerFile: string; -} - -export interface INLSConfiguration { - - /** - * Locale as defined in `argv.json` or `app.getLocale()`. - */ - readonly userLocale: string; - - /** - * Locale as defined by the OS (e.g. `app.getPreferredSystemLanguages()`). - */ - readonly osLocale: string; - - /** - * The actual language of the UI that ends up being used considering `userLocale` - * and `osLocale`. - */ - readonly resolvedLanguage: string; - - /** - * Defined if a language pack is used that is not the - * default english language pack. This requires a language - * pack to be installed as extension. - */ - readonly languagePack?: INLSLanguagePackConfiguration; - - /** - * The path to the file containing the default english messages - * as flat string array. The file is only present in built - * versions of the application. - */ - readonly defaultMessagesFile: string; - - /** - * Below properties are deprecated and only there to continue support - * for `vscode-nls` module that depends on them. - * Refs https://github.com/microsoft/vscode-nls/blob/main/src/node/main.ts#L36-L46 - */ - /** @deprecated */ - readonly locale: string; - /** @deprecated */ - readonly availableLanguages: Record; - /** @deprecated */ - readonly _languagePackSupport?: boolean; - /** @deprecated */ - readonly _languagePackId?: string; - /** @deprecated */ - readonly _translationsConfigFile?: string; - /** @deprecated */ - readonly _cacheRoot?: string; - /** @deprecated */ - readonly _resolvedLanguagePackCoreLocation?: string; - /** @deprecated */ - readonly _corruptedFile?: string; -} diff --git a/src/browser/scrollable/numbers.ts b/src/browser/scrollable/numbers.ts deleted file mode 100644 index 78be7f21..00000000 --- a/src/browser/scrollable/numbers.ts +++ /dev/null @@ -1,98 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export function clamp(value: number, min: number, max: number): number { - return Math.min(Math.max(value, min), max); -} - -export function rot(index: number, modulo: number): number { - return (modulo + (index % modulo)) % modulo; -} - -export class Counter { - private _next = 0; - - getNext(): number { - return this._next++; - } -} - -export class MovingAverage { - - private _n = 1; - private _val = 0; - - update(value: number): number { - this._val = this._val + (value - this._val) / this._n; - this._n += 1; - return this._val; - } - - get value(): number { - return this._val; - } -} - -export class SlidingWindowAverage { - - private _n: number = 0; - private _val = 0; - - private readonly _values: number[] = []; - private _index: number = 0; - private _sum = 0; - - constructor(size: number) { - this._values = new Array(size); - this._values.fill(0, 0, size); - } - - update(value: number): number { - const oldValue = this._values[this._index]; - this._values[this._index] = value; - this._index = (this._index + 1) % this._values.length; - - this._sum -= oldValue; - this._sum += value; - - if (this._n < this._values.length) { - this._n += 1; - } - - this._val = this._sum / this._n; - return this._val; - } - - get value(): number { - return this._val; - } -} - -/** Returns whether the point is within the triangle formed by the following 6 x/y point pairs */ -export function isPointWithinTriangle( - x: number, y: number, - ax: number, ay: number, - bx: number, by: number, - cx: number, cy: number -) { - const v0x = cx - ax; - const v0y = cy - ay; - const v1x = bx - ax; - const v1y = by - ay; - const v2x = x - ax; - const v2y = y - ay; - - const dot00 = v0x * v0x + v0y * v0y; - const dot01 = v0x * v1x + v0y * v1y; - const dot02 = v0x * v2x + v0y * v2y; - const dot11 = v1x * v1x + v1y * v1y; - const dot12 = v1x * v2x + v1y * v2y; - - const invDenom = 1 / (dot00 * dot11 - dot01 * dot01); - const u = (dot11 * dot02 - dot01 * dot12) * invDenom; - const v = (dot00 * dot12 - dot01 * dot02) * invDenom; - - return u >= 0 && v >= 0 && u + v < 1; -} diff --git a/src/browser/scrollable/platform.ts b/src/browser/scrollable/platform.ts deleted file mode 100644 index 5af117f8..00000000 --- a/src/browser/scrollable/platform.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright (c) 2026 The xterm.js authors. All rights reserved. - * @license MIT - */ - -const userAgent = typeof navigator === 'object' ? navigator.userAgent : ''; -const platform = typeof navigator === 'object' ? navigator.platform : ''; - -export const isMacintosh = platform.indexOf('Mac') >= 0; -export const isWindows = platform.indexOf('Win') >= 0; -export const isLinux = platform.indexOf('Linux') >= 0; -export const isIOS = /iPad|iPhone|iPod/.test(userAgent); diff --git a/src/browser/scrollable/scrollable.ts b/src/browser/scrollable/scrollable.ts index 7e3f5858..06895e7a 100644 --- a/src/browser/scrollable/scrollable.ts +++ b/src/browser/scrollable/scrollable.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter, Event } from './event'; -import { Disposable, IDisposable } from './lifecycle'; +import { Emitter, IEvent } from 'common/Event'; +import { Disposable, IDisposable } from 'common/Lifecycle'; export const enum ScrollbarVisibility { Auto = 1, @@ -223,7 +223,7 @@ export class Scrollable extends Disposable { private _smoothScrolling: SmoothScrollingOperation | null; private _onScroll = this._register(new Emitter()); - public readonly onScroll: Event = this._onScroll.event; + public readonly onScroll: IEvent = this._onScroll.event; constructor(options: IScrollableOptions) { super(); diff --git a/src/browser/scrollable/scrollableElement.ts b/src/browser/scrollable/scrollableElement.ts index 30821215..dc973248 100644 --- a/src/browser/scrollable/scrollableElement.ts +++ b/src/browser/scrollable/scrollableElement.ts @@ -12,10 +12,10 @@ import { HorizontalScrollbar } from './horizontalScrollbar'; import { ScrollableElementChangeOptions, ScrollableElementCreationOptions, ScrollableElementResolvedOptions } from './scrollableElementOptions'; import { VerticalScrollbar } from './verticalScrollbar'; import { Widget } from './widget'; -import { TimeoutTimer } from './async'; -import { Emitter, Event } from './event'; -import { IDisposable, dispose } from './lifecycle'; -import * as platform from './platform'; +import { TimeoutTimer } from 'common/Async'; +import { Emitter, IEvent } from 'common/Event'; +import { IDisposable, dispose } from 'common/Lifecycle'; +import * as platform from 'common/Platform'; import { INewScrollDimensions, INewScrollPosition, IScrollDimensions, IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from './scrollable'; // import 'vs/css!./media/scrollbars'; @@ -180,10 +180,10 @@ export abstract class AbstractScrollableElement extends Widget { private _revealOnScroll: boolean; private readonly _onScroll = this._register(new Emitter()); - public readonly onScroll: Event = this._onScroll.event; + public readonly onScroll: IEvent = this._onScroll.event; private readonly _onWillScroll = this._register(new Emitter()); - public readonly onWillScroll: Event = this._onWillScroll.event; + public readonly onWillScroll: IEvent = this._onWillScroll.event; public get options(): Readonly { return this._options; @@ -281,7 +281,7 @@ export abstract class AbstractScrollableElement extends Widget { public updateClassName(newClassName: string): void { this._options.className = newClassName; - if (platform.isMacintosh) { + if (platform.isMac) { this._options.className += ' mac'; } this._domNode.className = 'xterm-scrollable-element ' + this._options.className; @@ -382,7 +382,7 @@ export abstract class AbstractScrollableElement extends Widget { [deltaY, deltaX] = [deltaX, deltaY]; } - const shiftConvert = !platform.isMacintosh && e.browserEvent && e.browserEvent.shiftKey; + const shiftConvert = !platform.isMac && e.browserEvent && e.browserEvent.shiftKey; if ((this._options.scrollYToX || shiftConvert) && !deltaX) { deltaX = deltaY; deltaY = 0; @@ -575,54 +575,6 @@ export class SmoothScrollableElement extends AbstractScrollableElement { } -export class DomScrollableElement extends AbstractScrollableElement { - - private _element: HTMLElement; - - constructor(element: HTMLElement, options: ScrollableElementCreationOptions) { - options = options || {}; - options.mouseWheelSmoothScroll = false; - const scrollable = new Scrollable({ - forceIntegerValues: false, - smoothScrollDuration: 0, - scheduleAtNextAnimationFrame: (callback) => dom.scheduleAtNextAnimationFrame(dom.getWindow(element), callback) - }); - super(element, options, scrollable); - this._register(scrollable); - this._element = element; - this._register(this.onScroll((e) => { - if (e.scrollTopChanged) { - this._element.scrollTop = e.scrollTop; - } - if (e.scrollLeftChanged) { - this._element.scrollLeft = e.scrollLeft; - } - })); - this.scanDomNode(); - } - - public setScrollPosition(update: INewScrollPosition): void { - this._scrollable.setScrollPositionNow(update); - } - - public getScrollPosition(): IScrollPosition { - return this._scrollable.getCurrentScrollPosition(); - } - - public scanDomNode(): void { - this.setScrollDimensions({ - width: this._element.clientWidth, - scrollWidth: this._element.scrollWidth, - height: this._element.clientHeight, - scrollHeight: this._element.scrollHeight - }); - this.setScrollPosition({ - scrollLeft: this._element.scrollLeft, - scrollTop: this._element.scrollTop, - }); - } -} - function resolveOptions(opts: ScrollableElementCreationOptions): ScrollableElementResolvedOptions { const result: ScrollableElementResolvedOptions = { lazyRender: (typeof opts.lazyRender !== 'undefined' ? opts.lazyRender : false), @@ -657,7 +609,7 @@ function resolveOptions(opts: ScrollableElementCreationOptions): ScrollableEleme result.horizontalSliderSize = (typeof opts.horizontalSliderSize !== 'undefined' ? opts.horizontalSliderSize : result.horizontalScrollbarSize); result.verticalSliderSize = (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : result.verticalScrollbarSize); - if (platform.isMacintosh) { + if (platform.isMac) { result.className += ' mac'; } diff --git a/src/browser/scrollable/scrollbarArrow.ts b/src/browser/scrollable/scrollbarArrow.ts index ef403a03..81e52998 100644 --- a/src/browser/scrollable/scrollbarArrow.ts +++ b/src/browser/scrollable/scrollbarArrow.ts @@ -5,13 +5,13 @@ import { GlobalPointerMoveMonitor } from './globalPointerMoveMonitor'; import { Widget } from './widget'; -import { TimeoutTimer } from './async'; +import { TimeoutTimer } from 'common/Async'; import * as dom from './dom'; /** * The arrow image size. */ -export const ARROW_IMG_SIZE = 11; +const ARROW_IMG_SIZE = 11; export interface ScrollbarArrowOptions { onActivate: () => void; diff --git a/src/browser/scrollable/scrollbarVisibilityController.ts b/src/browser/scrollable/scrollbarVisibilityController.ts index 5ef550b4..a35388fd 100644 --- a/src/browser/scrollable/scrollbarVisibilityController.ts +++ b/src/browser/scrollable/scrollbarVisibilityController.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { FastDomNode } from './fastDomNode'; -import { TimeoutTimer } from './async'; -import { Disposable } from './lifecycle'; +import { TimeoutTimer } from 'common/Async'; +import { Disposable } from 'common/Lifecycle'; import { ScrollbarVisibility } from './scrollable'; export class ScrollbarVisibilityController extends Disposable { diff --git a/src/browser/scrollable/stopwatch.ts b/src/browser/scrollable/stopwatch.ts deleted file mode 100644 index b9993984..00000000 --- a/src/browser/scrollable/stopwatch.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// fake definition so that the valid layers check won't trip on this -declare const globalThis: { performance?: { now(): number } }; - -const hasPerformanceNow = (globalThis.performance && typeof globalThis.performance.now === 'function'); - -export class StopWatch { - - private _startTime: number; - private _stopTime: number; - - private readonly _now: () => number; - - public static create(highResolution?: boolean): StopWatch { - return new StopWatch(highResolution); - } - - constructor(highResolution?: boolean) { - this._now = hasPerformanceNow && highResolution === false ? Date.now : globalThis.performance!.now.bind(globalThis.performance); - this._startTime = this._now(); - this._stopTime = -1; - } - - public stop(): void { - this._stopTime = this._now(); - } - - public reset(): void { - this._startTime = this._now(); - this._stopTime = -1; - } - - public elapsed(): number { - if (this._stopTime !== -1) { - return this._stopTime - this._startTime; - } - return this._now() - this._startTime; - } -} diff --git a/src/browser/scrollable/symbols.ts b/src/browser/scrollable/symbols.ts deleted file mode 100644 index 5064287c..00000000 --- a/src/browser/scrollable/symbols.ts +++ /dev/null @@ -1,9 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * Can be passed into the Delayed to defer using a microtask - */ -export const MicrotaskDelay = Symbol('MicrotaskDelay'); diff --git a/src/browser/scrollable/touch.ts b/src/browser/scrollable/touch.ts index 90c60bf0..d1bd02dc 100644 --- a/src/browser/scrollable/touch.ts +++ b/src/browser/scrollable/touch.ts @@ -7,8 +7,8 @@ import * as DomUtils from './dom'; import { mainWindow } from './window'; import * as arrays from './arrays'; import { memoize } from './decorators'; -import { Event as EventUtils } from './event'; -import { Disposable, IDisposable, markAsSingleton, toDisposable } from './lifecycle'; +import { EventUtils } from 'common/Event'; +import { Disposable, IDisposable, toDisposable } from 'common/Lifecycle'; import { LinkedList } from './linkedList'; export namespace EventType { @@ -104,7 +104,7 @@ export class Gesture extends Disposable { return Disposable.None; } if (!Gesture.INSTANCE) { - Gesture.INSTANCE = markAsSingleton(new Gesture()); + Gesture.INSTANCE = new Gesture(); } const remove = Gesture.INSTANCE.targets.push(element); @@ -116,7 +116,7 @@ export class Gesture extends Disposable { return Disposable.None; } if (!Gesture.INSTANCE) { - Gesture.INSTANCE = markAsSingleton(new Gesture()); + Gesture.INSTANCE = new Gesture(); } const remove = Gesture.INSTANCE.ignoreTargets.push(element); diff --git a/src/browser/scrollable/widget.ts b/src/browser/scrollable/widget.ts index fa721796..19a476a8 100644 --- a/src/browser/scrollable/widget.ts +++ b/src/browser/scrollable/widget.ts @@ -7,7 +7,7 @@ import * as dom from './dom'; import { IKeyboardEvent, StandardKeyboardEvent } from './keyboardEvent'; import { IMouseEvent, StandardMouseEvent } from './mouseEvent'; import { Gesture } from './touch'; -import { Disposable, IDisposable } from './lifecycle'; +import { Disposable, IDisposable } from 'common/Lifecycle'; export abstract class Widget extends Disposable { diff --git a/src/browser/scrollable/window.ts b/src/browser/scrollable/window.ts index 141f1330..553c915d 100644 --- a/src/browser/scrollable/window.ts +++ b/src/browser/scrollable/window.ts @@ -7,8 +7,4 @@ export type CodeWindow = Window & typeof globalThis & { readonly vscodeWindowId: number; }; -export function ensureCodeWindow(targetWindow: Window, fallbackWindowId: number): asserts targetWindow is CodeWindow { -} - - export const mainWindow = (typeof window === 'object' ? window : globalThis) as CodeWindow; diff --git a/src/common/Async.ts b/src/common/Async.ts index f98c4942..72b8723f 100644 --- a/src/common/Async.ts +++ b/src/common/Async.ts @@ -31,3 +31,75 @@ export function disposableTimeout(handler: () => void, timeout = 0, store?: Disp store?.add(disposable); return disposable; } + +export class TimeoutTimer implements IDisposable { + private _token: any = -1; + private _isDisposed = false; + + dispose(): void { + this.cancel(); + this._isDisposed = true; + } + + cancel(): void { + if (this._token !== -1) { + clearTimeout(this._token); + this._token = -1; + } + } + + cancelAndSet(runner: () => void, timeout: number): void { + if (this._isDisposed) { + throw new Error('Calling cancelAndSet on a disposed TimeoutTimer'); + } + this.cancel(); + this._token = setTimeout(() => { + this._token = -1; + runner(); + }, timeout); + } + + setIfNotSet(runner: () => void, timeout: number): void { + if (this._isDisposed) { + throw new Error('Calling setIfNotSet on a disposed TimeoutTimer'); + } + if (this._token !== -1) { + return; + } + this._token = setTimeout(() => { + this._token = -1; + runner(); + }, timeout); + } +} + +export class IntervalTimer implements IDisposable { + private _disposable: IDisposable | undefined; + private _isDisposed = false; + + cancel(): void { + this._disposable?.dispose(); + this._disposable = undefined; + } + + cancelAndSet(runner: () => void, interval: number, context: Window | typeof globalThis = globalThis): void { + if (this._isDisposed) { + throw new Error('Calling cancelAndSet on a disposed IntervalTimer'); + } + this.cancel(); + const handle = context.setInterval(() => { + runner(); + }, interval); + this._disposable = { + dispose: () => { + context.clearInterval(handle as any); + this._disposable = undefined; + } + }; + } + + dispose(): void { + this.cancel(); + this._isDisposed = true; + } +} diff --git a/src/common/Event.ts b/src/common/Event.ts index 7173a955..f92ab2ac 100644 --- a/src/common/Event.ts +++ b/src/common/Event.ts @@ -109,8 +109,10 @@ export namespace EventUtils { }; } - export function runAndSubscribe(event: IEvent, handler: (e: T | undefined) => void): IDisposable { - handler(undefined); + export function runAndSubscribe(event: IEvent, handler: (e: T) => void, initial: T): IDisposable; + export function runAndSubscribe(event: IEvent, handler: (e: T | undefined) => void): IDisposable; + export function runAndSubscribe(event: IEvent, handler: (e: T | undefined) => void, initial?: T): IDisposable { + handler(initial); return event(e => handler(e)); } } diff --git a/src/common/Lifecycle.ts b/src/common/Lifecycle.ts index 8d7001cd..7046e466 100644 --- a/src/common/Lifecycle.ts +++ b/src/common/Lifecycle.ts @@ -14,17 +14,21 @@ export function toDisposable(fn: () => void): IDisposable { return { dispose: fn }; } -export function dispose(disposables: T | T[] | undefined): void { - if (!disposables) { - return; +export function dispose(disposable: T): T; +export function dispose(disposable: T | undefined): T | undefined; +export function dispose(disposables: T[]): T[]; +export function dispose(arg: T | T[] | undefined): T | T[] | undefined { + if (!arg) { + return arg; } - if (Array.isArray(disposables)) { - for (const d of disposables) { + if (Array.isArray(arg)) { + for (const d of arg) { d.dispose(); } - } else { - disposables.dispose(); + return []; } + arg.dispose(); + return arg; } export function combinedDisposable(...disposables: IDisposable[]): IDisposable { @@ -58,9 +62,18 @@ export class DisposableStore implements IDisposable { } this._disposables.clear(); } + + public clear(): void { + for (const d of this._disposables) { + d.dispose(); + } + this._disposables.clear(); + } } export abstract class Disposable implements IDisposable { + public static readonly None: IDisposable = Object.freeze({ dispose() { } }); + protected readonly _store = new DisposableStore(); public dispose(): void {