diff --git a/src/vs/base/browser/broadcast.ts b/src/vs/base/browser/broadcast.ts deleted file mode 100644 index 53c921fd..00000000 --- a/src/vs/base/browser/broadcast.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { mainWindow } from 'vs/base/browser/window'; -import { getErrorMessage } from 'vs/base/common/errors'; -import { Emitter } from 'vs/base/common/event'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; - -export class BroadcastDataChannel extends Disposable { - - private broadcastChannel: BroadcastChannel | undefined; - - private readonly _onDidReceiveData = this._register(new Emitter()); - readonly onDidReceiveData = this._onDidReceiveData.event; - - constructor(private readonly channelName: string) { - super(); - - // Use BroadcastChannel - if ('BroadcastChannel' in mainWindow) { - try { - this.broadcastChannel = new BroadcastChannel(channelName); - const listener = (event: MessageEvent) => { - this._onDidReceiveData.fire(event.data); - }; - this.broadcastChannel.addEventListener('message', listener); - this._register(toDisposable(() => { - if (this.broadcastChannel) { - this.broadcastChannel.removeEventListener('message', listener); - this.broadcastChannel.close(); - } - })); - } catch (error) { - console.warn('Error while creating broadcast channel. Falling back to localStorage.', getErrorMessage(error)); - } - } - - // BroadcastChannel is not supported. Use storage. - if (!this.broadcastChannel) { - this.channelName = `BroadcastDataChannel.${channelName}`; - this.createBroadcastChannel(); - } - } - - private createBroadcastChannel(): void { - const listener = (event: StorageEvent) => { - if (event.key === this.channelName && event.newValue) { - this._onDidReceiveData.fire(JSON.parse(event.newValue)); - } - }; - mainWindow.addEventListener('storage', listener); - this._register(toDisposable(() => mainWindow.removeEventListener('storage', listener))); - } - - /** - * Sends the data to other BroadcastChannel objects set up for this channel. Data can be structured objects, e.g. nested objects and arrays. - * @param data data to broadcast - */ - postData(data: T): void { - if (this.broadcastChannel) { - this.broadcastChannel.postMessage(data); - } else { - // remove previous changes so that event is triggered even if new changes are same as old changes - localStorage.removeItem(this.channelName); - localStorage.setItem(this.channelName, JSON.stringify(data)); - } - } -} diff --git a/src/vs/base/browser/defaultWorkerFactory.ts b/src/vs/base/browser/defaultWorkerFactory.ts deleted file mode 100644 index 834fa4d3..00000000 --- a/src/vs/base/browser/defaultWorkerFactory.ts +++ /dev/null @@ -1,174 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { createTrustedTypesPolicy } from 'vs/base/browser/trustedTypes'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { COI } from 'vs/base/common/network'; -import { IWorker, IWorkerCallback, IWorkerFactory, logOnceWebWorkerWarning } from 'vs/base/common/worker/simpleWorker'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; - -const ttPolicy = createTrustedTypesPolicy('defaultWorkerFactory', { createScriptURL: value => value }); - -export function createBlobWorker(blobUrl: string, options?: WorkerOptions): Worker { - if (!blobUrl.startsWith('blob:')) { - throw new URIError('Not a blob-url: ' + blobUrl); - } - return new Worker(ttPolicy ? ttPolicy.createScriptURL(blobUrl) as unknown as string : blobUrl, options); -} - -function getWorker(label: string): Worker | Promise { - // Option for hosts to overwrite the worker script (used in the standalone editor) - interface IMonacoEnvironment { - getWorker?(moduleId: string, label: string): Worker | Promise; - getWorkerUrl?(moduleId: string, label: string): string; - } - const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any).MonacoEnvironment; - if (monacoEnvironment) { - if (typeof monacoEnvironment.getWorker === 'function') { - return monacoEnvironment.getWorker('workerMain.js', label); - } - if (typeof monacoEnvironment.getWorkerUrl === 'function') { - const workerUrl = monacoEnvironment.getWorkerUrl('workerMain.js', label); - return new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label }); - } - } - // ESM-comment-begin - if (typeof require === 'function') { - // check if the JS lives on a different origin - const workerMain = require.toUrl('vs/base/worker/workerMain.js'); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321 - const workerUrl = getWorkerBootstrapUrl(workerMain, label); - return new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label }); - } - // ESM-comment-end - throw new Error(`You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker`); -} - -// ESM-comment-begin -export function getWorkerBootstrapUrl(scriptPath: string, label: string): string { - if (/^((http:)|(https:)|(file:))/.test(scriptPath) && scriptPath.substring(0, globalThis.origin.length) !== globalThis.origin) { - // this is the cross-origin case - // i.e. the webpage is running at a different origin than where the scripts are loaded from - } else { - const start = scriptPath.lastIndexOf('?'); - const end = scriptPath.lastIndexOf('#', start); - const params = start > 0 - ? new URLSearchParams(scriptPath.substring(start + 1, ~end ? end : undefined)) - : new URLSearchParams(); - - COI.addSearchParam(params, true, true); - const search = params.toString(); - if (!search) { - scriptPath = `${scriptPath}#${label}`; - } else { - scriptPath = `${scriptPath}?${params.toString()}#${label}`; - } - } - - const factoryModuleId = 'vs/base/worker/defaultWorkerFactory.js'; - const workerBaseUrl = require.toUrl(factoryModuleId).slice(0, -factoryModuleId.length); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321 - const blob = new Blob([[ - `/*${label}*/`, - `globalThis.MonacoEnvironment = { baseUrl: '${workerBaseUrl}' };`, - // VSCODE_GLOBALS: NLS - `globalThis._VSCODE_NLS_MESSAGES = ${JSON.stringify(globalThis._VSCODE_NLS_MESSAGES)};`, - `globalThis._VSCODE_NLS_LANGUAGE = ${JSON.stringify(globalThis._VSCODE_NLS_LANGUAGE)};`, - `const ttPolicy = globalThis.trustedTypes?.createPolicy('defaultWorkerFactory', { createScriptURL: value => value });`, - `importScripts(ttPolicy?.createScriptURL('${scriptPath}') ?? '${scriptPath}');`, - `/*${label}*/` - ].join('')], { type: 'application/javascript' }); - return URL.createObjectURL(blob); -} -// ESM-comment-end - -function isPromiseLike(obj: any): obj is PromiseLike { - if (typeof obj.then === 'function') { - return true; - } - return false; -} - -/** - * A worker that uses HTML5 web workers so that is has - * its own global scope and its own thread. - */ -class WebWorker extends Disposable implements IWorker { - - private readonly id: number; - private readonly label: string; - private worker: Promise | null; - - constructor(moduleId: string, id: number, label: string, onMessageCallback: IWorkerCallback, onErrorCallback: (err: any) => void) { - super(); - this.id = id; - this.label = label; - const workerOrPromise = getWorker(label); - if (isPromiseLike(workerOrPromise)) { - this.worker = workerOrPromise; - } else { - this.worker = Promise.resolve(workerOrPromise); - } - this.postMessage(moduleId, []); - this.worker.then((w) => { - w.onmessage = function (ev) { - onMessageCallback(ev.data); - }; - w.onmessageerror = onErrorCallback; - if (typeof w.addEventListener === 'function') { - w.addEventListener('error', onErrorCallback); - } - }); - this._register(toDisposable(() => { - this.worker?.then(w => { - w.onmessage = null; - w.onmessageerror = null; - w.removeEventListener('error', onErrorCallback); - w.terminate(); - }); - this.worker = null; - })); - } - - public getId(): number { - return this.id; - } - - public postMessage(message: any, transfer: Transferable[]): void { - this.worker?.then(w => { - try { - w.postMessage(message, transfer); - } catch (err) { - onUnexpectedError(err); - onUnexpectedError(new Error(`FAILED to post message to '${this.label}'-worker`, { cause: err })); - } - }); - } -} - -export class DefaultWorkerFactory implements IWorkerFactory { - - private static LAST_WORKER_ID = 0; - - private _label: string | undefined; - private _webWorkerFailedBeforeError: any; - - constructor(label: string | undefined) { - this._label = label; - this._webWorkerFailedBeforeError = false; - } - - public create(moduleId: string, onMessageCallback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker { - const workerId = (++DefaultWorkerFactory.LAST_WORKER_ID); - - if (this._webWorkerFailedBeforeError) { - throw this._webWorkerFailedBeforeError; - } - - return new WebWorker(moduleId, workerId, this._label || 'anonymous' + workerId, onMessageCallback, (err) => { - logOnceWebWorkerWarning(err); - this._webWorkerFailedBeforeError = err; - onErrorCallback(err); - }); - } -} diff --git a/src/vs/base/browser/deviceAccess.ts b/src/vs/base/browser/deviceAccess.ts deleted file mode 100644 index 17cb1beb..00000000 --- a/src/vs/base/browser/deviceAccess.ts +++ /dev/null @@ -1,108 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// https://wicg.github.io/webusb/ - -export interface UsbDeviceData { - readonly deviceClass: number; - readonly deviceProtocol: number; - readonly deviceSubclass: number; - readonly deviceVersionMajor: number; - readonly deviceVersionMinor: number; - readonly deviceVersionSubminor: number; - readonly manufacturerName?: string; - readonly productId: number; - readonly productName?: string; - readonly serialNumber?: string; - readonly usbVersionMajor: number; - readonly usbVersionMinor: number; - readonly usbVersionSubminor: number; - readonly vendorId: number; -} - -export async function requestUsbDevice(options?: { filters?: unknown[] }): Promise { - const usb = (navigator as any).usb; - if (!usb) { - return undefined; - } - - const device = await usb.requestDevice({ filters: options?.filters ?? [] }); - if (!device) { - return undefined; - } - - return { - deviceClass: device.deviceClass, - deviceProtocol: device.deviceProtocol, - deviceSubclass: device.deviceSubclass, - deviceVersionMajor: device.deviceVersionMajor, - deviceVersionMinor: device.deviceVersionMinor, - deviceVersionSubminor: device.deviceVersionSubminor, - manufacturerName: device.manufacturerName, - productId: device.productId, - productName: device.productName, - serialNumber: device.serialNumber, - usbVersionMajor: device.usbVersionMajor, - usbVersionMinor: device.usbVersionMinor, - usbVersionSubminor: device.usbVersionSubminor, - vendorId: device.vendorId, - }; -} - -// https://wicg.github.io/serial/ - -export interface SerialPortData { - readonly usbVendorId?: number | undefined; - readonly usbProductId?: number | undefined; -} - -export async function requestSerialPort(options?: { filters?: unknown[] }): Promise { - const serial = (navigator as any).serial; - if (!serial) { - return undefined; - } - - const port = await serial.requestPort({ filters: options?.filters ?? [] }); - if (!port) { - return undefined; - } - - const info = port.getInfo(); - return { - usbVendorId: info.usbVendorId, - usbProductId: info.usbProductId - }; -} - -// https://wicg.github.io/webhid/ - -export interface HidDeviceData { - readonly opened: boolean; - readonly vendorId: number; - readonly productId: number; - readonly productName: string; - readonly collections: []; -} - -export async function requestHidDevice(options?: { filters?: unknown[] }): Promise { - const hid = (navigator as any).hid; - if (!hid) { - return undefined; - } - - const devices = await hid.requestDevice({ filters: options?.filters ?? [] }); - if (!devices.length) { - return undefined; - } - - const device = devices[0]; - return { - opened: device.opened, - vendorId: device.vendorId, - productId: device.productId, - productName: device.productName, - collections: device.collections - }; -} diff --git a/src/vs/base/browser/dnd.ts b/src/vs/base/browser/dnd.ts deleted file mode 100644 index 96259a4e..00000000 --- a/src/vs/base/browser/dnd.ts +++ /dev/null @@ -1,110 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { addDisposableListener, getWindow } from 'vs/base/browser/dom'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { Mimes } from 'vs/base/common/mime'; - -/** - * A helper that will execute a provided function when the provided HTMLElement receives - * dragover event for 800ms. If the drag is aborted before, the callback will not be triggered. - */ -export class DelayedDragHandler extends Disposable { - private timeout: any; - - constructor(container: HTMLElement, callback: () => void) { - super(); - - this._register(addDisposableListener(container, 'dragover', e => { - e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome) - - if (!this.timeout) { - this.timeout = setTimeout(() => { - callback(); - - this.timeout = null; - }, 800); - } - })); - - ['dragleave', 'drop', 'dragend'].forEach(type => { - this._register(addDisposableListener(container, type, () => { - this.clearDragTimeout(); - })); - }); - } - - private clearDragTimeout(): void { - if (this.timeout) { - clearTimeout(this.timeout); - this.timeout = null; - } - } - - override dispose(): void { - super.dispose(); - - this.clearDragTimeout(); - } -} - -// Common data transfers -export const DataTransfers = { - - /** - * Application specific resource transfer type - */ - RESOURCES: 'ResourceURLs', - - /** - * Browser specific transfer type to download - */ - DOWNLOAD_URL: 'DownloadURL', - - /** - * Browser specific transfer type for files - */ - FILES: 'Files', - - /** - * Typically transfer type for copy/paste transfers. - */ - TEXT: Mimes.text, - - /** - * Internal type used to pass around text/uri-list data. - * - * This is needed to work around https://bugs.chromium.org/p/chromium/issues/detail?id=239745. - */ - INTERNAL_URI_LIST: 'application/vnd.code.uri-list', -}; - -export function applyDragImage(event: DragEvent, label: string | null, clazz: string, backgroundColor?: string | null, foregroundColor?: string | null): void { - const dragImage = document.createElement('div'); - dragImage.className = clazz; - dragImage.textContent = label; - - if (foregroundColor) { - dragImage.style.color = foregroundColor; - } - - if (backgroundColor) { - dragImage.style.background = backgroundColor; - } - - if (event.dataTransfer) { - const ownerDocument = getWindow(event).document; - ownerDocument.body.appendChild(dragImage); - event.dataTransfer.setDragImage(dragImage, -10, -10); - - // Removes the element when the DND operation is done - setTimeout(() => dragImage.remove(), 0); - } -} - -export interface IDragAndDropData { - update(dataTransfer: DataTransfer): void; - getData(): unknown; -} diff --git a/src/vs/base/browser/domObservable.ts b/src/vs/base/browser/domObservable.ts deleted file mode 100644 index dd206377..00000000 --- a/src/vs/base/browser/domObservable.ts +++ /dev/null @@ -1,17 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { createStyleSheet2 } from 'vs/base/browser/dom'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { autorun, IObservable } from 'vs/base/common/observable'; - -export function createStyleSheetFromObservable(css: IObservable): IDisposable { - const store = new DisposableStore(); - const w = store.add(createStyleSheet2()); - store.add(autorun(reader => { - w.setStyle(css.read(reader)); - })); - return store; -} diff --git a/src/vs/base/browser/event.ts b/src/vs/base/browser/event.ts deleted file mode 100644 index 760f8646..00000000 --- a/src/vs/base/browser/event.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { GestureEvent } from 'vs/base/browser/touch'; -import { Emitter, Event as BaseEvent } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; - -export type EventHandler = HTMLElement | HTMLDocument | Window; - -export interface IDomEvent { - (element: EventHandler, type: K, useCapture?: boolean): BaseEvent; - (element: EventHandler, type: string, useCapture?: boolean): BaseEvent; -} - -export interface DOMEventMap extends HTMLElementEventMap, DocumentEventMap, WindowEventMap { - '-monaco-gesturetap': GestureEvent; - '-monaco-gesturechange': GestureEvent; - '-monaco-gesturestart': GestureEvent; - '-monaco-gesturesend': GestureEvent; - '-monaco-gesturecontextmenu': GestureEvent; - 'compositionstart': CompositionEvent; - 'compositionupdate': CompositionEvent; - 'compositionend': CompositionEvent; -} - -export class DomEmitter implements IDisposable { - - private emitter: Emitter; - - get event(): BaseEvent { - return this.emitter.event; - } - - constructor(element: Window & typeof globalThis, type: WindowEventMap, useCapture?: boolean); - constructor(element: Document, type: DocumentEventMap, useCapture?: boolean); - constructor(element: EventHandler, type: K, useCapture?: boolean); - constructor(element: EventHandler, type: K, useCapture?: boolean) { - const fn = (e: Event) => this.emitter.fire(e as DOMEventMap[K]); - this.emitter = new Emitter({ - onWillAddFirstListener: () => element.addEventListener(type, fn, useCapture), - onDidRemoveLastListener: () => element.removeEventListener(type, fn, useCapture) - }); - } - - dispose(): void { - this.emitter.dispose(); - } -} diff --git a/src/vs/base/browser/fonts.ts b/src/vs/base/browser/fonts.ts deleted file mode 100644 index a5e78d00..00000000 --- a/src/vs/base/browser/fonts.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { isMacintosh, isWindows } from 'vs/base/common/platform'; - -/** - * The best font-family to be used in CSS based on the platform: - * - Windows: Segoe preferred, fallback to sans-serif - * - macOS: standard system font, fallback to sans-serif - * - Linux: standard system font preferred, fallback to Ubuntu fonts - * - * Note: this currently does not adjust for different locales. - */ -export const DEFAULT_FONT_FAMILY = isWindows ? '"Segoe WPC", "Segoe UI", sans-serif' : isMacintosh ? '-apple-system, BlinkMacSystemFont, sans-serif' : 'system-ui, "Ubuntu", "Droid Sans", sans-serif'; diff --git a/src/vs/base/browser/formattedTextRenderer.ts b/src/vs/base/browser/formattedTextRenderer.ts deleted file mode 100644 index 12371671..00000000 --- a/src/vs/base/browser/formattedTextRenderer.ts +++ /dev/null @@ -1,226 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as DOM from 'vs/base/browser/dom'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { IMouseEvent } from 'vs/base/browser/mouseEvent'; -import { DisposableStore } from 'vs/base/common/lifecycle'; - -export interface IContentActionHandler { - callback: (content: string, event: IMouseEvent | IKeyboardEvent) => void; - readonly disposables: DisposableStore; -} - -export interface FormattedTextRenderOptions { - readonly className?: string; - readonly inline?: boolean; - readonly actionHandler?: IContentActionHandler; - readonly renderCodeSegments?: boolean; -} - -export function renderText(text: string, options: FormattedTextRenderOptions = {}): HTMLElement { - const element = createElement(options); - element.textContent = text; - return element; -} - -export function renderFormattedText(formattedText: string, options: FormattedTextRenderOptions = {}): HTMLElement { - const element = createElement(options); - _renderFormattedText(element, parseFormattedText(formattedText, !!options.renderCodeSegments), options.actionHandler, options.renderCodeSegments); - return element; -} - -export function createElement(options: FormattedTextRenderOptions): HTMLElement { - const tagName = options.inline ? 'span' : 'div'; - const element = document.createElement(tagName); - if (options.className) { - element.className = options.className; - } - return element; -} - -class StringStream { - private source: string; - private index: number; - - constructor(source: string) { - this.source = source; - this.index = 0; - } - - public eos(): boolean { - return this.index >= this.source.length; - } - - public next(): string { - const next = this.peek(); - this.advance(); - return next; - } - - public peek(): string { - return this.source[this.index]; - } - - public advance(): void { - this.index++; - } -} - -const enum FormatType { - Invalid, - Root, - Text, - Bold, - Italics, - Action, - ActionClose, - Code, - NewLine -} - -interface IFormatParseTree { - type: FormatType; - content?: string; - index?: number; - children?: IFormatParseTree[]; -} - -function _renderFormattedText(element: Node, treeNode: IFormatParseTree, actionHandler?: IContentActionHandler, renderCodeSegments?: boolean) { - let child: Node | undefined; - - if (treeNode.type === FormatType.Text) { - child = document.createTextNode(treeNode.content || ''); - } else if (treeNode.type === FormatType.Bold) { - child = document.createElement('b'); - } else if (treeNode.type === FormatType.Italics) { - child = document.createElement('i'); - } else if (treeNode.type === FormatType.Code && renderCodeSegments) { - child = document.createElement('code'); - } else if (treeNode.type === FormatType.Action && actionHandler) { - const a = document.createElement('a'); - actionHandler.disposables.add(DOM.addStandardDisposableListener(a, 'click', (event) => { - actionHandler.callback(String(treeNode.index), event); - })); - - child = a; - } else if (treeNode.type === FormatType.NewLine) { - child = document.createElement('br'); - } else if (treeNode.type === FormatType.Root) { - child = element; - } - - if (child && element !== child) { - element.appendChild(child); - } - - if (child && Array.isArray(treeNode.children)) { - treeNode.children.forEach((nodeChild) => { - _renderFormattedText(child, nodeChild, actionHandler, renderCodeSegments); - }); - } -} - -function parseFormattedText(content: string, parseCodeSegments: boolean): IFormatParseTree { - - const root: IFormatParseTree = { - type: FormatType.Root, - children: [] - }; - - let actionViewItemIndex = 0; - let current = root; - const stack: IFormatParseTree[] = []; - const stream = new StringStream(content); - - while (!stream.eos()) { - let next = stream.next(); - - const isEscapedFormatType = (next === '\\' && formatTagType(stream.peek(), parseCodeSegments) !== FormatType.Invalid); - if (isEscapedFormatType) { - next = stream.next(); // unread the backslash if it escapes a format tag type - } - - if (!isEscapedFormatType && isFormatTag(next, parseCodeSegments) && next === stream.peek()) { - stream.advance(); - - if (current.type === FormatType.Text) { - current = stack.pop()!; - } - - const type = formatTagType(next, parseCodeSegments); - if (current.type === type || (current.type === FormatType.Action && type === FormatType.ActionClose)) { - current = stack.pop()!; - } else { - const newCurrent: IFormatParseTree = { - type: type, - children: [] - }; - - if (type === FormatType.Action) { - newCurrent.index = actionViewItemIndex; - actionViewItemIndex++; - } - - current.children!.push(newCurrent); - stack.push(current); - current = newCurrent; - } - } else if (next === '\n') { - if (current.type === FormatType.Text) { - current = stack.pop()!; - } - - current.children!.push({ - type: FormatType.NewLine - }); - - } else { - if (current.type !== FormatType.Text) { - const textCurrent: IFormatParseTree = { - type: FormatType.Text, - content: next - }; - current.children!.push(textCurrent); - stack.push(current); - current = textCurrent; - - } else { - current.content += next; - } - } - } - - if (current.type === FormatType.Text) { - current = stack.pop()!; - } - - if (stack.length) { - // incorrectly formatted string literal - } - - return root; -} - -function isFormatTag(char: string, supportCodeSegments: boolean): boolean { - return formatTagType(char, supportCodeSegments) !== FormatType.Invalid; -} - -function formatTagType(char: string, supportCodeSegments: boolean): FormatType { - switch (char) { - case '*': - return FormatType.Bold; - case '_': - return FormatType.Italics; - case '[': - return FormatType.Action; - case ']': - return FormatType.ActionClose; - case '`': - return supportCodeSegments ? FormatType.Code : FormatType.Invalid; - default: - return FormatType.Invalid; - } -} diff --git a/src/vs/base/browser/hash.ts b/src/vs/base/browser/hash.ts deleted file mode 100644 index a9a5b669..00000000 --- a/src/vs/base/browser/hash.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. - *--------------------------------------------------------------------------------------------*/ - -import { VSBuffer } from 'vs/base/common/buffer'; -import { StringSHA1, toHexString } from 'vs/base/common/hash'; - -export async function sha1Hex(str: string): Promise { - - // Prefer to use browser's crypto module - if (globalThis?.crypto?.subtle) { - - // Careful to use `dontUseNodeBuffer` when passing the - // buffer to the browser `crypto` API. Users reported - // native crashes in certain cases that we could trace - // back to passing node.js `Buffer` around - // (https://github.com/microsoft/vscode/issues/114227) - const buffer = VSBuffer.fromString(str, { dontUseNodeBuffer: true }).buffer; - const hash = await globalThis.crypto.subtle.digest({ name: 'sha-1' }, buffer); - - return toHexString(hash); - } - - // Otherwise fallback to `StringSHA1` - else { - const computer = new StringSHA1(); - computer.update(str); - - return computer.digest(); - } -} diff --git a/src/vs/base/browser/history.ts b/src/vs/base/browser/history.ts deleted file mode 100644 index e31b68ed..00000000 --- a/src/vs/base/browser/history.ts +++ /dev/null @@ -1,20 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; - -export interface IHistoryNavigationWidget { - - readonly element: HTMLElement; - - showPreviousValue(): void; - - showNextValue(): void; - - onDidFocus: Event; - - onDidBlur: Event; - -} diff --git a/src/vs/base/browser/indexedDB.ts b/src/vs/base/browser/indexedDB.ts deleted file mode 100644 index 6d56022c..00000000 --- a/src/vs/base/browser/indexedDB.ts +++ /dev/null @@ -1,177 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { ErrorNoTelemetry, getErrorMessage } from 'vs/base/common/errors'; -import { mark } from 'vs/base/common/performance'; - -class MissingStoresError extends Error { - constructor(readonly db: IDBDatabase) { - super('Missing stores'); - } -} - -export class DBClosedError extends Error { - readonly code = 'DBClosed'; - constructor(dbName: string) { - super(`IndexedDB database '${dbName}' is closed.`); - } -} - -export class IndexedDB { - - static async create(name: string, version: number | undefined, stores: string[]): Promise { - const database = await IndexedDB.openDatabase(name, version, stores); - return new IndexedDB(database, name); - } - - private static async openDatabase(name: string, version: number | undefined, stores: string[]): Promise { - mark(`code/willOpenDatabase/${name}`); - try { - return await IndexedDB.doOpenDatabase(name, version, stores); - } catch (err) { - if (err instanceof MissingStoresError) { - console.info(`Attempting to recreate the IndexedDB once.`, name); - - try { - // Try to delete the db - await IndexedDB.deleteDatabase(err.db); - } catch (error) { - console.error(`Error while deleting the IndexedDB`, getErrorMessage(error)); - throw error; - } - - return await IndexedDB.doOpenDatabase(name, version, stores); - } - - throw err; - } finally { - mark(`code/didOpenDatabase/${name}`); - } - } - - private static doOpenDatabase(name: string, version: number | undefined, stores: string[]): Promise { - return new Promise((c, e) => { - const request = indexedDB.open(name, version); - request.onerror = () => e(request.error); - request.onsuccess = () => { - const db = request.result; - for (const store of stores) { - if (!db.objectStoreNames.contains(store)) { - console.error(`Error while opening IndexedDB. Could not find '${store}'' object store`); - e(new MissingStoresError(db)); - return; - } - } - c(db); - }; - request.onupgradeneeded = () => { - const db = request.result; - for (const store of stores) { - if (!db.objectStoreNames.contains(store)) { - db.createObjectStore(store); - } - } - }; - }); - } - - private static deleteDatabase(database: IDBDatabase): Promise { - return new Promise((c, e) => { - // Close any opened connections - database.close(); - - // Delete the db - const deleteRequest = indexedDB.deleteDatabase(database.name); - deleteRequest.onerror = (err) => e(deleteRequest.error); - deleteRequest.onsuccess = () => c(); - }); - } - - private database: IDBDatabase | null = null; - private readonly pendingTransactions: IDBTransaction[] = []; - - constructor(database: IDBDatabase, private readonly name: string) { - this.database = database; - } - - hasPendingTransactions(): boolean { - return this.pendingTransactions.length > 0; - } - - close(): void { - if (this.pendingTransactions.length) { - this.pendingTransactions.splice(0, this.pendingTransactions.length).forEach(transaction => transaction.abort()); - } - this.database?.close(); - this.database = null; - } - - runInTransaction(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest[]): Promise; - runInTransaction(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest): Promise; - async runInTransaction(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest | IDBRequest[]): Promise { - if (!this.database) { - throw new DBClosedError(this.name); - } - const transaction = this.database.transaction(store, transactionMode); - this.pendingTransactions.push(transaction); - return new Promise((c, e) => { - transaction.oncomplete = () => { - if (Array.isArray(request)) { - c(request.map(r => r.result)); - } else { - c(request.result); - } - }; - transaction.onerror = () => e(transaction.error ? ErrorNoTelemetry.fromError(transaction.error) : new ErrorNoTelemetry('unknown error')); - transaction.onabort = () => e(transaction.error ? ErrorNoTelemetry.fromError(transaction.error) : new ErrorNoTelemetry('unknown error')); - const request = dbRequestFn(transaction.objectStore(store)); - }).finally(() => this.pendingTransactions.splice(this.pendingTransactions.indexOf(transaction), 1)); - } - - async getKeyValues(store: string, isValid: (value: unknown) => value is V): Promise> { - if (!this.database) { - throw new DBClosedError(this.name); - } - const transaction = this.database.transaction(store, 'readonly'); - this.pendingTransactions.push(transaction); - return new Promise>(resolve => { - const items = new Map(); - - const objectStore = transaction.objectStore(store); - - // Open a IndexedDB Cursor to iterate over key/values - const cursor = objectStore.openCursor(); - if (!cursor) { - return resolve(items); // this means the `ItemTable` was empty - } - - // Iterate over rows of `ItemTable` until the end - cursor.onsuccess = () => { - if (cursor.result) { - - // Keep cursor key/value in our map - if (isValid(cursor.result.value)) { - items.set(cursor.result.key.toString(), cursor.result.value); - } - - // Advance cursor to next row - cursor.result.continue(); - } else { - resolve(items); // reached end of table - } - }; - - // Error handlers - const onError = (error: Error | null) => { - console.error(`IndexedDB getKeyValues(): ${toErrorMessage(error, true)}`); - - resolve(items); - }; - cursor.onerror = () => onError(cursor.error); - transaction.onerror = () => onError(transaction.error); - }).finally(() => this.pendingTransactions.splice(this.pendingTransactions.indexOf(transaction), 1)); - } -} diff --git a/src/vs/base/browser/ui/aria/aria.css b/src/vs/base/browser/ui/aria/aria.css deleted file mode 100644 index c04b8784..00000000 --- a/src/vs/base/browser/ui/aria/aria.css +++ /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. - *--------------------------------------------------------------------------------------------*/ - -.monaco-aria-container { - position: absolute; /* try to hide from window but not from screen readers */ - left:-999em; -} diff --git a/src/vs/base/browser/ui/aria/aria.ts b/src/vs/base/browser/ui/aria/aria.ts deleted file mode 100644 index 52c0f91e..00000000 --- a/src/vs/base/browser/ui/aria/aria.ts +++ /dev/null @@ -1,163 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as dom from 'vs/base/browser/dom'; -// import 'vs/css!./aria'; - -// Use a max length since we are inserting the whole msg in the DOM and that can cause browsers to freeze for long messages #94233 -const MAX_MESSAGE_LENGTH = 20000; -let ariaContainer: HTMLElement; -let alertContainer: HTMLElement; -let alertContainer2: HTMLElement; -let statusContainer: HTMLElement; -let statusContainer2: HTMLElement; -export function setARIAContainer(parent: HTMLElement) { - ariaContainer = document.createElement('div'); - ariaContainer.className = 'monaco-aria-container'; - - const createAlertContainer = () => { - const element = document.createElement('div'); - element.className = 'monaco-alert'; - element.setAttribute('role', 'alert'); - element.setAttribute('aria-atomic', 'true'); - ariaContainer.appendChild(element); - return element; - }; - alertContainer = createAlertContainer(); - alertContainer2 = createAlertContainer(); - - const createStatusContainer = () => { - const element = document.createElement('div'); - element.className = 'monaco-status'; - element.setAttribute('aria-live', 'polite'); - element.setAttribute('aria-atomic', 'true'); - ariaContainer.appendChild(element); - return element; - }; - statusContainer = createStatusContainer(); - statusContainer2 = createStatusContainer(); - - parent.appendChild(ariaContainer); -} -/** - * Given the provided message, will make sure that it is read as alert to screen readers. - */ -export function alert(msg: string): void { - if (!ariaContainer) { - return; - } - - // Use alternate containers such that duplicated messages get read out by screen readers #99466 - if (alertContainer.textContent !== msg) { - dom.clearNode(alertContainer2); - insertMessage(alertContainer, msg); - } else { - dom.clearNode(alertContainer); - insertMessage(alertContainer2, msg); - } -} - -/** - * Given the provided message, will make sure that it is read as status to screen readers. - */ -export function status(msg: string): void { - if (!ariaContainer) { - return; - } - - if (statusContainer.textContent !== msg) { - dom.clearNode(statusContainer2); - insertMessage(statusContainer, msg); - } else { - dom.clearNode(statusContainer); - insertMessage(statusContainer2, msg); - } -} - -function insertMessage(target: HTMLElement, msg: string): void { - dom.clearNode(target); - if (msg.length > MAX_MESSAGE_LENGTH) { - msg = msg.substr(0, MAX_MESSAGE_LENGTH); - } - target.textContent = msg; - - // See https://www.paciellogroup.com/blog/2012/06/html5-accessibility-chops-aria-rolealert-browser-support/ - target.style.visibility = 'hidden'; - target.style.visibility = 'visible'; -} - -// Copied from @types/react which original came from https://www.w3.org/TR/wai-aria-1.1/#role_definitions -export type AriaRole = - | 'alert' - | 'alertdialog' - | 'application' - | 'article' - | 'banner' - | 'button' - | 'cell' - | 'checkbox' - | 'columnheader' - | 'combobox' - | 'complementary' - | 'contentinfo' - | 'definition' - | 'dialog' - | 'directory' - | 'document' - | 'feed' - | 'figure' - | 'form' - | 'grid' - | 'gridcell' - | 'group' - | 'heading' - | 'img' - | 'link' - | 'list' - | 'listbox' - | 'listitem' - | 'log' - | 'main' - | 'marquee' - | 'math' - | 'menu' - | 'menubar' - | 'menuitem' - | 'menuitemcheckbox' - | 'menuitemradio' - | 'navigation' - | 'none' - | 'note' - | 'option' - | 'presentation' - | 'progressbar' - | 'radio' - | 'radiogroup' - | 'region' - | 'row' - | 'rowgroup' - | 'rowheader' - | 'scrollbar' - | 'search' - | 'searchbox' - | 'separator' - | 'slider' - | 'spinbutton' - | 'status' - | 'switch' - | 'tab' - | 'table' - | 'tablist' - | 'tabpanel' - | 'term' - | 'textbox' - | 'timer' - | 'toolbar' - | 'tooltip' - | 'tree' - | 'treegrid' - | 'treeitem' - | (string & {}) // Prevent type collapsing to `string` - ; diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css deleted file mode 100644 index 4a3cf074..00000000 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css +++ /dev/null @@ -1,36 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-breadcrumbs { - user-select: none; - -webkit-user-select: none; - display: flex; - flex-direction: row; - flex-wrap: nowrap; - justify-content: flex-start; - outline-style: none; -} - -.monaco-breadcrumbs .monaco-breadcrumb-item { - display: flex; - align-items: center; - flex: 0 1 auto; - white-space: nowrap; - cursor: pointer; - align-self: center; - height: 100%; - outline: none; -} -.monaco-breadcrumbs.disabled .monaco-breadcrumb-item { - cursor: default; -} - -.monaco-breadcrumbs .monaco-breadcrumb-item .codicon-breadcrumb-separator { - color: inherit; -} - -.monaco-breadcrumbs .monaco-breadcrumb-item:first-of-type::before { - content: ' '; -} diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts deleted file mode 100644 index ea33ce5c..00000000 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ /dev/null @@ -1,356 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as dom from 'vs/base/browser/dom'; -import { IMouseEvent } from 'vs/base/browser/mouseEvent'; -import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; -import { commonPrefixLength } from 'vs/base/common/arrays'; -import { ThemeIcon } from 'vs/base/common/themables'; -import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -// import 'vs/css!./breadcrumbsWidget'; - -export abstract class BreadcrumbsItem { - abstract dispose(): void; - abstract equals(other: BreadcrumbsItem): boolean; - abstract render(container: HTMLElement): void; -} - -export interface IBreadcrumbsWidgetStyles { - readonly breadcrumbsBackground: string | undefined; - readonly breadcrumbsForeground: string | undefined; - readonly breadcrumbsHoverForeground: string | undefined; - readonly breadcrumbsFocusForeground: string | undefined; - readonly breadcrumbsFocusAndSelectionForeground: string | undefined; -} - -export interface IBreadcrumbsItemEvent { - type: 'select' | 'focus'; - item: BreadcrumbsItem; - node: HTMLElement; - payload: any; -} - -export class BreadcrumbsWidget { - - private readonly _disposables = new DisposableStore(); - private readonly _domNode: HTMLDivElement; - private readonly _scrollable: DomScrollableElement; - - private readonly _onDidSelectItem = new Emitter(); - private readonly _onDidFocusItem = new Emitter(); - private readonly _onDidChangeFocus = new Emitter(); - - readonly onDidSelectItem: Event = this._onDidSelectItem.event; - readonly onDidFocusItem: Event = this._onDidFocusItem.event; - readonly onDidChangeFocus: Event = this._onDidChangeFocus.event; - - private readonly _items = new Array(); - private readonly _nodes = new Array(); - private readonly _freeNodes = new Array(); - private readonly _separatorIcon: ThemeIcon; - - private _enabled: boolean = true; - private _focusedItemIdx: number = -1; - private _selectedItemIdx: number = -1; - - private _pendingDimLayout: IDisposable | undefined; - private _pendingLayout: IDisposable | undefined; - private _dimension: dom.Dimension | undefined; - - constructor( - container: HTMLElement, - horizontalScrollbarSize: number, - separatorIcon: ThemeIcon, - styles: IBreadcrumbsWidgetStyles - ) { - this._domNode = document.createElement('div'); - this._domNode.className = 'monaco-breadcrumbs'; - this._domNode.tabIndex = 0; - this._domNode.setAttribute('role', 'list'); - this._scrollable = new DomScrollableElement(this._domNode, { - vertical: ScrollbarVisibility.Hidden, - horizontal: ScrollbarVisibility.Auto, - horizontalScrollbarSize, - useShadows: false, - scrollYToX: true - }); - this._separatorIcon = separatorIcon; - this._disposables.add(this._scrollable); - this._disposables.add(dom.addStandardDisposableListener(this._domNode, 'click', e => this._onClick(e))); - container.appendChild(this._scrollable.getDomNode()); - - const styleElement = dom.createStyleSheet(this._domNode); - this._style(styleElement, styles); - - const focusTracker = dom.trackFocus(this._domNode); - this._disposables.add(focusTracker); - this._disposables.add(focusTracker.onDidBlur(_ => this._onDidChangeFocus.fire(false))); - this._disposables.add(focusTracker.onDidFocus(_ => this._onDidChangeFocus.fire(true))); - } - - setHorizontalScrollbarSize(size: number) { - this._scrollable.updateOptions({ - horizontalScrollbarSize: size - }); - } - - dispose(): void { - this._disposables.dispose(); - this._pendingLayout?.dispose(); - this._pendingDimLayout?.dispose(); - this._onDidSelectItem.dispose(); - this._onDidFocusItem.dispose(); - this._onDidChangeFocus.dispose(); - this._domNode.remove(); - this._nodes.length = 0; - this._freeNodes.length = 0; - } - - layout(dim: dom.Dimension | undefined): void { - if (dim && dom.Dimension.equals(dim, this._dimension)) { - return; - } - if (dim) { - // only measure - this._pendingDimLayout?.dispose(); - this._pendingDimLayout = this._updateDimensions(dim); - } else { - this._pendingLayout?.dispose(); - this._pendingLayout = this._updateScrollbar(); - } - } - - private _updateDimensions(dim: dom.Dimension): IDisposable { - const disposables = new DisposableStore(); - disposables.add(dom.modify(dom.getWindow(this._domNode), () => { - this._dimension = dim; - this._domNode.style.width = `${dim.width}px`; - this._domNode.style.height = `${dim.height}px`; - disposables.add(this._updateScrollbar()); - })); - return disposables; - } - - private _updateScrollbar(): IDisposable { - return dom.measure(dom.getWindow(this._domNode), () => { - dom.measure(dom.getWindow(this._domNode), () => { // double RAF - this._scrollable.setRevealOnScroll(false); - this._scrollable.scanDomNode(); - this._scrollable.setRevealOnScroll(true); - }); - }); - } - - private _style(styleElement: HTMLStyleElement, style: IBreadcrumbsWidgetStyles): void { - let content = ''; - if (style.breadcrumbsBackground) { - content += `.monaco-breadcrumbs { background-color: ${style.breadcrumbsBackground}}`; - } - if (style.breadcrumbsForeground) { - content += `.monaco-breadcrumbs .monaco-breadcrumb-item { color: ${style.breadcrumbsForeground}}\n`; - } - if (style.breadcrumbsFocusForeground) { - content += `.monaco-breadcrumbs .monaco-breadcrumb-item.focused { color: ${style.breadcrumbsFocusForeground}}\n`; - } - if (style.breadcrumbsFocusAndSelectionForeground) { - content += `.monaco-breadcrumbs .monaco-breadcrumb-item.focused.selected { color: ${style.breadcrumbsFocusAndSelectionForeground}}\n`; - } - if (style.breadcrumbsHoverForeground) { - content += `.monaco-breadcrumbs:not(.disabled ) .monaco-breadcrumb-item:hover:not(.focused):not(.selected) { color: ${style.breadcrumbsHoverForeground}}\n`; - } - styleElement.innerText = content; - } - - setEnabled(value: boolean) { - this._enabled = value; - this._domNode.classList.toggle('disabled', !this._enabled); - } - - domFocus(): void { - const idx = this._focusedItemIdx >= 0 ? this._focusedItemIdx : this._items.length - 1; - if (idx >= 0 && idx < this._items.length) { - this._focus(idx, undefined); - } else { - this._domNode.focus(); - } - } - - isDOMFocused(): boolean { - return dom.isAncestorOfActiveElement(this._domNode); - } - - getFocused(): BreadcrumbsItem { - return this._items[this._focusedItemIdx]; - } - - setFocused(item: BreadcrumbsItem | undefined, payload?: any): void { - this._focus(this._items.indexOf(item!), payload); - } - - focusPrev(payload?: any): any { - if (this._focusedItemIdx > 0) { - this._focus(this._focusedItemIdx - 1, payload); - } - } - - focusNext(payload?: any): any { - if (this._focusedItemIdx + 1 < this._nodes.length) { - this._focus(this._focusedItemIdx + 1, payload); - } - } - - private _focus(nth: number, payload: any): void { - this._focusedItemIdx = -1; - for (let i = 0; i < this._nodes.length; i++) { - const node = this._nodes[i]; - if (i !== nth) { - node.classList.remove('focused'); - } else { - this._focusedItemIdx = i; - node.classList.add('focused'); - node.focus(); - } - } - this._reveal(this._focusedItemIdx, true); - this._onDidFocusItem.fire({ type: 'focus', item: this._items[this._focusedItemIdx], node: this._nodes[this._focusedItemIdx], payload }); - } - - reveal(item: BreadcrumbsItem): void { - const idx = this._items.indexOf(item); - if (idx >= 0) { - this._reveal(idx, false); - } - } - - revealLast(): void { - this._reveal(this._items.length - 1, false); - } - - private _reveal(nth: number, minimal: boolean): void { - if (nth < 0 || nth >= this._nodes.length) { - return; - } - const node = this._nodes[nth]; - if (!node) { - return; - } - const { width } = this._scrollable.getScrollDimensions(); - const { scrollLeft } = this._scrollable.getScrollPosition(); - if (!minimal || node.offsetLeft > scrollLeft + width || node.offsetLeft < scrollLeft) { - this._scrollable.setRevealOnScroll(false); - this._scrollable.setScrollPosition({ scrollLeft: node.offsetLeft }); - this._scrollable.setRevealOnScroll(true); - } - } - - getSelection(): BreadcrumbsItem { - return this._items[this._selectedItemIdx]; - } - - setSelection(item: BreadcrumbsItem | undefined, payload?: any): void { - this._select(this._items.indexOf(item!), payload); - } - - private _select(nth: number, payload: any): void { - this._selectedItemIdx = -1; - for (let i = 0; i < this._nodes.length; i++) { - const node = this._nodes[i]; - if (i !== nth) { - node.classList.remove('selected'); - } else { - this._selectedItemIdx = i; - node.classList.add('selected'); - } - } - this._onDidSelectItem.fire({ type: 'select', item: this._items[this._selectedItemIdx], node: this._nodes[this._selectedItemIdx], payload }); - } - - getItems(): readonly BreadcrumbsItem[] { - return this._items; - } - - setItems(items: BreadcrumbsItem[]): void { - let prefix: number | undefined; - let removed: BreadcrumbsItem[] = []; - try { - prefix = commonPrefixLength(this._items, items, (a, b) => a.equals(b)); - removed = this._items.splice(prefix, this._items.length - prefix, ...items.slice(prefix)); - this._render(prefix); - dispose(removed); - this._focus(-1, undefined); - } catch (e) { - const newError = new Error(`BreadcrumbsItem#setItems: newItems: ${items.length}, prefix: ${prefix}, removed: ${removed.length}`); - newError.name = e.name; - newError.stack = e.stack; - throw newError; - } - } - - private _render(start: number): void { - let didChange = false; - for (; start < this._items.length && start < this._nodes.length; start++) { - const item = this._items[start]; - const node = this._nodes[start]; - this._renderItem(item, node); - didChange = true; - } - // case a: more nodes -> remove them - while (start < this._nodes.length) { - const free = this._nodes.pop(); - if (free) { - this._freeNodes.push(free); - free.remove(); - didChange = true; - } - } - - // case b: more items -> render them - for (; start < this._items.length; start++) { - const item = this._items[start]; - const node = this._freeNodes.length > 0 ? this._freeNodes.pop() : document.createElement('div'); - if (node) { - this._renderItem(item, node); - this._domNode.appendChild(node); - this._nodes.push(node); - didChange = true; - } - } - if (didChange) { - this.layout(undefined); - } - } - - private _renderItem(item: BreadcrumbsItem, container: HTMLDivElement): void { - dom.clearNode(container); - container.className = ''; - try { - item.render(container); - } catch (err) { - container.innerText = '<>'; - console.error(err); - } - container.tabIndex = -1; - container.setAttribute('role', 'listitem'); - container.classList.add('monaco-breadcrumb-item'); - const iconContainer = dom.$(ThemeIcon.asCSSSelector(this._separatorIcon)); - container.appendChild(iconContainer); - } - - private _onClick(event: IMouseEvent): void { - if (!this._enabled) { - return; - } - for (let el: HTMLElement | null = event.target; el; el = el.parentElement) { - const idx = this._nodes.indexOf(el as HTMLDivElement); - if (idx >= 0) { - this._focus(idx, event); - this._select(idx, event); - break; - } - } - } -} diff --git a/src/vs/base/browser/ui/centered/centeredViewLayout.ts b/src/vs/base/browser/ui/centered/centeredViewLayout.ts deleted file mode 100644 index b6bc80b1..00000000 --- a/src/vs/base/browser/ui/centered/centeredViewLayout.ts +++ /dev/null @@ -1,225 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { $, IDomNodePagePosition } from 'vs/base/browser/dom'; -import { IView, IViewSize } from 'vs/base/browser/ui/grid/grid'; -import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; -import { DistributeSizing, ISplitViewStyles, IView as ISplitViewView, Orientation, SplitView } from 'vs/base/browser/ui/splitview/splitview'; -import { Color } from 'vs/base/common/color'; -import { Event } from 'vs/base/common/event'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; - -export interface CenteredViewState { - // width of the fixed centered layout - targetWidth: number; - // proportional size of left margin - leftMarginRatio: number; - // proportional size of right margin - rightMarginRatio: number; -} - -const defaultState: CenteredViewState = { - targetWidth: 900, - leftMarginRatio: 0.1909, - rightMarginRatio: 0.1909, -}; - -const distributeSizing: DistributeSizing = { type: 'distribute' }; - -function createEmptyView(background: Color | undefined): ISplitViewView<{ top: number; left: number }> { - const element = $('.centered-layout-margin'); - element.style.height = '100%'; - if (background) { - element.style.backgroundColor = background.toString(); - } - - return { - element, - layout: () => undefined, - minimumSize: 60, - maximumSize: Number.POSITIVE_INFINITY, - onDidChange: Event.None - }; -} - -function toSplitViewView(view: IView, getHeight: () => number): ISplitViewView<{ top: number; left: number }> { - return { - element: view.element, - get maximumSize() { return view.maximumWidth; }, - get minimumSize() { return view.minimumWidth; }, - onDidChange: Event.map(view.onDidChange, e => e && e.width), - layout: (size, offset, ctx) => view.layout(size, getHeight(), ctx?.top ?? 0, (ctx?.left ?? 0) + offset) - }; -} - -export interface ICenteredViewStyles extends ISplitViewStyles { - background: Color; -} - -export class CenteredViewLayout implements IDisposable { - - private splitView?: SplitView<{ top: number; left: number }>; - private lastLayoutPosition: IDomNodePagePosition = { width: 0, height: 0, left: 0, top: 0 }; - private style!: ICenteredViewStyles; - private didLayout = false; - private emptyViews: ISplitViewView<{ top: number; left: number }>[] | undefined; - private readonly splitViewDisposables = new DisposableStore(); - - constructor( - private container: HTMLElement, - private view: IView, - public state: CenteredViewState = { ...defaultState }, - private centeredLayoutFixedWidth: boolean = false - ) { - this.container.appendChild(this.view.element); - // Make sure to hide the split view overflow like sashes #52892 - this.container.style.overflow = 'hidden'; - } - - get minimumWidth(): number { return this.splitView ? this.splitView.minimumSize : this.view.minimumWidth; } - get maximumWidth(): number { return this.splitView ? this.splitView.maximumSize : this.view.maximumWidth; } - get minimumHeight(): number { return this.view.minimumHeight; } - get maximumHeight(): number { return this.view.maximumHeight; } - get onDidChange(): Event { return this.view.onDidChange; } - - private _boundarySashes: IBoundarySashes = {}; - get boundarySashes(): IBoundarySashes { return this._boundarySashes; } - set boundarySashes(boundarySashes: IBoundarySashes) { - this._boundarySashes = boundarySashes; - - if (!this.splitView) { - return; - } - - this.splitView.orthogonalStartSash = boundarySashes.top; - this.splitView.orthogonalEndSash = boundarySashes.bottom; - } - - layout(width: number, height: number, top: number, left: number): void { - this.lastLayoutPosition = { width, height, top, left }; - if (this.splitView) { - this.splitView.layout(width, this.lastLayoutPosition); - if (!this.didLayout || this.centeredLayoutFixedWidth) { - this.resizeSplitViews(); - } - } else { - this.view.layout(width, height, top, left); - } - - this.didLayout = true; - } - - private resizeSplitViews(): void { - if (!this.splitView) { - return; - } - if (this.centeredLayoutFixedWidth) { - const centerViewWidth = Math.min(this.lastLayoutPosition.width, this.state.targetWidth); - const marginWidthFloat = (this.lastLayoutPosition.width - centerViewWidth) / 2; - this.splitView.resizeView(0, Math.floor(marginWidthFloat)); - this.splitView.resizeView(1, centerViewWidth); - this.splitView.resizeView(2, Math.ceil(marginWidthFloat)); - } else { - const leftMargin = this.state.leftMarginRatio * this.lastLayoutPosition.width; - const rightMargin = this.state.rightMarginRatio * this.lastLayoutPosition.width; - const center = this.lastLayoutPosition.width - leftMargin - rightMargin; - this.splitView.resizeView(0, leftMargin); - this.splitView.resizeView(1, center); - this.splitView.resizeView(2, rightMargin); - } - } - - setFixedWidth(option: boolean) { - this.centeredLayoutFixedWidth = option; - if (!!this.splitView) { - this.updateState(); - this.resizeSplitViews(); - } - } - - private updateState() { - if (!!this.splitView) { - this.state.targetWidth = this.splitView.getViewSize(1); - this.state.leftMarginRatio = this.splitView.getViewSize(0) / this.lastLayoutPosition.width; - this.state.rightMarginRatio = this.splitView.getViewSize(2) / this.lastLayoutPosition.width; - } - } - - isActive(): boolean { - return !!this.splitView; - } - - styles(style: ICenteredViewStyles): void { - this.style = style; - if (this.splitView && this.emptyViews) { - this.splitView.style(this.style); - this.emptyViews[0].element.style.backgroundColor = this.style.background.toString(); - this.emptyViews[1].element.style.backgroundColor = this.style.background.toString(); - } - } - - activate(active: boolean): void { - if (active === this.isActive()) { - return; - } - - if (active) { - this.view.element.remove(); - this.splitView = new SplitView(this.container, { - inverseAltBehavior: true, - orientation: Orientation.HORIZONTAL, - styles: this.style - }); - this.splitView.orthogonalStartSash = this.boundarySashes.top; - this.splitView.orthogonalEndSash = this.boundarySashes.bottom; - - this.splitViewDisposables.add(this.splitView.onDidSashChange(() => { - if (!!this.splitView) { - this.updateState(); - } - })); - this.splitViewDisposables.add(this.splitView.onDidSashReset(() => { - this.state = { ...defaultState }; - this.resizeSplitViews(); - })); - - this.splitView.layout(this.lastLayoutPosition.width, this.lastLayoutPosition); - const backgroundColor = this.style ? this.style.background : undefined; - this.emptyViews = [createEmptyView(backgroundColor), createEmptyView(backgroundColor)]; - - this.splitView.addView(this.emptyViews[0], distributeSizing, 0); - this.splitView.addView(toSplitViewView(this.view, () => this.lastLayoutPosition.height), distributeSizing, 1); - this.splitView.addView(this.emptyViews[1], distributeSizing, 2); - - this.resizeSplitViews(); - } else { - this.splitView?.el.remove(); - this.splitViewDisposables.clear(); - this.splitView?.dispose(); - this.splitView = undefined; - this.emptyViews = undefined; - this.container.appendChild(this.view.element); - this.view.layout(this.lastLayoutPosition.width, this.lastLayoutPosition.height, this.lastLayoutPosition.top, this.lastLayoutPosition.left); - } - } - - isDefault(state: CenteredViewState): boolean { - if (this.centeredLayoutFixedWidth) { - return state.targetWidth === defaultState.targetWidth; - } else { - return state.leftMarginRatio === defaultState.leftMarginRatio - && state.rightMarginRatio === defaultState.rightMarginRatio; - } - } - - dispose(): void { - this.splitViewDisposables.dispose(); - - if (this.splitView) { - this.splitView.dispose(); - this.splitView = undefined; - } - } -} diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css b/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css deleted file mode 100644 index 9666216f..00000000 --- a/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css +++ /dev/null @@ -1,33 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.codicon-wrench-subaction { - opacity: 0.5; -} - -@keyframes codicon-spin { - 100% { - transform:rotate(360deg); - } -} - -.codicon-sync.codicon-modifier-spin, -.codicon-loading.codicon-modifier-spin, -.codicon-gear.codicon-modifier-spin, -.codicon-notebook-state-executing.codicon-modifier-spin { - /* Use steps to throttle FPS to reduce CPU usage */ - animation: codicon-spin 1.5s steps(30) infinite; -} - -.codicon-modifier-disabled { - opacity: 0.4; -} - -/* custom speed & easing for loading icon */ -.codicon-loading, -.codicon-tree-item-loading::before { - animation-duration: 1s !important; - animation-timing-function: cubic-bezier(0.53, 0.21, 0.29, 0.67) !important; -} diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.css b/src/vs/base/browser/ui/codicons/codicon/codicon.css deleted file mode 100644 index 02154e77..00000000 --- a/src/vs/base/browser/ui/codicons/codicon/codicon.css +++ /dev/null @@ -1,25 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -@font-face { - font-family: "codicon"; - font-display: block; - src: url("./codicon.ttf?5d4d76ab2ce5108968ad644d591a16a6") format("truetype"); -} - -.codicon[class*='codicon-'] { - font: normal normal normal 16px/1 codicon; - display: inline-block; - text-decoration: none; - text-rendering: auto; - text-align: center; - text-transform: none; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - user-select: none; - -webkit-user-select: none; -} - -/* icon rules are dynamically created by the platform theme service (see iconsStyleSheet.ts) */ diff --git a/src/vs/base/browser/ui/codicons/codiconStyles.ts b/src/vs/base/browser/ui/codicons/codiconStyles.ts deleted file mode 100644 index a88fd3ca..00000000 --- a/src/vs/base/browser/ui/codicons/codiconStyles.ts +++ /dev/null @@ -1,7 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// import 'vs/css!./codicon/codicon'; -// import 'vs/css!./codicon/codicon-modifiers'; diff --git a/src/vs/base/browser/ui/countBadge/countBadge.css b/src/vs/base/browser/ui/countBadge/countBadge.css deleted file mode 100644 index eb0c0837..00000000 --- a/src/vs/base/browser/ui/countBadge/countBadge.css +++ /dev/null @@ -1,24 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-count-badge { - padding: 3px 6px; - border-radius: 11px; - font-size: 11px; - min-width: 18px; - min-height: 18px; - line-height: 11px; - font-weight: normal; - text-align: center; - display: inline-block; - box-sizing: border-box; -} - -.monaco-count-badge.long { - padding: 2px 3px; - border-radius: 2px; - min-height: auto; - line-height: normal; -} diff --git a/src/vs/base/browser/ui/countBadge/countBadge.ts b/src/vs/base/browser/ui/countBadge/countBadge.ts deleted file mode 100644 index fe2f762a..00000000 --- a/src/vs/base/browser/ui/countBadge/countBadge.ts +++ /dev/null @@ -1,69 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { $, append } from 'vs/base/browser/dom'; -import { format } from 'vs/base/common/strings'; -// import 'vs/css!./countBadge'; - -export interface ICountBadgeOptions { - readonly count?: number; - readonly countFormat?: string; - readonly titleFormat?: string; -} - -export interface ICountBadgeStyles { - readonly badgeBackground: string | undefined; - readonly badgeForeground: string | undefined; - readonly badgeBorder: string | undefined; -} - -export const unthemedCountStyles: ICountBadgeStyles = { - badgeBackground: '#4D4D4D', - badgeForeground: '#FFFFFF', - badgeBorder: undefined -}; - -export class CountBadge { - - private element: HTMLElement; - private count: number = 0; - private countFormat: string; - private titleFormat: string; - - constructor(container: HTMLElement, private readonly options: ICountBadgeOptions, private readonly styles: ICountBadgeStyles) { - - this.element = append(container, $('.monaco-count-badge')); - this.countFormat = this.options.countFormat || '{0}'; - this.titleFormat = this.options.titleFormat || ''; - this.setCount(this.options.count || 0); - } - - setCount(count: number) { - this.count = count; - this.render(); - } - - setCountFormat(countFormat: string) { - this.countFormat = countFormat; - this.render(); - } - - setTitleFormat(titleFormat: string) { - this.titleFormat = titleFormat; - this.render(); - } - - private render() { - this.element.textContent = format(this.countFormat, this.count); - this.element.title = format(this.titleFormat, this.count); - - this.element.style.backgroundColor = this.styles.badgeBackground ?? ''; - this.element.style.color = this.styles.badgeForeground ?? ''; - - if (this.styles.badgeBorder) { - this.element.style.border = `1px solid ${this.styles.badgeBorder}`; - } - } -} diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts deleted file mode 100644 index cf7533e5..00000000 --- a/src/vs/base/browser/ui/grid/grid.ts +++ /dev/null @@ -1,947 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IBoundarySashes, Orientation } from 'vs/base/browser/ui/sash/sash'; -import { equals, tail2 as tail } from 'vs/base/common/arrays'; -import { Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -// import 'vs/css!./gridview'; -import { Box, GridView, IGridViewOptions, IGridViewStyles, IView as IGridViewView, IViewSize, orthogonal, Sizing as GridViewSizing, GridLocation } from './gridview'; -import type { SplitView, AutoSizing as SplitViewAutoSizing } from 'vs/base/browser/ui/splitview/splitview'; - -export type { IViewSize }; -export { LayoutPriority, Orientation, orthogonal } from './gridview'; - -export const enum Direction { - Up, - Down, - Left, - Right -} - -function oppositeDirection(direction: Direction): Direction { - switch (direction) { - case Direction.Up: return Direction.Down; - case Direction.Down: return Direction.Up; - case Direction.Left: return Direction.Right; - case Direction.Right: return Direction.Left; - } -} - -/** - * The interface to implement for views within a {@link Grid}. - */ -export interface IView extends IGridViewView { - - /** - * The preferred width for when the user double clicks a sash - * adjacent to this view. - */ - readonly preferredWidth?: number; - - /** - * The preferred height for when the user double clicks a sash - * adjacent to this view. - */ - readonly preferredHeight?: number; -} - -export interface GridLeafNode { - readonly view: T; - readonly box: Box; - readonly cachedVisibleSize: number | undefined; - readonly maximized: boolean; -} - -export interface GridBranchNode { - readonly children: GridNode[]; - readonly box: Box; -} - -export type GridNode = GridLeafNode | GridBranchNode; - -export function isGridBranchNode(node: GridNode): node is GridBranchNode { - return !!(node as any).children; -} - -function getGridNode(node: GridNode, location: GridLocation): GridNode { - if (location.length === 0) { - return node; - } - - if (!isGridBranchNode(node)) { - throw new Error('Invalid location'); - } - - const [index, ...rest] = location; - return getGridNode(node.children[index], rest); -} - -interface Range { - readonly start: number; - readonly end: number; -} - -function intersects(one: Range, other: Range): boolean { - return !(one.start >= other.end || other.start >= one.end); -} - -interface Boundary { - readonly offset: number; - readonly range: Range; -} - -function getBoxBoundary(box: Box, direction: Direction): Boundary { - const orientation = getDirectionOrientation(direction); - const offset = direction === Direction.Up ? box.top : - direction === Direction.Right ? box.left + box.width : - direction === Direction.Down ? box.top + box.height : - box.left; - - const range = { - start: orientation === Orientation.HORIZONTAL ? box.top : box.left, - end: orientation === Orientation.HORIZONTAL ? box.top + box.height : box.left + box.width - }; - - return { offset, range }; -} - -function findAdjacentBoxLeafNodes(boxNode: GridNode, direction: Direction, boundary: Boundary): GridLeafNode[] { - const result: GridLeafNode[] = []; - - function _(boxNode: GridNode, direction: Direction, boundary: Boundary): void { - if (isGridBranchNode(boxNode)) { - for (const child of boxNode.children) { - _(child, direction, boundary); - } - } else { - const { offset, range } = getBoxBoundary(boxNode.box, direction); - - if (offset === boundary.offset && intersects(range, boundary.range)) { - result.push(boxNode); - } - } - } - - _(boxNode, direction, boundary); - return result; -} - -function getLocationOrientation(rootOrientation: Orientation, location: GridLocation): Orientation { - return location.length % 2 === 0 ? orthogonal(rootOrientation) : rootOrientation; -} - -function getDirectionOrientation(direction: Direction): Orientation { - return direction === Direction.Up || direction === Direction.Down ? Orientation.VERTICAL : Orientation.HORIZONTAL; -} - -export function getRelativeLocation(rootOrientation: Orientation, location: GridLocation, direction: Direction): GridLocation { - const orientation = getLocationOrientation(rootOrientation, location); - const directionOrientation = getDirectionOrientation(direction); - - if (orientation === directionOrientation) { - let [rest, index] = tail(location); - - if (direction === Direction.Right || direction === Direction.Down) { - index += 1; - } - - return [...rest, index]; - } else { - const index = (direction === Direction.Right || direction === Direction.Down) ? 1 : 0; - return [...location, index]; - } -} - -function indexInParent(element: HTMLElement): number { - const parentElement = element.parentElement; - - if (!parentElement) { - throw new Error('Invalid grid element'); - } - - let el = parentElement.firstElementChild; - let index = 0; - - while (el !== element && el !== parentElement.lastElementChild && el) { - el = el.nextElementSibling; - index++; - } - - return index; -} - -/** - * Find the grid location of a specific DOM element by traversing the parent - * chain and finding each child index on the way. - * - * This will break as soon as DOM structures of the Splitview or Gridview change. - */ -function getGridLocation(element: HTMLElement): GridLocation { - const parentElement = element.parentElement; - - if (!parentElement) { - throw new Error('Invalid grid element'); - } - - if (/\bmonaco-grid-view\b/.test(parentElement.className)) { - return []; - } - - const index = indexInParent(parentElement); - const ancestor = parentElement.parentElement!.parentElement!.parentElement!.parentElement!; - return [...getGridLocation(ancestor), index]; -} - -export type DistributeSizing = { type: 'distribute' }; -export type SplitSizing = { type: 'split' }; -export type AutoSizing = { type: 'auto' }; -export type InvisibleSizing = { type: 'invisible'; cachedVisibleSize: number }; -export type Sizing = DistributeSizing | SplitSizing | AutoSizing | InvisibleSizing; - -export namespace Sizing { - export const Distribute: DistributeSizing = { type: 'distribute' }; - export const Split: SplitSizing = { type: 'split' }; - export const Auto: AutoSizing = { type: 'auto' }; - export function Invisible(cachedVisibleSize: number): InvisibleSizing { return { type: 'invisible', cachedVisibleSize }; } -} - -export interface IGridStyles extends IGridViewStyles { } -export interface IGridOptions extends IGridViewOptions { } - -/** - * The {@link Grid} exposes a Grid widget in a friendlier API than the underlying - * {@link GridView} widget. Namely, all mutation operations are addressed by the - * model elements, rather than indexes. - * - * It support the same features as the {@link GridView}. - */ -export class Grid extends Disposable { - - protected gridview: GridView; - private views = new Map(); - - /** - * The orientation of the grid. Matches the orientation of the root - * {@link SplitView} in the grid's {@link GridLocation} model. - */ - get orientation(): Orientation { return this.gridview.orientation; } - set orientation(orientation: Orientation) { this.gridview.orientation = orientation; } - - /** - * The width of the grid. - */ - get width(): number { return this.gridview.width; } - - /** - * The height of the grid. - */ - get height(): number { return this.gridview.height; } - - /** - * The minimum width of the grid. - */ - get minimumWidth(): number { return this.gridview.minimumWidth; } - - /** - * The minimum height of the grid. - */ - get minimumHeight(): number { return this.gridview.minimumHeight; } - - /** - * The maximum width of the grid. - */ - get maximumWidth(): number { return this.gridview.maximumWidth; } - - /** - * The maximum height of the grid. - */ - get maximumHeight(): number { return this.gridview.maximumHeight; } - - /** - * Fires whenever a view within the grid changes its size constraints. - */ - readonly onDidChange: Event<{ width: number; height: number } | undefined>; - - /** - * Fires whenever the user scrolls a {@link SplitView} within - * the grid. - */ - readonly onDidScroll: Event; - - /** - * A collection of sashes perpendicular to each edge of the grid. - * Corner sashes will be created for each intersection. - */ - get boundarySashes(): IBoundarySashes { return this.gridview.boundarySashes; } - set boundarySashes(boundarySashes: IBoundarySashes) { this.gridview.boundarySashes = boundarySashes; } - - /** - * Enable/disable edge snapping across all grid views. - */ - set edgeSnapping(edgeSnapping: boolean) { this.gridview.edgeSnapping = edgeSnapping; } - - /** - * The DOM element for this view. - */ - get element(): HTMLElement { return this.gridview.element; } - - private didLayout = false; - - readonly onDidChangeViewMaximized: Event; - /** - * Create a new {@link Grid}. A grid must *always* have a view - * inside. - * - * @param view An initial view for this Grid. - */ - constructor(view: T | GridView, options: IGridOptions = {}) { - super(); - - if (view instanceof GridView) { - this.gridview = view; - this.gridview.getViewMap(this.views); - } else { - this.gridview = new GridView(options); - } - - this._register(this.gridview); - this._register(this.gridview.onDidSashReset(this.onDidSashReset, this)); - - if (!(view instanceof GridView)) { - this._addView(view, 0, [0]); - } - - this.onDidChange = this.gridview.onDidChange; - this.onDidScroll = this.gridview.onDidScroll; - this.onDidChangeViewMaximized = this.gridview.onDidChangeViewMaximized; - } - - style(styles: IGridStyles): void { - this.gridview.style(styles); - } - - /** - * Layout the {@link Grid}. - * - * Optionally provide a `top` and `left` positions, those will propagate - * as an origin for positions passed to {@link IView.layout}. - * - * @param width The width of the {@link Grid}. - * @param height The height of the {@link Grid}. - * @param top Optional, the top location of the {@link Grid}. - * @param left Optional, the left location of the {@link Grid}. - */ - layout(width: number, height: number, top: number = 0, left: number = 0): void { - this.gridview.layout(width, height, top, left); - this.didLayout = true; - } - - /** - * Add a {@link IView view} to this {@link Grid}, based on another reference view. - * - * Take this grid as an example: - * - * ``` - * +-----+---------------+ - * | A | B | - * +-----+---------+-----+ - * | C | | - * +---------------+ D | - * | E | | - * +---------------+-----+ - * ``` - * - * Calling `addView(X, Sizing.Distribute, C, Direction.Right)` will make the following - * changes: - * - * ``` - * +-----+---------------+ - * | A | B | - * +-----+-+-------+-----+ - * | C | X | | - * +-------+-------+ D | - * | E | | - * +---------------+-----+ - * ``` - * - * Or `addView(X, Sizing.Distribute, D, Direction.Down)`: - * - * ``` - * +-----+---------------+ - * | A | B | - * +-----+---------+-----+ - * | C | D | - * +---------------+-----+ - * | E | X | - * +---------------+-----+ - * ``` - * - * @param newView The view to add. - * @param size Either a fixed size, or a dynamic {@link Sizing} strategy. - * @param referenceView Another view to place this new view next to. - * @param direction The direction the new view should be placed next to the reference view. - */ - addView(newView: T, size: number | Sizing, referenceView: T, direction: Direction): void { - if (this.views.has(newView)) { - throw new Error('Can\'t add same view twice'); - } - - const orientation = getDirectionOrientation(direction); - - if (this.views.size === 1 && this.orientation !== orientation) { - this.orientation = orientation; - } - - const referenceLocation = this.getViewLocation(referenceView); - const location = getRelativeLocation(this.gridview.orientation, referenceLocation, direction); - - let viewSize: number | GridViewSizing; - - if (typeof size === 'number') { - viewSize = size; - } else if (size.type === 'split') { - const [, index] = tail(referenceLocation); - viewSize = GridViewSizing.Split(index); - } else if (size.type === 'distribute') { - viewSize = GridViewSizing.Distribute; - } else if (size.type === 'auto') { - const [, index] = tail(referenceLocation); - viewSize = GridViewSizing.Auto(index); - } else { - viewSize = size; - } - - this._addView(newView, viewSize, location); - } - - private addViewAt(newView: T, size: number | DistributeSizing | InvisibleSizing, location: GridLocation): void { - if (this.views.has(newView)) { - throw new Error('Can\'t add same view twice'); - } - - let viewSize: number | GridViewSizing; - - if (typeof size === 'number') { - viewSize = size; - } else if (size.type === 'distribute') { - viewSize = GridViewSizing.Distribute; - } else { - viewSize = size; - } - - this._addView(newView, viewSize, location); - } - - protected _addView(newView: T, size: number | GridViewSizing, location: GridLocation): void { - this.views.set(newView, newView.element); - this.gridview.addView(newView, size, location); - } - - /** - * Remove a {@link IView view} from this {@link Grid}. - * - * @param view The {@link IView view} to remove. - * @param sizing Whether to distribute other {@link IView view}'s sizes. - */ - removeView(view: T, sizing?: Sizing): void { - if (this.views.size === 1) { - throw new Error('Can\'t remove last view'); - } - - const location = this.getViewLocation(view); - - let gridViewSizing: DistributeSizing | SplitViewAutoSizing | undefined; - - if (sizing?.type === 'distribute') { - gridViewSizing = GridViewSizing.Distribute; - } else if (sizing?.type === 'auto') { - const index = location[location.length - 1]; - gridViewSizing = GridViewSizing.Auto(index === 0 ? 1 : index - 1); - } - - this.gridview.removeView(location, gridViewSizing); - this.views.delete(view); - } - - /** - * Move a {@link IView view} to another location in the grid. - * - * @remarks See {@link Grid.addView}. - * - * @param view The {@link IView view} to move. - * @param sizing Either a fixed size, or a dynamic {@link Sizing} strategy. - * @param referenceView Another view to place the view next to. - * @param direction The direction the view should be placed next to the reference view. - */ - moveView(view: T, sizing: number | Sizing, referenceView: T, direction: Direction): void { - const sourceLocation = this.getViewLocation(view); - const [sourceParentLocation, from] = tail(sourceLocation); - - const referenceLocation = this.getViewLocation(referenceView); - const targetLocation = getRelativeLocation(this.gridview.orientation, referenceLocation, direction); - const [targetParentLocation, to] = tail(targetLocation); - - if (equals(sourceParentLocation, targetParentLocation)) { - this.gridview.moveView(sourceParentLocation, from, to); - } else { - this.removeView(view, typeof sizing === 'number' ? undefined : sizing); - this.addView(view, sizing, referenceView, direction); - } - } - - /** - * Move a {@link IView view} to another location in the grid. - * - * @remarks Internal method, do not use without knowing what you're doing. - * @remarks See {@link GridView.moveView}. - * - * @param view The {@link IView view} to move. - * @param location The {@link GridLocation location} to insert the view on. - */ - moveViewTo(view: T, location: GridLocation): void { - const sourceLocation = this.getViewLocation(view); - const [sourceParentLocation, from] = tail(sourceLocation); - const [targetParentLocation, to] = tail(location); - - if (equals(sourceParentLocation, targetParentLocation)) { - this.gridview.moveView(sourceParentLocation, from, to); - } else { - const size = this.getViewSize(view); - const orientation = getLocationOrientation(this.gridview.orientation, sourceLocation); - const cachedViewSize = this.getViewCachedVisibleSize(view); - const sizing = typeof cachedViewSize === 'undefined' - ? (orientation === Orientation.HORIZONTAL ? size.width : size.height) - : Sizing.Invisible(cachedViewSize); - - this.removeView(view); - this.addViewAt(view, sizing, location); - } - } - - /** - * Swap two {@link IView views} within the {@link Grid}. - * - * @param from One {@link IView view}. - * @param to Another {@link IView view}. - */ - swapViews(from: T, to: T): void { - const fromLocation = this.getViewLocation(from); - const toLocation = this.getViewLocation(to); - return this.gridview.swapViews(fromLocation, toLocation); - } - - /** - * Resize a {@link IView view}. - * - * @param view The {@link IView view} to resize. - * @param size The size the view should be. - */ - resizeView(view: T, size: IViewSize): void { - const location = this.getViewLocation(view); - return this.gridview.resizeView(location, size); - } - - /** - * Returns whether all other {@link IView views} are at their minimum size. - * - * @param view The reference {@link IView view}. - */ - isViewExpanded(view: T): boolean { - const location = this.getViewLocation(view); - return this.gridview.isViewExpanded(location); - } - - /** - * Returns whether the {@link IView view} is maximized. - * - * @param view The reference {@link IView view}. - */ - isViewMaximized(view: T): boolean { - const location = this.getViewLocation(view); - return this.gridview.isViewMaximized(location); - } - - /** - * Returns whether the {@link IView view} is maximized. - * - * @param view The reference {@link IView view}. - */ - hasMaximizedView(): boolean { - return this.gridview.hasMaximizedView(); - } - - /** - * Get the size of a {@link IView view}. - * - * @param view The {@link IView view}. Provide `undefined` to get the size - * of the grid itself. - */ - getViewSize(view?: T): IViewSize { - if (!view) { - return this.gridview.getViewSize(); - } - - const location = this.getViewLocation(view); - return this.gridview.getViewSize(location); - } - - /** - * Get the cached visible size of a {@link IView view}. This was the size - * of the view at the moment it last became hidden. - * - * @param view The {@link IView view}. - */ - getViewCachedVisibleSize(view: T): number | undefined { - const location = this.getViewLocation(view); - return this.gridview.getViewCachedVisibleSize(location); - } - - /** - * Maximizes the specified view and hides all other views. - * @param view The view to maximize. - */ - maximizeView(view: T) { - if (this.views.size < 2) { - throw new Error('At least two views are required to maximize a view'); - } - const location = this.getViewLocation(view); - this.gridview.maximizeView(location); - } - - exitMaximizedView(): void { - this.gridview.exitMaximizedView(); - } - - /** - * Expand the size of a {@link IView view} by collapsing all other views - * to their minimum sizes. - * - * @param view The {@link IView view}. - */ - expandView(view: T): void { - const location = this.getViewLocation(view); - this.gridview.expandView(location); - } - - /** - * Distribute the size among all {@link IView views} within the entire - * grid or within a single {@link SplitView}. - */ - distributeViewSizes(): void { - this.gridview.distributeViewSizes(); - } - - /** - * Returns whether a {@link IView view} is visible. - * - * @param view The {@link IView view}. - */ - isViewVisible(view: T): boolean { - const location = this.getViewLocation(view); - return this.gridview.isViewVisible(location); - } - - /** - * Set the visibility state of a {@link IView view}. - * - * @param view The {@link IView view}. - */ - setViewVisible(view: T, visible: boolean): void { - const location = this.getViewLocation(view); - this.gridview.setViewVisible(location, visible); - } - - /** - * Returns a descriptor for the entire grid. - */ - getViews(): GridBranchNode { - return this.gridview.getView() as GridBranchNode; - } - - /** - * Utility method to return the collection all views which intersect - * a view's edge. - * - * @param view The {@link IView view}. - * @param direction Which direction edge to be considered. - * @param wrap Whether the grid wraps around (from right to left, from bottom to top). - */ - getNeighborViews(view: T, direction: Direction, wrap: boolean = false): T[] { - if (!this.didLayout) { - throw new Error('Can\'t call getNeighborViews before first layout'); - } - - const location = this.getViewLocation(view); - const root = this.getViews(); - const node = getGridNode(root, location); - let boundary = getBoxBoundary(node.box, direction); - - if (wrap) { - if (direction === Direction.Up && node.box.top === 0) { - boundary = { offset: root.box.top + root.box.height, range: boundary.range }; - } else if (direction === Direction.Right && node.box.left + node.box.width === root.box.width) { - boundary = { offset: 0, range: boundary.range }; - } else if (direction === Direction.Down && node.box.top + node.box.height === root.box.height) { - boundary = { offset: 0, range: boundary.range }; - } else if (direction === Direction.Left && node.box.left === 0) { - boundary = { offset: root.box.left + root.box.width, range: boundary.range }; - } - } - - return findAdjacentBoxLeafNodes(root, oppositeDirection(direction), boundary) - .map(node => node.view); - } - - private getViewLocation(view: T): GridLocation { - const element = this.views.get(view); - - if (!element) { - throw new Error('View not found'); - } - - return getGridLocation(element); - } - - private onDidSashReset(location: GridLocation): void { - const resizeToPreferredSize = (location: GridLocation): boolean => { - const node = this.gridview.getView(location) as GridNode; - - if (isGridBranchNode(node)) { - return false; - } - - const direction = getLocationOrientation(this.orientation, location); - const size = direction === Orientation.HORIZONTAL ? node.view.preferredWidth : node.view.preferredHeight; - - if (typeof size !== 'number') { - return false; - } - - const viewSize = direction === Orientation.HORIZONTAL ? { width: Math.round(size) } : { height: Math.round(size) }; - this.gridview.resizeView(location, viewSize); - return true; - }; - - if (resizeToPreferredSize(location)) { - return; - } - - const [parentLocation, index] = tail(location); - - if (resizeToPreferredSize([...parentLocation, index + 1])) { - return; - } - - this.gridview.distributeViewSizes(parentLocation); - } -} - -export interface ISerializableView extends IView { - toJSON(): object; -} - -export interface IViewDeserializer { - fromJSON(json: any): T; -} - -export interface ISerializedLeafNode { - type: 'leaf'; - data: any; - size: number; - visible?: boolean; - maximized?: boolean; -} - -export interface ISerializedBranchNode { - type: 'branch'; - data: ISerializedNode[]; - size: number; - visible?: boolean; -} - -export type ISerializedNode = ISerializedLeafNode | ISerializedBranchNode; - -export interface ISerializedGrid { - root: ISerializedNode; - orientation: Orientation; - width: number; - height: number; -} - -/** - * A {@link Grid} which can serialize itself. - */ -export class SerializableGrid extends Grid { - - private static serializeNode(node: GridNode, orientation: Orientation): ISerializedNode { - const size = orientation === Orientation.VERTICAL ? node.box.width : node.box.height; - - if (!isGridBranchNode(node)) { - const serializedLeafNode: ISerializedLeafNode = { type: 'leaf', data: node.view.toJSON(), size }; - - if (typeof node.cachedVisibleSize === 'number') { - serializedLeafNode.size = node.cachedVisibleSize; - serializedLeafNode.visible = false; - } else if (node.maximized) { - serializedLeafNode.maximized = true; - } - - return serializedLeafNode; - } - - const data = node.children.map(c => SerializableGrid.serializeNode(c, orthogonal(orientation))); - if (data.some(c => c.visible !== false)) { - return { type: 'branch', data: data, size }; - } - return { type: 'branch', data: data, size, visible: false }; - } - - /** - * Construct a new {@link SerializableGrid} from a JSON object. - * - * @param json The JSON object. - * @param deserializer A deserializer which can revive each view. - * @returns A new {@link SerializableGrid} instance. - */ - static deserialize(json: ISerializedGrid, deserializer: IViewDeserializer, options: IGridOptions = {}): SerializableGrid { - if (typeof json.orientation !== 'number') { - throw new Error('Invalid JSON: \'orientation\' property must be a number.'); - } else if (typeof json.width !== 'number') { - throw new Error('Invalid JSON: \'width\' property must be a number.'); - } else if (typeof json.height !== 'number') { - throw new Error('Invalid JSON: \'height\' property must be a number.'); - } - - const gridview = GridView.deserialize(json, deserializer, options); - const result = new SerializableGrid(gridview, options); - - return result; - } - - /** - * Construct a new {@link SerializableGrid} from a grid descriptor. - * - * @param gridDescriptor A grid descriptor in which leaf nodes point to actual views. - * @returns A new {@link SerializableGrid} instance. - */ - static from(gridDescriptor: GridDescriptor, options: IGridOptions = {}): SerializableGrid { - return SerializableGrid.deserialize(createSerializedGrid(gridDescriptor), { fromJSON: view => view }, options); - } - - /** - * Useful information in order to proportionally restore view sizes - * upon the very first layout call. - */ - private initialLayoutContext: boolean = true; - - /** - * Serialize this grid into a JSON object. - */ - serialize(): ISerializedGrid { - return { - root: SerializableGrid.serializeNode(this.getViews(), this.orientation), - orientation: this.orientation, - width: this.width, - height: this.height - }; - } - - override layout(width: number, height: number, top: number = 0, left: number = 0): void { - super.layout(width, height, top, left); - - if (this.initialLayoutContext) { - this.initialLayoutContext = false; - this.gridview.trySet2x2(); - } - } -} - -export type GridLeafNodeDescriptor = { size?: number; data?: any }; -export type GridBranchNodeDescriptor = { size?: number; groups: GridNodeDescriptor[] }; -export type GridNodeDescriptor = GridBranchNodeDescriptor | GridLeafNodeDescriptor; -export type GridDescriptor = { orientation: Orientation } & GridBranchNodeDescriptor; - -function isGridBranchNodeDescriptor(nodeDescriptor: GridNodeDescriptor): nodeDescriptor is GridBranchNodeDescriptor { - return !!(nodeDescriptor as GridBranchNodeDescriptor).groups; -} - -export function sanitizeGridNodeDescriptor(nodeDescriptor: GridNodeDescriptor, rootNode: boolean): void { - if (!rootNode && (nodeDescriptor as any).groups && (nodeDescriptor as any).groups.length <= 1) { - (nodeDescriptor as any).groups = undefined; - } - - if (!isGridBranchNodeDescriptor(nodeDescriptor)) { - return; - } - - let totalDefinedSize = 0; - let totalDefinedSizeCount = 0; - - for (const child of nodeDescriptor.groups) { - sanitizeGridNodeDescriptor(child, false); - - if (child.size) { - totalDefinedSize += child.size; - totalDefinedSizeCount++; - } - } - - const totalUndefinedSize = totalDefinedSizeCount > 0 ? totalDefinedSize : 1; - const totalUndefinedSizeCount = nodeDescriptor.groups.length - totalDefinedSizeCount; - const eachUndefinedSize = totalUndefinedSize / totalUndefinedSizeCount; - - for (const child of nodeDescriptor.groups) { - if (!child.size) { - child.size = eachUndefinedSize; - } - } -} - -function createSerializedNode(nodeDescriptor: GridNodeDescriptor): ISerializedNode { - if (isGridBranchNodeDescriptor(nodeDescriptor)) { - return { type: 'branch', data: nodeDescriptor.groups.map(c => createSerializedNode(c)), size: nodeDescriptor.size! }; - } else { - return { type: 'leaf', data: nodeDescriptor.data, size: nodeDescriptor.size! }; - } -} - -function getDimensions(node: ISerializedNode, orientation: Orientation): { width?: number; height?: number } { - if (node.type === 'branch') { - const childrenDimensions = node.data.map(c => getDimensions(c, orthogonal(orientation))); - - if (orientation === Orientation.VERTICAL) { - const width = node.size || (childrenDimensions.length === 0 ? undefined : Math.max(...childrenDimensions.map(d => d.width || 0))); - const height = childrenDimensions.length === 0 ? undefined : childrenDimensions.reduce((r, d) => r + (d.height || 0), 0); - return { width, height }; - } else { - const width = childrenDimensions.length === 0 ? undefined : childrenDimensions.reduce((r, d) => r + (d.width || 0), 0); - const height = node.size || (childrenDimensions.length === 0 ? undefined : Math.max(...childrenDimensions.map(d => d.height || 0))); - return { width, height }; - } - } else { - const width = orientation === Orientation.VERTICAL ? node.size : undefined; - const height = orientation === Orientation.VERTICAL ? undefined : node.size; - return { width, height }; - } -} - -/** - * Creates a new JSON object from a {@link GridDescriptor}, which can - * be deserialized by {@link SerializableGrid.deserialize}. - */ -export function createSerializedGrid(gridDescriptor: GridDescriptor): ISerializedGrid { - sanitizeGridNodeDescriptor(gridDescriptor, true); - - const root = createSerializedNode(gridDescriptor); - const { width, height } = getDimensions(root, gridDescriptor.orientation); - - return { - root, - orientation: gridDescriptor.orientation, - width: width || 1, - height: height || 1 - }; -} diff --git a/src/vs/base/browser/ui/grid/gridview.css b/src/vs/base/browser/ui/grid/gridview.css deleted file mode 100644 index d38154de..00000000 --- a/src/vs/base/browser/ui/grid/gridview.css +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-grid-view { - position: relative; - overflow: hidden; - width: 100%; - height: 100%; -} - -.monaco-grid-branch-node { - width: 100%; - height: 100%; -} diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts deleted file mode 100644 index 0d2179a6..00000000 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ /dev/null @@ -1,1836 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { $ } from 'vs/base/browser/dom'; -import { IBoundarySashes, Orientation, Sash } from 'vs/base/browser/ui/sash/sash'; -import { DistributeSizing, ISplitViewStyles, IView as ISplitView, LayoutPriority, Sizing, AutoSizing, SplitView } from 'vs/base/browser/ui/splitview/splitview'; -import { equals as arrayEquals, tail2 as tail } from 'vs/base/common/arrays'; -import { Color } from 'vs/base/common/color'; -import { Emitter, Event, Relay } from 'vs/base/common/event'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { rot } from 'vs/base/common/numbers'; -import { isUndefined } from 'vs/base/common/types'; -// import 'vs/css!./gridview'; - -export { Orientation } from 'vs/base/browser/ui/sash/sash'; -export { LayoutPriority, Sizing } from 'vs/base/browser/ui/splitview/splitview'; - -export interface IGridViewStyles extends ISplitViewStyles { } - -const defaultStyles: IGridViewStyles = { - separatorBorder: Color.transparent -}; - -export interface IViewSize { - readonly width: number; - readonly height: number; -} - -interface IRelativeBoundarySashes { - readonly start?: Sash; - readonly end?: Sash; - readonly orthogonalStart?: Sash; - readonly orthogonalEnd?: Sash; -} - -/** - * The interface to implement for views within a {@link GridView}. - */ -export interface IView { - - /** - * The DOM element for this view. - */ - readonly element: HTMLElement; - - /** - * A minimum width for this view. - * - * @remarks If none, set it to `0`. - */ - readonly minimumWidth: number; - - /** - * A minimum width for this view. - * - * @remarks If none, set it to `Number.POSITIVE_INFINITY`. - */ - readonly maximumWidth: number; - - /** - * A minimum height for this view. - * - * @remarks If none, set it to `0`. - */ - readonly minimumHeight: number; - - /** - * A minimum height for this view. - * - * @remarks If none, set it to `Number.POSITIVE_INFINITY`. - */ - readonly maximumHeight: number; - - /** - * The priority of the view when the {@link GridView} layout algorithm - * runs. Views with higher priority will be resized first. - * - * @remarks Only used when `proportionalLayout` is false. - */ - readonly priority?: LayoutPriority; - - /** - * If the {@link GridView} supports proportional layout, - * this property allows for finer control over the proportional layout algorithm, per view. - * - * @defaultValue `true` - */ - readonly proportionalLayout?: boolean; - - /** - * Whether the view will snap whenever the user reaches its minimum size or - * attempts to grow it beyond the minimum size. - * - * @defaultValue `false` - */ - readonly snap?: boolean; - - /** - * View instances are supposed to fire this event whenever any of the constraint - * properties have changed: - * - * - {@link IView.minimumWidth} - * - {@link IView.maximumWidth} - * - {@link IView.minimumHeight} - * - {@link IView.maximumHeight} - * - {@link IView.priority} - * - {@link IView.snap} - * - * The {@link GridView} will relayout whenever that happens. The event can - * optionally emit the view's preferred size for that relayout. - */ - readonly onDidChange: Event; - - /** - * This will be called by the {@link GridView} during layout. A view meant to - * pass along the layout information down to its descendants. - */ - layout(width: number, height: number, top: number, left: number): void; - - /** - * This will be called by the {@link GridView} whenever this view is made - * visible or hidden. - * - * @param visible Whether the view becomes visible. - */ - setVisible?(visible: boolean): void; - - /** - * This will be called by the {@link GridView} whenever this view is on - * an edge of the grid and the grid's - * {@link GridView.boundarySashes boundary sashes} change. - */ - setBoundarySashes?(sashes: IBoundarySashes): void; -} - -export interface ISerializableView extends IView { - toJSON(): object; -} - -export interface IViewDeserializer { - fromJSON(json: any): T; -} - -export interface ISerializedLeafNode { - type: 'leaf'; - data: any; - size: number; - visible?: boolean; - maximized?: boolean; -} - -export interface ISerializedBranchNode { - type: 'branch'; - data: ISerializedNode[]; - size: number; - visible?: boolean; -} - -export type ISerializedNode = ISerializedLeafNode | ISerializedBranchNode; - -export interface ISerializedGridView { - root: ISerializedNode; - orientation: Orientation; - width: number; - height: number; -} - -export function orthogonal(orientation: Orientation): Orientation { - return orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL; -} - -export interface Box { - readonly top: number; - readonly left: number; - readonly width: number; - readonly height: number; -} - -export interface GridLeafNode { - readonly view: IView; - readonly box: Box; - readonly cachedVisibleSize: number | undefined; - readonly maximized: boolean; -} - -export interface GridBranchNode { - readonly children: GridNode[]; - readonly box: Box; -} - -export type GridNode = GridLeafNode | GridBranchNode; - -export function isGridBranchNode(node: GridNode): node is GridBranchNode { - return !!(node as any).children; -} - -class LayoutController { - constructor(public isLayoutEnabled: boolean) { } -} - -export interface IGridViewOptions { - - /** - * Styles overriding the {@link defaultStyles default ones}. - */ - readonly styles?: IGridViewStyles; - - /** - * Resize each view proportionally when resizing the {@link GridView}. - * - * @defaultValue `true` - */ - readonly proportionalLayout?: boolean; // default true -} - -interface ILayoutContext { - readonly orthogonalSize: number; - readonly absoluteOffset: number; - readonly absoluteOrthogonalOffset: number; - readonly absoluteSize: number; - readonly absoluteOrthogonalSize: number; -} - -function toAbsoluteBoundarySashes(sashes: IRelativeBoundarySashes, orientation: Orientation): IBoundarySashes { - if (orientation === Orientation.HORIZONTAL) { - return { left: sashes.start, right: sashes.end, top: sashes.orthogonalStart, bottom: sashes.orthogonalEnd }; - } else { - return { top: sashes.start, bottom: sashes.end, left: sashes.orthogonalStart, right: sashes.orthogonalEnd }; - } -} - -function fromAbsoluteBoundarySashes(sashes: IBoundarySashes, orientation: Orientation): IRelativeBoundarySashes { - if (orientation === Orientation.HORIZONTAL) { - return { start: sashes.left, end: sashes.right, orthogonalStart: sashes.top, orthogonalEnd: sashes.bottom }; - } else { - return { start: sashes.top, end: sashes.bottom, orthogonalStart: sashes.left, orthogonalEnd: sashes.right }; - } -} - -function validateIndex(index: number, numChildren: number): number { - if (Math.abs(index) > numChildren) { - throw new Error('Invalid index'); - } - - return rot(index, numChildren + 1); -} - -class BranchNode implements ISplitView, IDisposable { - - readonly element: HTMLElement; - readonly children: Node[] = []; - private splitview: SplitView; - - private _size: number; - get size(): number { return this._size; } - - private _orthogonalSize: number; - get orthogonalSize(): number { return this._orthogonalSize; } - - private _absoluteOffset: number = 0; - get absoluteOffset(): number { return this._absoluteOffset; } - - private _absoluteOrthogonalOffset: number = 0; - get absoluteOrthogonalOffset(): number { return this._absoluteOrthogonalOffset; } - - private absoluteOrthogonalSize: number = 0; - - private _styles: IGridViewStyles; - get styles(): IGridViewStyles { return this._styles; } - - get width(): number { - return this.orientation === Orientation.HORIZONTAL ? this.size : this.orthogonalSize; - } - - get height(): number { - return this.orientation === Orientation.HORIZONTAL ? this.orthogonalSize : this.size; - } - - get top(): number { - return this.orientation === Orientation.HORIZONTAL ? this._absoluteOffset : this._absoluteOrthogonalOffset; - } - - get left(): number { - return this.orientation === Orientation.HORIZONTAL ? this._absoluteOrthogonalOffset : this._absoluteOffset; - } - - get minimumSize(): number { - return this.children.length === 0 ? 0 : Math.max(...this.children.map((c, index) => this.splitview.isViewVisible(index) ? c.minimumOrthogonalSize : 0)); - } - - get maximumSize(): number { - return Math.min(...this.children.map((c, index) => this.splitview.isViewVisible(index) ? c.maximumOrthogonalSize : Number.POSITIVE_INFINITY)); - } - - get priority(): LayoutPriority { - if (this.children.length === 0) { - return LayoutPriority.Normal; - } - - const priorities = this.children.map(c => typeof c.priority === 'undefined' ? LayoutPriority.Normal : c.priority); - - if (priorities.some(p => p === LayoutPriority.High)) { - return LayoutPriority.High; - } else if (priorities.some(p => p === LayoutPriority.Low)) { - return LayoutPriority.Low; - } - - return LayoutPriority.Normal; - } - - get proportionalLayout(): boolean { - if (this.children.length === 0) { - return true; - } - - return this.children.every(c => c.proportionalLayout); - } - - get minimumOrthogonalSize(): number { - return this.splitview.minimumSize; - } - - get maximumOrthogonalSize(): number { - return this.splitview.maximumSize; - } - - get minimumWidth(): number { - return this.orientation === Orientation.HORIZONTAL ? this.minimumOrthogonalSize : this.minimumSize; - } - - get minimumHeight(): number { - return this.orientation === Orientation.HORIZONTAL ? this.minimumSize : this.minimumOrthogonalSize; - } - - get maximumWidth(): number { - return this.orientation === Orientation.HORIZONTAL ? this.maximumOrthogonalSize : this.maximumSize; - } - - get maximumHeight(): number { - return this.orientation === Orientation.HORIZONTAL ? this.maximumSize : this.maximumOrthogonalSize; - } - - private readonly _onDidChange = new Emitter(); - readonly onDidChange: Event = this._onDidChange.event; - - private readonly _onDidVisibilityChange = new Emitter(); - readonly onDidVisibilityChange: Event = this._onDidVisibilityChange.event; - private readonly childrenVisibilityChangeDisposable: DisposableStore = new DisposableStore(); - - private _onDidScroll = new Emitter(); - private onDidScrollDisposable: IDisposable = Disposable.None; - readonly onDidScroll: Event = this._onDidScroll.event; - - private childrenChangeDisposable: IDisposable = Disposable.None; - - private readonly _onDidSashReset = new Emitter(); - readonly onDidSashReset: Event = this._onDidSashReset.event; - private splitviewSashResetDisposable: IDisposable = Disposable.None; - private childrenSashResetDisposable: IDisposable = Disposable.None; - - private _boundarySashes: IRelativeBoundarySashes = {}; - get boundarySashes(): IRelativeBoundarySashes { return this._boundarySashes; } - set boundarySashes(boundarySashes: IRelativeBoundarySashes) { - if (this._boundarySashes.start === boundarySashes.start - && this._boundarySashes.end === boundarySashes.end - && this._boundarySashes.orthogonalStart === boundarySashes.orthogonalStart - && this._boundarySashes.orthogonalEnd === boundarySashes.orthogonalEnd) { - return; - } - - this._boundarySashes = boundarySashes; - - this.splitview.orthogonalStartSash = boundarySashes.orthogonalStart; - this.splitview.orthogonalEndSash = boundarySashes.orthogonalEnd; - - for (let index = 0; index < this.children.length; index++) { - const child = this.children[index]; - const first = index === 0; - const last = index === this.children.length - 1; - - child.boundarySashes = { - start: boundarySashes.orthogonalStart, - end: boundarySashes.orthogonalEnd, - orthogonalStart: first ? boundarySashes.start : child.boundarySashes.orthogonalStart, - orthogonalEnd: last ? boundarySashes.end : child.boundarySashes.orthogonalEnd, - }; - } - } - - private _edgeSnapping = false; - get edgeSnapping(): boolean { return this._edgeSnapping; } - set edgeSnapping(edgeSnapping: boolean) { - if (this._edgeSnapping === edgeSnapping) { - return; - } - - this._edgeSnapping = edgeSnapping; - - for (const child of this.children) { - if (child instanceof BranchNode) { - child.edgeSnapping = edgeSnapping; - } - } - - this.updateSplitviewEdgeSnappingEnablement(); - } - - constructor( - readonly orientation: Orientation, - readonly layoutController: LayoutController, - styles: IGridViewStyles, - readonly splitviewProportionalLayout: boolean, - size: number = 0, - orthogonalSize: number = 0, - edgeSnapping: boolean = false, - childDescriptors?: INodeDescriptor[] - ) { - this._styles = styles; - this._size = size; - this._orthogonalSize = orthogonalSize; - - this.element = $('.monaco-grid-branch-node'); - - if (!childDescriptors) { - // Normal behavior, we have no children yet, just set up the splitview - this.splitview = new SplitView(this.element, { orientation, styles, proportionalLayout: splitviewProportionalLayout }); - this.splitview.layout(size, { orthogonalSize, absoluteOffset: 0, absoluteOrthogonalOffset: 0, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize }); - } else { - // Reconstruction behavior, we want to reconstruct a splitview - const descriptor = { - views: childDescriptors.map(childDescriptor => { - return { - view: childDescriptor.node, - size: childDescriptor.node.size, - visible: childDescriptor.visible !== false - }; - }), - size: this.orthogonalSize - }; - - const options = { proportionalLayout: splitviewProportionalLayout, orientation, styles }; - - this.children = childDescriptors.map(c => c.node); - this.splitview = new SplitView(this.element, { ...options, descriptor }); - - this.children.forEach((node, index) => { - const first = index === 0; - const last = index === this.children.length; - - node.boundarySashes = { - start: this.boundarySashes.orthogonalStart, - end: this.boundarySashes.orthogonalEnd, - orthogonalStart: first ? this.boundarySashes.start : this.splitview.sashes[index - 1], - orthogonalEnd: last ? this.boundarySashes.end : this.splitview.sashes[index], - }; - }); - } - - const onDidSashReset = Event.map(this.splitview.onDidSashReset, i => [i]); - this.splitviewSashResetDisposable = onDidSashReset(this._onDidSashReset.fire, this._onDidSashReset); - - this.updateChildrenEvents(); - } - - style(styles: IGridViewStyles): void { - this._styles = styles; - this.splitview.style(styles); - - for (const child of this.children) { - if (child instanceof BranchNode) { - child.style(styles); - } - } - } - - layout(size: number, offset: number, ctx: ILayoutContext | undefined): void { - if (!this.layoutController.isLayoutEnabled) { - return; - } - - if (typeof ctx === 'undefined') { - throw new Error('Invalid state'); - } - - // branch nodes should flip the normal/orthogonal directions - this._size = ctx.orthogonalSize; - this._orthogonalSize = size; - this._absoluteOffset = ctx.absoluteOffset + offset; - this._absoluteOrthogonalOffset = ctx.absoluteOrthogonalOffset; - this.absoluteOrthogonalSize = ctx.absoluteOrthogonalSize; - - this.splitview.layout(ctx.orthogonalSize, { - orthogonalSize: size, - absoluteOffset: this._absoluteOrthogonalOffset, - absoluteOrthogonalOffset: this._absoluteOffset, - absoluteSize: ctx.absoluteOrthogonalSize, - absoluteOrthogonalSize: ctx.absoluteSize - }); - - this.updateSplitviewEdgeSnappingEnablement(); - } - - setVisible(visible: boolean): void { - for (const child of this.children) { - child.setVisible(visible); - } - } - - addChild(node: Node, size: number | Sizing, index: number, skipLayout?: boolean): void { - index = validateIndex(index, this.children.length); - - this.splitview.addView(node, size, index, skipLayout); - this.children.splice(index, 0, node); - - this.updateBoundarySashes(); - this.onDidChildrenChange(); - } - - removeChild(index: number, sizing?: Sizing): Node { - index = validateIndex(index, this.children.length); - - const result = this.splitview.removeView(index, sizing); - this.children.splice(index, 1); - - this.updateBoundarySashes(); - this.onDidChildrenChange(); - - return result; - } - - removeAllChildren(): Node[] { - const result = this.splitview.removeAllViews(); - - this.children.splice(0, this.children.length); - - this.updateBoundarySashes(); - this.onDidChildrenChange(); - - return result; - } - - moveChild(from: number, to: number): void { - from = validateIndex(from, this.children.length); - to = validateIndex(to, this.children.length); - - if (from === to) { - return; - } - - if (from < to) { - to -= 1; - } - - this.splitview.moveView(from, to); - this.children.splice(to, 0, this.children.splice(from, 1)[0]); - - this.updateBoundarySashes(); - this.onDidChildrenChange(); - } - - swapChildren(from: number, to: number): void { - from = validateIndex(from, this.children.length); - to = validateIndex(to, this.children.length); - - if (from === to) { - return; - } - - this.splitview.swapViews(from, to); - - // swap boundary sashes - [this.children[from].boundarySashes, this.children[to].boundarySashes] - = [this.children[from].boundarySashes, this.children[to].boundarySashes]; - - // swap children - [this.children[from], this.children[to]] = [this.children[to], this.children[from]]; - - this.onDidChildrenChange(); - } - - resizeChild(index: number, size: number): void { - index = validateIndex(index, this.children.length); - - this.splitview.resizeView(index, size); - } - - isChildExpanded(index: number): boolean { - return this.splitview.isViewExpanded(index); - } - - distributeViewSizes(recursive = false): void { - this.splitview.distributeViewSizes(); - - if (recursive) { - for (const child of this.children) { - if (child instanceof BranchNode) { - child.distributeViewSizes(true); - } - } - } - } - - getChildSize(index: number): number { - index = validateIndex(index, this.children.length); - - return this.splitview.getViewSize(index); - } - - isChildVisible(index: number): boolean { - index = validateIndex(index, this.children.length); - - return this.splitview.isViewVisible(index); - } - - setChildVisible(index: number, visible: boolean): void { - index = validateIndex(index, this.children.length); - - if (this.splitview.isViewVisible(index) === visible) { - return; - } - - const wereAllChildrenHidden = this.splitview.contentSize === 0; - this.splitview.setViewVisible(index, visible); - const areAllChildrenHidden = this.splitview.contentSize === 0; - - // If all children are hidden then the parent should hide the entire splitview - // If the entire splitview is hidden then the parent should show the splitview when a child is shown - if ((visible && wereAllChildrenHidden) || (!visible && areAllChildrenHidden)) { - this._onDidVisibilityChange.fire(visible); - } - } - - getChildCachedVisibleSize(index: number): number | undefined { - index = validateIndex(index, this.children.length); - - return this.splitview.getViewCachedVisibleSize(index); - } - - private updateBoundarySashes(): void { - for (let i = 0; i < this.children.length; i++) { - this.children[i].boundarySashes = { - start: this.boundarySashes.orthogonalStart, - end: this.boundarySashes.orthogonalEnd, - orthogonalStart: i === 0 ? this.boundarySashes.start : this.splitview.sashes[i - 1], - orthogonalEnd: i === this.children.length - 1 ? this.boundarySashes.end : this.splitview.sashes[i], - }; - } - } - - private onDidChildrenChange(): void { - this.updateChildrenEvents(); - this._onDidChange.fire(undefined); - } - - private updateChildrenEvents(): void { - const onDidChildrenChange = Event.map(Event.any(...this.children.map(c => c.onDidChange)), () => undefined); - this.childrenChangeDisposable.dispose(); - this.childrenChangeDisposable = onDidChildrenChange(this._onDidChange.fire, this._onDidChange); - - const onDidChildrenSashReset = Event.any(...this.children.map((c, i) => Event.map(c.onDidSashReset, location => [i, ...location]))); - this.childrenSashResetDisposable.dispose(); - this.childrenSashResetDisposable = onDidChildrenSashReset(this._onDidSashReset.fire, this._onDidSashReset); - - const onDidScroll = Event.any(Event.signal(this.splitview.onDidScroll), ...this.children.map(c => c.onDidScroll)); - this.onDidScrollDisposable.dispose(); - this.onDidScrollDisposable = onDidScroll(this._onDidScroll.fire, this._onDidScroll); - - this.childrenVisibilityChangeDisposable.clear(); - this.children.forEach((child, index) => { - if (child instanceof BranchNode) { - this.childrenVisibilityChangeDisposable.add(child.onDidVisibilityChange((visible) => { - this.setChildVisible(index, visible); - })); - } - }); - } - - trySet2x2(other: BranchNode): IDisposable { - if (this.children.length !== 2 || other.children.length !== 2) { - return Disposable.None; - } - - if (this.getChildSize(0) !== other.getChildSize(0)) { - return Disposable.None; - } - - const [firstChild, secondChild] = this.children; - const [otherFirstChild, otherSecondChild] = other.children; - - if (!(firstChild instanceof LeafNode) || !(secondChild instanceof LeafNode)) { - return Disposable.None; - } - - if (!(otherFirstChild instanceof LeafNode) || !(otherSecondChild instanceof LeafNode)) { - return Disposable.None; - } - - if (this.orientation === Orientation.VERTICAL) { - secondChild.linkedWidthNode = otherFirstChild.linkedHeightNode = firstChild; - firstChild.linkedWidthNode = otherSecondChild.linkedHeightNode = secondChild; - otherSecondChild.linkedWidthNode = firstChild.linkedHeightNode = otherFirstChild; - otherFirstChild.linkedWidthNode = secondChild.linkedHeightNode = otherSecondChild; - } else { - otherFirstChild.linkedWidthNode = secondChild.linkedHeightNode = firstChild; - otherSecondChild.linkedWidthNode = firstChild.linkedHeightNode = secondChild; - firstChild.linkedWidthNode = otherSecondChild.linkedHeightNode = otherFirstChild; - secondChild.linkedWidthNode = otherFirstChild.linkedHeightNode = otherSecondChild; - } - - const mySash = this.splitview.sashes[0]; - const otherSash = other.splitview.sashes[0]; - mySash.linkedSash = otherSash; - otherSash.linkedSash = mySash; - - this._onDidChange.fire(undefined); - other._onDidChange.fire(undefined); - - return toDisposable(() => { - mySash.linkedSash = otherSash.linkedSash = undefined; - firstChild.linkedHeightNode = firstChild.linkedWidthNode = undefined; - secondChild.linkedHeightNode = secondChild.linkedWidthNode = undefined; - otherFirstChild.linkedHeightNode = otherFirstChild.linkedWidthNode = undefined; - otherSecondChild.linkedHeightNode = otherSecondChild.linkedWidthNode = undefined; - }); - } - - private updateSplitviewEdgeSnappingEnablement(): void { - this.splitview.startSnappingEnabled = this._edgeSnapping || this._absoluteOrthogonalOffset > 0; - this.splitview.endSnappingEnabled = this._edgeSnapping || this._absoluteOrthogonalOffset + this._size < this.absoluteOrthogonalSize; - } - - dispose(): void { - for (const child of this.children) { - child.dispose(); - } - - this._onDidChange.dispose(); - this._onDidSashReset.dispose(); - this._onDidVisibilityChange.dispose(); - - this.childrenVisibilityChangeDisposable.dispose(); - this.splitviewSashResetDisposable.dispose(); - this.childrenSashResetDisposable.dispose(); - this.childrenChangeDisposable.dispose(); - this.onDidScrollDisposable.dispose(); - this.splitview.dispose(); - } -} - -/** - * Creates a latched event that avoids being fired when the view - * constraints do not change at all. - */ -function createLatchedOnDidChangeViewEvent(view: IView): Event { - const [onDidChangeViewConstraints, onDidSetViewSize] = Event.split(view.onDidChange, isUndefined); - - return Event.any( - onDidSetViewSize, - Event.map( - Event.latch( - Event.map(onDidChangeViewConstraints, _ => ([view.minimumWidth, view.maximumWidth, view.minimumHeight, view.maximumHeight])), - arrayEquals - ), - _ => undefined - ) - ); -} - -class LeafNode implements ISplitView, IDisposable { - - private _size: number = 0; - get size(): number { return this._size; } - - private _orthogonalSize: number; - get orthogonalSize(): number { return this._orthogonalSize; } - - private absoluteOffset: number = 0; - private absoluteOrthogonalOffset: number = 0; - - readonly onDidScroll: Event = Event.None; - readonly onDidSashReset: Event = Event.None; - - private _onDidLinkedWidthNodeChange = new Relay(); - private _linkedWidthNode: LeafNode | undefined = undefined; - get linkedWidthNode(): LeafNode | undefined { return this._linkedWidthNode; } - set linkedWidthNode(node: LeafNode | undefined) { - this._onDidLinkedWidthNodeChange.input = node ? node._onDidViewChange : Event.None; - this._linkedWidthNode = node; - this._onDidSetLinkedNode.fire(undefined); - } - - private _onDidLinkedHeightNodeChange = new Relay(); - private _linkedHeightNode: LeafNode | undefined = undefined; - get linkedHeightNode(): LeafNode | undefined { return this._linkedHeightNode; } - set linkedHeightNode(node: LeafNode | undefined) { - this._onDidLinkedHeightNodeChange.input = node ? node._onDidViewChange : Event.None; - this._linkedHeightNode = node; - this._onDidSetLinkedNode.fire(undefined); - } - - private readonly _onDidSetLinkedNode = new Emitter(); - private _onDidViewChange: Event; - readonly onDidChange: Event; - - private readonly disposables = new DisposableStore(); - - constructor( - readonly view: IView, - readonly orientation: Orientation, - readonly layoutController: LayoutController, - orthogonalSize: number, - size: number = 0 - ) { - this._orthogonalSize = orthogonalSize; - this._size = size; - - const onDidChange = createLatchedOnDidChangeViewEvent(view); - this._onDidViewChange = Event.map(onDidChange, e => e && (this.orientation === Orientation.VERTICAL ? e.width : e.height), this.disposables); - this.onDidChange = Event.any(this._onDidViewChange, this._onDidSetLinkedNode.event, this._onDidLinkedWidthNodeChange.event, this._onDidLinkedHeightNodeChange.event); - } - - get width(): number { - return this.orientation === Orientation.HORIZONTAL ? this.orthogonalSize : this.size; - } - - get height(): number { - return this.orientation === Orientation.HORIZONTAL ? this.size : this.orthogonalSize; - } - - get top(): number { - return this.orientation === Orientation.HORIZONTAL ? this.absoluteOffset : this.absoluteOrthogonalOffset; - } - - get left(): number { - return this.orientation === Orientation.HORIZONTAL ? this.absoluteOrthogonalOffset : this.absoluteOffset; - } - - get element(): HTMLElement { - return this.view.element; - } - - private get minimumWidth(): number { - return this.linkedWidthNode ? Math.max(this.linkedWidthNode.view.minimumWidth, this.view.minimumWidth) : this.view.minimumWidth; - } - - private get maximumWidth(): number { - return this.linkedWidthNode ? Math.min(this.linkedWidthNode.view.maximumWidth, this.view.maximumWidth) : this.view.maximumWidth; - } - - private get minimumHeight(): number { - return this.linkedHeightNode ? Math.max(this.linkedHeightNode.view.minimumHeight, this.view.minimumHeight) : this.view.minimumHeight; - } - - private get maximumHeight(): number { - return this.linkedHeightNode ? Math.min(this.linkedHeightNode.view.maximumHeight, this.view.maximumHeight) : this.view.maximumHeight; - } - - get minimumSize(): number { - return this.orientation === Orientation.HORIZONTAL ? this.minimumHeight : this.minimumWidth; - } - - get maximumSize(): number { - return this.orientation === Orientation.HORIZONTAL ? this.maximumHeight : this.maximumWidth; - } - - get priority(): LayoutPriority | undefined { - return this.view.priority; - } - - get proportionalLayout(): boolean { - return this.view.proportionalLayout ?? true; - } - - get snap(): boolean | undefined { - return this.view.snap; - } - - get minimumOrthogonalSize(): number { - return this.orientation === Orientation.HORIZONTAL ? this.minimumWidth : this.minimumHeight; - } - - get maximumOrthogonalSize(): number { - return this.orientation === Orientation.HORIZONTAL ? this.maximumWidth : this.maximumHeight; - } - - private _boundarySashes: IRelativeBoundarySashes = {}; - get boundarySashes(): IRelativeBoundarySashes { return this._boundarySashes; } - set boundarySashes(boundarySashes: IRelativeBoundarySashes) { - this._boundarySashes = boundarySashes; - - this.view.setBoundarySashes?.(toAbsoluteBoundarySashes(boundarySashes, this.orientation)); - } - - layout(size: number, offset: number, ctx: ILayoutContext | undefined): void { - if (!this.layoutController.isLayoutEnabled) { - return; - } - - if (typeof ctx === 'undefined') { - throw new Error('Invalid state'); - } - - this._size = size; - this._orthogonalSize = ctx.orthogonalSize; - this.absoluteOffset = ctx.absoluteOffset + offset; - this.absoluteOrthogonalOffset = ctx.absoluteOrthogonalOffset; - - this._layout(this.width, this.height, this.top, this.left); - } - - private cachedWidth: number = 0; - private cachedHeight: number = 0; - private cachedTop: number = 0; - private cachedLeft: number = 0; - - private _layout(width: number, height: number, top: number, left: number): void { - if (this.cachedWidth === width && this.cachedHeight === height && this.cachedTop === top && this.cachedLeft === left) { - return; - } - - this.cachedWidth = width; - this.cachedHeight = height; - this.cachedTop = top; - this.cachedLeft = left; - this.view.layout(width, height, top, left); - } - - setVisible(visible: boolean): void { - this.view.setVisible?.(visible); - } - - dispose(): void { - this.disposables.dispose(); - } -} - -type Node = BranchNode | LeafNode; - -export interface INodeDescriptor { - node: Node; - visible?: boolean; -} - -function flipNode(node: BranchNode, size: number, orthogonalSize: number): BranchNode; -function flipNode(node: LeafNode, size: number, orthogonalSize: number): LeafNode; -function flipNode(node: Node, size: number, orthogonalSize: number): Node; -function flipNode(node: Node, size: number, orthogonalSize: number): Node { - if (node instanceof BranchNode) { - const result = new BranchNode(orthogonal(node.orientation), node.layoutController, node.styles, node.splitviewProportionalLayout, size, orthogonalSize, node.edgeSnapping); - - let totalSize = 0; - - for (let i = node.children.length - 1; i >= 0; i--) { - const child = node.children[i]; - const childSize = child instanceof BranchNode ? child.orthogonalSize : child.size; - - let newSize = node.size === 0 ? 0 : Math.round((size * childSize) / node.size); - totalSize += newSize; - - // The last view to add should adjust to rounding errors - if (i === 0) { - newSize += size - totalSize; - } - - result.addChild(flipNode(child, orthogonalSize, newSize), newSize, 0, true); - } - - node.dispose(); - return result; - } else { - const result = new LeafNode(node.view, orthogonal(node.orientation), node.layoutController, orthogonalSize); - node.dispose(); - return result; - } -} - -/** - * The location of a {@link IView view} within a {@link GridView}. - * - * A GridView is a tree composition of multiple {@link SplitView} instances, orthogonal - * between one another. Here's an example: - * - * ``` - * +-----+---------------+ - * | A | B | - * +-----+---------+-----+ - * | C | | - * +---------------+ D | - * | E | | - * +---------------+-----+ - * ``` - * - * The above grid's tree structure is: - * - * ``` - * Vertical SplitView - * +-Horizontal SplitView - * | +-A - * | +-B - * +- Horizontal SplitView - * +-Vertical SplitView - * | +-C - * | +-E - * +-D - * ``` - * - * So, {@link IView views} within a {@link GridView} can be referenced by - * a sequence of indexes, each index referencing each SplitView. Here are - * each view's locations, from the example above: - * - * - `A`: `[0,0]` - * - `B`: `[0,1]` - * - `C`: `[1,0,0]` - * - `D`: `[1,1]` - * - `E`: `[1,0,1]` - */ -export type GridLocation = number[]; - -/** - * The {@link GridView} is the UI component which implements a two dimensional - * flex-like layout algorithm for a collection of {@link IView} instances, which - * are mostly HTMLElement instances with size constraints. A {@link GridView} is a - * tree composition of multiple {@link SplitView} instances, orthogonal between - * one another. It will respect view's size contraints, just like the SplitView. - * - * It has a low-level index based API, allowing for fine grain performant operations. - * Look into the {@link Grid} widget for a higher-level API. - * - * Features: - * - flex-like layout algorithm - * - snap support - * - corner sash support - * - Alt key modifier behavior, macOS style - * - layout (de)serialization - */ -export class GridView implements IDisposable { - - /** - * The DOM element for this view. - */ - readonly element: HTMLElement; - - private styles: IGridViewStyles; - private proportionalLayout: boolean; - private _root!: BranchNode; - private onDidSashResetRelay = new Relay(); - private _onDidScroll = new Relay(); - private _onDidChange = new Relay(); - private _boundarySashes: IBoundarySashes = {}; - - /** - * The layout controller makes sure layout only propagates - * to the views after the very first call to {@link GridView.layout}. - */ - private layoutController: LayoutController; - private disposable2x2: IDisposable = Disposable.None; - - private get root(): BranchNode { return this._root; } - - private set root(root: BranchNode) { - const oldRoot = this._root; - - if (oldRoot) { - oldRoot.element.remove(); - oldRoot.dispose(); - } - - this._root = root; - this.element.appendChild(root.element); - this.onDidSashResetRelay.input = root.onDidSashReset; - this._onDidChange.input = Event.map(root.onDidChange, () => undefined); // TODO - this._onDidScroll.input = root.onDidScroll; - } - - /** - * Fires whenever the user double clicks a {@link Sash sash}. - */ - readonly onDidSashReset = this.onDidSashResetRelay.event; - - /** - * Fires whenever the user scrolls a {@link SplitView} within - * the grid. - */ - readonly onDidScroll = this._onDidScroll.event; - - /** - * Fires whenever a view within the grid changes its size constraints. - */ - readonly onDidChange = this._onDidChange.event; - - /** - * The width of the grid. - */ - get width(): number { return this.root.width; } - - /** - * The height of the grid. - */ - get height(): number { return this.root.height; } - - /** - * The minimum width of the grid. - */ - get minimumWidth(): number { return this.root.minimumWidth; } - - /** - * The minimum height of the grid. - */ - get minimumHeight(): number { return this.root.minimumHeight; } - - /** - * The maximum width of the grid. - */ - get maximumWidth(): number { return this.root.maximumHeight; } - - /** - * The maximum height of the grid. - */ - get maximumHeight(): number { return this.root.maximumHeight; } - - get orientation(): Orientation { return this._root.orientation; } - get boundarySashes(): IBoundarySashes { return this._boundarySashes; } - - /** - * The orientation of the grid. Matches the orientation of the root - * {@link SplitView} in the grid's tree model. - */ - set orientation(orientation: Orientation) { - if (this._root.orientation === orientation) { - return; - } - - const { size, orthogonalSize, absoluteOffset, absoluteOrthogonalOffset } = this._root; - this.root = flipNode(this._root, orthogonalSize, size); - this.root.layout(size, 0, { orthogonalSize, absoluteOffset: absoluteOrthogonalOffset, absoluteOrthogonalOffset: absoluteOffset, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize }); - this.boundarySashes = this.boundarySashes; - } - - /** - * A collection of sashes perpendicular to each edge of the grid. - * Corner sashes will be created for each intersection. - */ - set boundarySashes(boundarySashes: IBoundarySashes) { - this._boundarySashes = boundarySashes; - this.root.boundarySashes = fromAbsoluteBoundarySashes(boundarySashes, this.orientation); - } - - /** - * Enable/disable edge snapping across all grid views. - */ - set edgeSnapping(edgeSnapping: boolean) { - this.root.edgeSnapping = edgeSnapping; - } - - private maximizedNode: LeafNode | undefined = undefined; - - private readonly _onDidChangeViewMaximized = new Emitter(); - readonly onDidChangeViewMaximized = this._onDidChangeViewMaximized.event; - - /** - * Create a new {@link GridView} instance. - * - * @remarks It's the caller's responsibility to append the - * {@link GridView.element} to the page's DOM. - */ - constructor(options: IGridViewOptions = {}) { - this.element = $('.monaco-grid-view'); - this.styles = options.styles || defaultStyles; - this.proportionalLayout = typeof options.proportionalLayout !== 'undefined' ? !!options.proportionalLayout : true; - this.layoutController = new LayoutController(false); - this.root = new BranchNode(Orientation.VERTICAL, this.layoutController, this.styles, this.proportionalLayout); - } - - style(styles: IGridViewStyles): void { - this.styles = styles; - this.root.style(styles); - } - - /** - * Layout the {@link GridView}. - * - * Optionally provide a `top` and `left` positions, those will propagate - * as an origin for positions passed to {@link IView.layout}. - * - * @param width The width of the {@link GridView}. - * @param height The height of the {@link GridView}. - * @param top Optional, the top location of the {@link GridView}. - * @param left Optional, the left location of the {@link GridView}. - */ - layout(width: number, height: number, top: number = 0, left: number = 0): void { - this.layoutController.isLayoutEnabled = true; - - const [size, orthogonalSize, offset, orthogonalOffset] = this.root.orientation === Orientation.HORIZONTAL ? [height, width, top, left] : [width, height, left, top]; - this.root.layout(size, 0, { orthogonalSize, absoluteOffset: offset, absoluteOrthogonalOffset: orthogonalOffset, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize }); - } - - /** - * Add a {@link IView view} to this {@link GridView}. - * - * @param view The view to add. - * @param size Either a fixed size, or a dynamic {@link Sizing} strategy. - * @param location The {@link GridLocation location} to insert the view on. - */ - addView(view: IView, size: number | Sizing, location: GridLocation): void { - if (this.hasMaximizedView()) { - this.exitMaximizedView(); - } - - this.disposable2x2.dispose(); - this.disposable2x2 = Disposable.None; - - const [rest, index] = tail(location); - const [pathToParent, parent] = this.getNode(rest); - - if (parent instanceof BranchNode) { - const node = new LeafNode(view, orthogonal(parent.orientation), this.layoutController, parent.orthogonalSize); - - try { - parent.addChild(node, size, index); - } catch (err) { - node.dispose(); - throw err; - } - } else { - const [, grandParent] = tail(pathToParent); - const [, parentIndex] = tail(rest); - - let newSiblingSize: number | Sizing = 0; - - const newSiblingCachedVisibleSize = grandParent.getChildCachedVisibleSize(parentIndex); - if (typeof newSiblingCachedVisibleSize === 'number') { - newSiblingSize = Sizing.Invisible(newSiblingCachedVisibleSize); - } - - const oldChild = grandParent.removeChild(parentIndex); - oldChild.dispose(); - - const newParent = new BranchNode(parent.orientation, parent.layoutController, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize, grandParent.edgeSnapping); - grandParent.addChild(newParent, parent.size, parentIndex); - - const newSibling = new LeafNode(parent.view, grandParent.orientation, this.layoutController, parent.size); - newParent.addChild(newSibling, newSiblingSize, 0); - - if (typeof size !== 'number' && size.type === 'split') { - size = Sizing.Split(0); - } - - const node = new LeafNode(view, grandParent.orientation, this.layoutController, parent.size); - newParent.addChild(node, size, index); - } - - this.trySet2x2(); - } - - /** - * Remove a {@link IView view} from this {@link GridView}. - * - * @param location The {@link GridLocation location} of the {@link IView view}. - * @param sizing Whether to distribute other {@link IView view}'s sizes. - */ - removeView(location: GridLocation, sizing?: DistributeSizing | AutoSizing): IView { - if (this.hasMaximizedView()) { - this.exitMaximizedView(); - } - - this.disposable2x2.dispose(); - this.disposable2x2 = Disposable.None; - - const [rest, index] = tail(location); - const [pathToParent, parent] = this.getNode(rest); - - if (!(parent instanceof BranchNode)) { - throw new Error('Invalid location'); - } - - const node = parent.children[index]; - - if (!(node instanceof LeafNode)) { - throw new Error('Invalid location'); - } - - parent.removeChild(index, sizing); - node.dispose(); - - if (parent.children.length === 0) { - throw new Error('Invalid grid state'); - } - - if (parent.children.length > 1) { - this.trySet2x2(); - return node.view; - } - - if (pathToParent.length === 0) { // parent is root - const sibling = parent.children[0]; - - if (sibling instanceof LeafNode) { - return node.view; - } - - // we must promote sibling to be the new root - parent.removeChild(0); - parent.dispose(); - this.root = sibling; - this.boundarySashes = this.boundarySashes; - this.trySet2x2(); - return node.view; - } - - const [, grandParent] = tail(pathToParent); - const [, parentIndex] = tail(rest); - - const isSiblingVisible = parent.isChildVisible(0); - const sibling = parent.removeChild(0); - - const sizes = grandParent.children.map((_, i) => grandParent.getChildSize(i)); - grandParent.removeChild(parentIndex, sizing); - parent.dispose(); - - if (sibling instanceof BranchNode) { - sizes.splice(parentIndex, 1, ...sibling.children.map(c => c.size)); - - const siblingChildren = sibling.removeAllChildren(); - - for (let i = 0; i < siblingChildren.length; i++) { - grandParent.addChild(siblingChildren[i], siblingChildren[i].size, parentIndex + i); - } - } else { - const newSibling = new LeafNode(sibling.view, orthogonal(sibling.orientation), this.layoutController, sibling.size); - const sizing = isSiblingVisible ? sibling.orthogonalSize : Sizing.Invisible(sibling.orthogonalSize); - grandParent.addChild(newSibling, sizing, parentIndex); - } - - sibling.dispose(); - - for (let i = 0; i < sizes.length; i++) { - grandParent.resizeChild(i, sizes[i]); - } - - this.trySet2x2(); - return node.view; - } - - /** - * Move a {@link IView view} within its parent. - * - * @param parentLocation The {@link GridLocation location} of the {@link IView view}'s parent. - * @param from The index of the {@link IView view} to move. - * @param to The index where the {@link IView view} should move to. - */ - moveView(parentLocation: GridLocation, from: number, to: number): void { - if (this.hasMaximizedView()) { - this.exitMaximizedView(); - } - - const [, parent] = this.getNode(parentLocation); - - if (!(parent instanceof BranchNode)) { - throw new Error('Invalid location'); - } - - parent.moveChild(from, to); - - this.trySet2x2(); - } - - /** - * Swap two {@link IView views} within the {@link GridView}. - * - * @param from The {@link GridLocation location} of one view. - * @param to The {@link GridLocation location} of another view. - */ - swapViews(from: GridLocation, to: GridLocation): void { - if (this.hasMaximizedView()) { - this.exitMaximizedView(); - } - - const [fromRest, fromIndex] = tail(from); - const [, fromParent] = this.getNode(fromRest); - - if (!(fromParent instanceof BranchNode)) { - throw new Error('Invalid from location'); - } - - const fromSize = fromParent.getChildSize(fromIndex); - const fromNode = fromParent.children[fromIndex]; - - if (!(fromNode instanceof LeafNode)) { - throw new Error('Invalid from location'); - } - - const [toRest, toIndex] = tail(to); - const [, toParent] = this.getNode(toRest); - - if (!(toParent instanceof BranchNode)) { - throw new Error('Invalid to location'); - } - - const toSize = toParent.getChildSize(toIndex); - const toNode = toParent.children[toIndex]; - - if (!(toNode instanceof LeafNode)) { - throw new Error('Invalid to location'); - } - - if (fromParent === toParent) { - fromParent.swapChildren(fromIndex, toIndex); - } else { - fromParent.removeChild(fromIndex); - toParent.removeChild(toIndex); - - fromParent.addChild(toNode, fromSize, fromIndex); - toParent.addChild(fromNode, toSize, toIndex); - } - - this.trySet2x2(); - } - - /** - * Resize a {@link IView view}. - * - * @param location The {@link GridLocation location} of the view. - * @param size The size the view should be. Optionally provide a single dimension. - */ - resizeView(location: GridLocation, size: Partial): void { - if (this.hasMaximizedView()) { - this.exitMaximizedView(); - } - - const [rest, index] = tail(location); - const [pathToParent, parent] = this.getNode(rest); - - if (!(parent instanceof BranchNode)) { - throw new Error('Invalid location'); - } - - if (!size.width && !size.height) { - return; - } - - const [parentSize, grandParentSize] = parent.orientation === Orientation.HORIZONTAL ? [size.width, size.height] : [size.height, size.width]; - - if (typeof grandParentSize === 'number' && pathToParent.length > 0) { - const [, grandParent] = tail(pathToParent); - const [, parentIndex] = tail(rest); - - grandParent.resizeChild(parentIndex, grandParentSize); - } - - if (typeof parentSize === 'number') { - parent.resizeChild(index, parentSize); - } - - this.trySet2x2(); - } - - /** - * Get the size of a {@link IView view}. - * - * @param location The {@link GridLocation location} of the view. Provide `undefined` to get - * the size of the grid itself. - */ - getViewSize(location?: GridLocation): IViewSize { - if (!location) { - return { width: this.root.width, height: this.root.height }; - } - - const [, node] = this.getNode(location); - return { width: node.width, height: node.height }; - } - - /** - * Get the cached visible size of a {@link IView view}. This was the size - * of the view at the moment it last became hidden. - * - * @param location The {@link GridLocation location} of the view. - */ - getViewCachedVisibleSize(location: GridLocation): number | undefined { - const [rest, index] = tail(location); - const [, parent] = this.getNode(rest); - - if (!(parent instanceof BranchNode)) { - throw new Error('Invalid location'); - } - - return parent.getChildCachedVisibleSize(index); - } - - /** - * Maximize the size of a {@link IView view} by collapsing all other views - * to their minimum sizes. - * - * @param location The {@link GridLocation location} of the view. - */ - expandView(location: GridLocation): void { - if (this.hasMaximizedView()) { - this.exitMaximizedView(); - } - - const [ancestors, node] = this.getNode(location); - - if (!(node instanceof LeafNode)) { - throw new Error('Invalid location'); - } - - for (let i = 0; i < ancestors.length; i++) { - ancestors[i].resizeChild(location[i], Number.POSITIVE_INFINITY); - } - } - - /** - * Returns whether all other {@link IView views} are at their minimum size. - * - * @param location The {@link GridLocation location} of the view. - */ - isViewExpanded(location: GridLocation): boolean { - if (this.hasMaximizedView()) { - // No view can be expanded when a view is maximized - return false; - } - - const [ancestors, node] = this.getNode(location); - - if (!(node instanceof LeafNode)) { - throw new Error('Invalid location'); - } - - for (let i = 0; i < ancestors.length; i++) { - if (!ancestors[i].isChildExpanded(location[i])) { - return false; - } - } - - return true; - } - - maximizeView(location: GridLocation) { - const [, nodeToMaximize] = this.getNode(location); - if (!(nodeToMaximize instanceof LeafNode)) { - throw new Error('Location is not a LeafNode'); - } - - if (this.maximizedNode === nodeToMaximize) { - return; - } - - if (this.hasMaximizedView()) { - this.exitMaximizedView(); - } - - function hideAllViewsBut(parent: BranchNode, exclude: LeafNode): void { - for (let i = 0; i < parent.children.length; i++) { - const child = parent.children[i]; - if (child instanceof LeafNode) { - if (child !== exclude) { - parent.setChildVisible(i, false); - } - } else { - hideAllViewsBut(child, exclude); - } - } - } - - hideAllViewsBut(this.root, nodeToMaximize); - - this.maximizedNode = nodeToMaximize; - this._onDidChangeViewMaximized.fire(true); - } - - exitMaximizedView(): void { - if (!this.maximizedNode) { - return; - } - this.maximizedNode = undefined; - - // When hiding a view, it's previous size is cached. - // To restore the sizes of all views, they need to be made visible in reverse order. - function showViewsInReverseOrder(parent: BranchNode): void { - for (let index = parent.children.length - 1; index >= 0; index--) { - const child = parent.children[index]; - if (child instanceof LeafNode) { - parent.setChildVisible(index, true); - } else { - showViewsInReverseOrder(child); - } - } - } - - showViewsInReverseOrder(this.root); - - this._onDidChangeViewMaximized.fire(false); - } - - hasMaximizedView(): boolean { - return this.maximizedNode !== undefined; - } - - /** - * Returns whether the {@link IView view} is maximized. - * - * @param location The {@link GridLocation location} of the view. - */ - isViewMaximized(location: GridLocation): boolean { - const [, node] = this.getNode(location); - if (!(node instanceof LeafNode)) { - throw new Error('Location is not a LeafNode'); - } - return node === this.maximizedNode; - } - - /** - * Distribute the size among all {@link IView views} within the entire - * grid or within a single {@link SplitView}. - * - * @param location The {@link GridLocation location} of a view containing - * children views, which will have their sizes distributed within the parent - * view's size. Provide `undefined` to recursively distribute all views' sizes - * in the entire grid. - */ - distributeViewSizes(location?: GridLocation): void { - if (this.hasMaximizedView()) { - this.exitMaximizedView(); - } - - if (!location) { - this.root.distributeViewSizes(true); - return; - } - - const [, node] = this.getNode(location); - - if (!(node instanceof BranchNode)) { - throw new Error('Invalid location'); - } - - node.distributeViewSizes(); - this.trySet2x2(); - } - - /** - * Returns whether a {@link IView view} is visible. - * - * @param location The {@link GridLocation location} of the view. - */ - isViewVisible(location: GridLocation): boolean { - const [rest, index] = tail(location); - const [, parent] = this.getNode(rest); - - if (!(parent instanceof BranchNode)) { - throw new Error('Invalid from location'); - } - - return parent.isChildVisible(index); - } - - /** - * Set the visibility state of a {@link IView view}. - * - * @param location The {@link GridLocation location} of the view. - */ - setViewVisible(location: GridLocation, visible: boolean): void { - if (this.hasMaximizedView()) { - this.exitMaximizedView(); - return; - } - - const [rest, index] = tail(location); - const [, parent] = this.getNode(rest); - - if (!(parent instanceof BranchNode)) { - throw new Error('Invalid from location'); - } - - parent.setChildVisible(index, visible); - } - - /** - * Returns a descriptor for the entire grid. - */ - getView(): GridBranchNode; - - /** - * Returns a descriptor for a {@link GridLocation subtree} within the - * {@link GridView}. - * - * @param location The {@link GridLocation location} of the root of - * the {@link GridLocation subtree}. - */ - getView(location: GridLocation): GridNode; - getView(location?: GridLocation): GridNode { - const node = location ? this.getNode(location)[1] : this._root; - return this._getViews(node, this.orientation); - } - - /** - * Construct a new {@link GridView} from a JSON object. - * - * @param json The JSON object. - * @param deserializer A deserializer which can revive each view. - * @returns A new {@link GridView} instance. - */ - static deserialize(json: ISerializedGridView, deserializer: IViewDeserializer, options: IGridViewOptions = {}): GridView { - if (typeof json.orientation !== 'number') { - throw new Error('Invalid JSON: \'orientation\' property must be a number.'); - } else if (typeof json.width !== 'number') { - throw new Error('Invalid JSON: \'width\' property must be a number.'); - } else if (typeof json.height !== 'number') { - throw new Error('Invalid JSON: \'height\' property must be a number.'); - } else if (json.root?.type !== 'branch') { - throw new Error('Invalid JSON: \'root\' property must have \'type\' value of branch.'); - } - - const orientation = json.orientation; - const height = json.height; - - const result = new GridView(options); - result._deserialize(json.root as ISerializedBranchNode, orientation, deserializer, height); - - return result; - } - - private _deserialize(root: ISerializedBranchNode, orientation: Orientation, deserializer: IViewDeserializer, orthogonalSize: number): void { - this.root = this._deserializeNode(root, orientation, deserializer, orthogonalSize) as BranchNode; - } - - private _deserializeNode(node: ISerializedNode, orientation: Orientation, deserializer: IViewDeserializer, orthogonalSize: number): Node { - let result: Node; - if (node.type === 'branch') { - const serializedChildren = node.data as ISerializedNode[]; - const children = serializedChildren.map(serializedChild => { - return { - node: this._deserializeNode(serializedChild, orthogonal(orientation), deserializer, node.size), - visible: (serializedChild as { visible?: boolean }).visible - } satisfies INodeDescriptor; - }); - - result = new BranchNode(orientation, this.layoutController, this.styles, this.proportionalLayout, node.size, orthogonalSize, undefined, children); - } else { - result = new LeafNode(deserializer.fromJSON(node.data), orientation, this.layoutController, orthogonalSize, node.size); - if (node.maximized && !this.maximizedNode) { - this.maximizedNode = result; - this._onDidChangeViewMaximized.fire(true); - } - } - - return result; - } - - private _getViews(node: Node, orientation: Orientation, cachedVisibleSize?: number): GridNode { - const box = { top: node.top, left: node.left, width: node.width, height: node.height }; - - if (node instanceof LeafNode) { - return { view: node.view, box, cachedVisibleSize, maximized: this.maximizedNode === node }; - } - - const children: GridNode[] = []; - - for (let i = 0; i < node.children.length; i++) { - const child = node.children[i]; - const cachedVisibleSize = node.getChildCachedVisibleSize(i); - - children.push(this._getViews(child, orthogonal(orientation), cachedVisibleSize)); - } - - return { children, box }; - } - - private getNode(location: GridLocation, node: Node = this.root, path: BranchNode[] = []): [BranchNode[], Node] { - if (location.length === 0) { - return [path, node]; - } - - if (!(node instanceof BranchNode)) { - throw new Error('Invalid location'); - } - - const [index, ...rest] = location; - - if (index < 0 || index >= node.children.length) { - throw new Error('Invalid location'); - } - - const child = node.children[index]; - path.push(node); - - return this.getNode(rest, child, path); - } - - /** - * Attempt to lock the {@link Sash sashes} in this {@link GridView} so - * the grid behaves as a 2x2 matrix, with a corner sash in the middle. - * - * In case the grid isn't a 2x2 grid _and_ all sashes are not aligned, - * this method is a no-op. - */ - trySet2x2(): void { - this.disposable2x2.dispose(); - this.disposable2x2 = Disposable.None; - - if (this.root.children.length !== 2) { - return; - } - - const [first, second] = this.root.children; - - if (!(first instanceof BranchNode) || !(second instanceof BranchNode)) { - return; - } - - this.disposable2x2 = first.trySet2x2(second); - } - - /** - * Populate a map with views to DOM nodes. - * @remarks To be used internally only. - */ - getViewMap(map: Map, node?: Node): void { - if (!node) { - node = this.root; - } - - if (node instanceof BranchNode) { - node.children.forEach(child => this.getViewMap(map, child)); - } else { - map.set(node.view, node.element); - } - } - - dispose(): void { - this.onDidSashResetRelay.dispose(); - this.root.dispose(); - this.element.remove(); - } -} diff --git a/src/vs/base/browser/ui/mouseCursor/mouseCursor.css b/src/vs/base/browser/ui/mouseCursor/mouseCursor.css deleted file mode 100644 index 1d7ede84..00000000 --- a/src/vs/base/browser/ui/mouseCursor/mouseCursor.css +++ /dev/null @@ -1,8 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-mouse-cursor-text { - cursor: text; -} diff --git a/src/vs/base/browser/ui/mouseCursor/mouseCursor.ts b/src/vs/base/browser/ui/mouseCursor/mouseCursor.ts deleted file mode 100644 index f845a4db..00000000 --- a/src/vs/base/browser/ui/mouseCursor/mouseCursor.ts +++ /dev/null @@ -1,8 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// import 'vs/css!./mouseCursor'; - -export const MOUSE_CURSOR_TEXT_CSS_CLASS_NAME = `monaco-mouse-cursor-text`; diff --git a/src/vs/base/browser/ui/progressbar/progressAccessibilitySignal.ts b/src/vs/base/browser/ui/progressbar/progressAccessibilitySignal.ts deleted file mode 100644 index 19a5deba..00000000 --- a/src/vs/base/browser/ui/progressbar/progressAccessibilitySignal.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IDisposable } from 'vs/base/common/lifecycle'; - -export interface IScopedAccessibilityProgressSignalDelegate extends IDisposable { } - -const nullScopedAccessibilityProgressSignalFactory = () => ({ - msLoopTime: -1, - msDelayTime: -1, - dispose: () => { }, -}); -let progressAccessibilitySignalSchedulerFactory: (msDelayTime: number, msLoopTime?: number) => IScopedAccessibilityProgressSignalDelegate = nullScopedAccessibilityProgressSignalFactory; - -export function setProgressAcccessibilitySignalScheduler(progressAccessibilitySignalScheduler: (msDelayTime: number, msLoopTime?: number) => IScopedAccessibilityProgressSignalDelegate) { - progressAccessibilitySignalSchedulerFactory = progressAccessibilitySignalScheduler; -} - -export function getProgressAcccessibilitySignalScheduler(msDelayTime: number, msLoopTime?: number): IScopedAccessibilityProgressSignalDelegate { - return progressAccessibilitySignalSchedulerFactory(msDelayTime, msLoopTime); -} diff --git a/src/vs/base/browser/ui/progressbar/progressbar.css b/src/vs/base/browser/ui/progressbar/progressbar.css deleted file mode 100644 index dc23cd25..00000000 --- a/src/vs/base/browser/ui/progressbar/progressbar.css +++ /dev/null @@ -1,61 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-progress-container { - width: 100%; - height: 2px; - overflow: hidden; /* keep progress bit in bounds */ -} - -.monaco-progress-container .progress-bit { - width: 2%; - height: 2px; - position: absolute; - left: 0; - display: none; -} - -.monaco-progress-container.active .progress-bit { - display: inherit; -} - -.monaco-progress-container.discrete .progress-bit { - left: 0; - transition: width 100ms linear; -} - -.monaco-progress-container.discrete.done .progress-bit { - width: 100%; -} - -.monaco-progress-container.infinite .progress-bit { - animation-name: progress; - animation-duration: 4s; - animation-iteration-count: infinite; - transform: translate3d(0px, 0px, 0px); - animation-timing-function: linear; -} - -.monaco-progress-container.infinite.infinite-long-running .progress-bit { - /* - The more smooth `linear` timing function can cause - higher GPU consumption as indicated in - https://github.com/microsoft/vscode/issues/97900 & - https://github.com/microsoft/vscode/issues/138396 - */ - animation-timing-function: steps(100); -} - -/** - * The progress bit has a width: 2% (1/50) of the parent container. The animation moves it from 0% to 100% of - * that container. Since translateX is relative to the progress bit size, we have to multiple it with - * its relative size to the parent container: - * parent width: 5000% - * bit width: 100% - * translateX should be as follow: - * 50%: 5000% * 50% - 50% (set to center) = 2450% - * 100%: 5000% * 100% - 100% (do not overflow) = 4900% - */ -@keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4900%) scaleX(1) } } diff --git a/src/vs/base/browser/ui/progressbar/progressbar.ts b/src/vs/base/browser/ui/progressbar/progressbar.ts deleted file mode 100644 index bc717f15..00000000 --- a/src/vs/base/browser/ui/progressbar/progressbar.ts +++ /dev/null @@ -1,224 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { hide, show } from 'vs/base/browser/dom'; -import { getProgressAcccessibilitySignalScheduler } from 'vs/base/browser/ui/progressbar/progressAccessibilitySignal'; -import { RunOnceScheduler } from 'vs/base/common/async'; -import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { isNumber } from 'vs/base/common/types'; -// import 'vs/css!./progressbar'; - -const CSS_DONE = 'done'; -const CSS_ACTIVE = 'active'; -const CSS_INFINITE = 'infinite'; -const CSS_INFINITE_LONG_RUNNING = 'infinite-long-running'; -const CSS_DISCRETE = 'discrete'; - -export interface IProgressBarOptions extends IProgressBarStyles { -} - -export interface IProgressBarStyles { - progressBarBackground: string | undefined; -} - -export const unthemedProgressBarOptions: IProgressBarOptions = { - progressBarBackground: undefined -}; - -/** - * A progress bar with support for infinite or discrete progress. - */ -export class ProgressBar extends Disposable { - - /** - * After a certain time of showing the progress bar, switch - * to long-running mode and throttle animations to reduce - * the pressure on the GPU process. - * - * https://github.com/microsoft/vscode/issues/97900 - * https://github.com/microsoft/vscode/issues/138396 - */ - private static readonly LONG_RUNNING_INFINITE_THRESHOLD = 10000; - - private static readonly PROGRESS_SIGNAL_DEFAULT_DELAY = 3000; - - private workedVal: number; - private element!: HTMLElement; - private bit!: HTMLElement; - private totalWork: number | undefined; - private showDelayedScheduler: RunOnceScheduler; - private longRunningScheduler: RunOnceScheduler; - private readonly progressSignal = this._register(new MutableDisposable()); - - constructor(container: HTMLElement, options?: IProgressBarOptions) { - super(); - - this.workedVal = 0; - - this.showDelayedScheduler = this._register(new RunOnceScheduler(() => show(this.element), 0)); - this.longRunningScheduler = this._register(new RunOnceScheduler(() => this.infiniteLongRunning(), ProgressBar.LONG_RUNNING_INFINITE_THRESHOLD)); - - this.create(container, options); - } - - private create(container: HTMLElement, options?: IProgressBarOptions): void { - this.element = document.createElement('div'); - this.element.classList.add('monaco-progress-container'); - this.element.setAttribute('role', 'progressbar'); - this.element.setAttribute('aria-valuemin', '0'); - container.appendChild(this.element); - - this.bit = document.createElement('div'); - this.bit.classList.add('progress-bit'); - this.bit.style.backgroundColor = options?.progressBarBackground || '#0E70C0'; - this.element.appendChild(this.bit); - } - - private off(): void { - this.bit.style.width = 'inherit'; - this.bit.style.opacity = '1'; - this.element.classList.remove(CSS_ACTIVE, CSS_INFINITE, CSS_INFINITE_LONG_RUNNING, CSS_DISCRETE); - - this.workedVal = 0; - this.totalWork = undefined; - - this.longRunningScheduler.cancel(); - this.progressSignal.clear(); - } - - /** - * Indicates to the progress bar that all work is done. - */ - done(): ProgressBar { - return this.doDone(true); - } - - /** - * Stops the progressbar from showing any progress instantly without fading out. - */ - stop(): ProgressBar { - return this.doDone(false); - } - - private doDone(delayed: boolean): ProgressBar { - this.element.classList.add(CSS_DONE); - - // discrete: let it grow to 100% width and hide afterwards - if (!this.element.classList.contains(CSS_INFINITE)) { - this.bit.style.width = 'inherit'; - - if (delayed) { - setTimeout(() => this.off(), 200); - } else { - this.off(); - } - } - - // infinite: let it fade out and hide afterwards - else { - this.bit.style.opacity = '0'; - if (delayed) { - setTimeout(() => this.off(), 200); - } else { - this.off(); - } - } - - return this; - } - - /** - * Use this mode to indicate progress that has no total number of work units. - */ - infinite(): ProgressBar { - this.bit.style.width = '2%'; - this.bit.style.opacity = '1'; - - this.element.classList.remove(CSS_DISCRETE, CSS_DONE, CSS_INFINITE_LONG_RUNNING); - this.element.classList.add(CSS_ACTIVE, CSS_INFINITE); - - this.longRunningScheduler.schedule(); - - return this; - } - - private infiniteLongRunning(): void { - this.element.classList.add(CSS_INFINITE_LONG_RUNNING); - } - - /** - * Tells the progress bar the total number of work. Use in combination with workedVal() to let - * the progress bar show the actual progress based on the work that is done. - */ - total(value: number): ProgressBar { - this.workedVal = 0; - this.totalWork = value; - this.element.setAttribute('aria-valuemax', value.toString()); - - return this; - } - - /** - * Finds out if this progress bar is configured with total work - */ - hasTotal(): boolean { - return isNumber(this.totalWork); - } - - /** - * Tells the progress bar that an increment of work has been completed. - */ - worked(value: number): ProgressBar { - value = Math.max(1, Number(value)); - - return this.doSetWorked(this.workedVal + value); - } - - /** - * Tells the progress bar the total amount of work that has been completed. - */ - setWorked(value: number): ProgressBar { - value = Math.max(1, Number(value)); - - return this.doSetWorked(value); - } - - private doSetWorked(value: number): ProgressBar { - const totalWork = this.totalWork || 100; - - this.workedVal = value; - this.workedVal = Math.min(totalWork, this.workedVal); - - this.element.classList.remove(CSS_INFINITE, CSS_INFINITE_LONG_RUNNING, CSS_DONE); - this.element.classList.add(CSS_ACTIVE, CSS_DISCRETE); - this.element.setAttribute('aria-valuenow', value.toString()); - - this.bit.style.width = 100 * (this.workedVal / (totalWork)) + '%'; - - return this; - } - - getContainer(): HTMLElement { - return this.element; - } - - show(delay?: number): void { - this.showDelayedScheduler.cancel(); - this.progressSignal.value = getProgressAcccessibilitySignalScheduler(ProgressBar.PROGRESS_SIGNAL_DEFAULT_DELAY); - - if (typeof delay === 'number') { - this.showDelayedScheduler.schedule(delay); - } else { - show(this.element); - } - } - - hide(): void { - hide(this.element); - - this.showDelayedScheduler.cancel(); - this.progressSignal.clear(); - } -} diff --git a/src/vs/base/browser/ui/resizable/resizable.ts b/src/vs/base/browser/ui/resizable/resizable.ts deleted file mode 100644 index 95dfb06b..00000000 --- a/src/vs/base/browser/ui/resizable/resizable.ts +++ /dev/null @@ -1,190 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Dimension } from 'vs/base/browser/dom'; -import { Orientation, OrthogonalEdge, Sash, SashState } from 'vs/base/browser/ui/sash/sash'; -import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; - - -export interface IResizeEvent { - dimension: Dimension; - done: boolean; - north?: boolean; - east?: boolean; - south?: boolean; - west?: boolean; -} - -export class ResizableHTMLElement { - - readonly domNode: HTMLElement; - - private readonly _onDidWillResize = new Emitter(); - readonly onDidWillResize: Event = this._onDidWillResize.event; - - private readonly _onDidResize = new Emitter(); - readonly onDidResize: Event = this._onDidResize.event; - - private readonly _northSash: Sash; - private readonly _eastSash: Sash; - private readonly _southSash: Sash; - private readonly _westSash: Sash; - private readonly _sashListener = new DisposableStore(); - - private _size = new Dimension(0, 0); - private _minSize = new Dimension(0, 0); - private _maxSize = new Dimension(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); - private _preferredSize?: Dimension; - - constructor() { - this.domNode = document.createElement('div'); - this._eastSash = new Sash(this.domNode, { getVerticalSashLeft: () => this._size.width }, { orientation: Orientation.VERTICAL }); - this._westSash = new Sash(this.domNode, { getVerticalSashLeft: () => 0 }, { orientation: Orientation.VERTICAL }); - this._northSash = new Sash(this.domNode, { getHorizontalSashTop: () => 0 }, { orientation: Orientation.HORIZONTAL, orthogonalEdge: OrthogonalEdge.North }); - this._southSash = new Sash(this.domNode, { getHorizontalSashTop: () => this._size.height }, { orientation: Orientation.HORIZONTAL, orthogonalEdge: OrthogonalEdge.South }); - - this._northSash.orthogonalStartSash = this._westSash; - this._northSash.orthogonalEndSash = this._eastSash; - this._southSash.orthogonalStartSash = this._westSash; - this._southSash.orthogonalEndSash = this._eastSash; - - let currentSize: Dimension | undefined; - let deltaY = 0; - let deltaX = 0; - - this._sashListener.add(Event.any(this._northSash.onDidStart, this._eastSash.onDidStart, this._southSash.onDidStart, this._westSash.onDidStart)(() => { - if (currentSize === undefined) { - this._onDidWillResize.fire(); - currentSize = this._size; - deltaY = 0; - deltaX = 0; - } - })); - this._sashListener.add(Event.any(this._northSash.onDidEnd, this._eastSash.onDidEnd, this._southSash.onDidEnd, this._westSash.onDidEnd)(() => { - if (currentSize !== undefined) { - currentSize = undefined; - deltaY = 0; - deltaX = 0; - this._onDidResize.fire({ dimension: this._size, done: true }); - } - })); - - this._sashListener.add(this._eastSash.onDidChange(e => { - if (currentSize) { - deltaX = e.currentX - e.startX; - this.layout(currentSize.height + deltaY, currentSize.width + deltaX); - this._onDidResize.fire({ dimension: this._size, done: false, east: true }); - } - })); - this._sashListener.add(this._westSash.onDidChange(e => { - if (currentSize) { - deltaX = -(e.currentX - e.startX); - this.layout(currentSize.height + deltaY, currentSize.width + deltaX); - this._onDidResize.fire({ dimension: this._size, done: false, west: true }); - } - })); - this._sashListener.add(this._northSash.onDidChange(e => { - if (currentSize) { - deltaY = -(e.currentY - e.startY); - this.layout(currentSize.height + deltaY, currentSize.width + deltaX); - this._onDidResize.fire({ dimension: this._size, done: false, north: true }); - } - })); - this._sashListener.add(this._southSash.onDidChange(e => { - if (currentSize) { - deltaY = e.currentY - e.startY; - this.layout(currentSize.height + deltaY, currentSize.width + deltaX); - this._onDidResize.fire({ dimension: this._size, done: false, south: true }); - } - })); - - this._sashListener.add(Event.any(this._eastSash.onDidReset, this._westSash.onDidReset)(e => { - if (this._preferredSize) { - this.layout(this._size.height, this._preferredSize.width); - this._onDidResize.fire({ dimension: this._size, done: true }); - } - })); - this._sashListener.add(Event.any(this._northSash.onDidReset, this._southSash.onDidReset)(e => { - if (this._preferredSize) { - this.layout(this._preferredSize.height, this._size.width); - this._onDidResize.fire({ dimension: this._size, done: true }); - } - })); - } - - dispose(): void { - this._northSash.dispose(); - this._southSash.dispose(); - this._eastSash.dispose(); - this._westSash.dispose(); - this._sashListener.dispose(); - this._onDidResize.dispose(); - this._onDidWillResize.dispose(); - this.domNode.remove(); - } - - enableSashes(north: boolean, east: boolean, south: boolean, west: boolean): void { - this._northSash.state = north ? SashState.Enabled : SashState.Disabled; - this._eastSash.state = east ? SashState.Enabled : SashState.Disabled; - this._southSash.state = south ? SashState.Enabled : SashState.Disabled; - this._westSash.state = west ? SashState.Enabled : SashState.Disabled; - } - - layout(height: number = this.size.height, width: number = this.size.width): void { - - const { height: minHeight, width: minWidth } = this._minSize; - const { height: maxHeight, width: maxWidth } = this._maxSize; - - height = Math.max(minHeight, Math.min(maxHeight, height)); - width = Math.max(minWidth, Math.min(maxWidth, width)); - - const newSize = new Dimension(width, height); - if (!Dimension.equals(newSize, this._size)) { - this.domNode.style.height = height + 'px'; - this.domNode.style.width = width + 'px'; - this._size = newSize; - this._northSash.layout(); - this._eastSash.layout(); - this._southSash.layout(); - this._westSash.layout(); - } - } - - clearSashHoverState(): void { - this._eastSash.clearSashHoverState(); - this._westSash.clearSashHoverState(); - this._northSash.clearSashHoverState(); - this._southSash.clearSashHoverState(); - } - - get size() { - return this._size; - } - - set maxSize(value: Dimension) { - this._maxSize = value; - } - - get maxSize() { - return this._maxSize; - } - - set minSize(value: Dimension) { - this._minSize = value; - } - - get minSize() { - return this._minSize; - } - - set preferredSize(value: Dimension | undefined) { - this._preferredSize = value; - } - - get preferredSize() { - return this._preferredSize; - } -} diff --git a/src/vs/base/browser/ui/sash/sash.css b/src/vs/base/browser/ui/sash/sash.css deleted file mode 100644 index 42b0f425..00000000 --- a/src/vs/base/browser/ui/sash/sash.css +++ /dev/null @@ -1,149 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -:root { - --vscode-sash-size: 4px; - --vscode-sash-hover-size: 4px; -} - -.monaco-sash { - position: absolute; - z-index: 35; - touch-action: none; -} - -.monaco-sash.disabled { - pointer-events: none; -} - -.monaco-sash.mac.vertical { - cursor: col-resize; -} - -.monaco-sash.vertical.minimum { - cursor: e-resize; -} - -.monaco-sash.vertical.maximum { - cursor: w-resize; -} - -.monaco-sash.mac.horizontal { - cursor: row-resize; -} - -.monaco-sash.horizontal.minimum { - cursor: s-resize; -} - -.monaco-sash.horizontal.maximum { - cursor: n-resize; -} - -.monaco-sash.disabled { - cursor: default !important; - pointer-events: none !important; -} - -.monaco-sash.vertical { - cursor: ew-resize; - top: 0; - width: var(--vscode-sash-size); - height: 100%; -} - -.monaco-sash.horizontal { - cursor: ns-resize; - left: 0; - width: 100%; - height: var(--vscode-sash-size); -} - -.monaco-sash:not(.disabled) > .orthogonal-drag-handle { - content: " "; - height: calc(var(--vscode-sash-size) * 2); - width: calc(var(--vscode-sash-size) * 2); - z-index: 100; - display: block; - cursor: all-scroll; - position: absolute; -} - -.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled) - > .orthogonal-drag-handle.start, -.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled) - > .orthogonal-drag-handle.end { - cursor: nwse-resize; -} - -.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled) - > .orthogonal-drag-handle.end, -.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled) - > .orthogonal-drag-handle.start { - cursor: nesw-resize; -} - -.monaco-sash.vertical > .orthogonal-drag-handle.start { - left: calc(var(--vscode-sash-size) * -0.5); - top: calc(var(--vscode-sash-size) * -1); -} -.monaco-sash.vertical > .orthogonal-drag-handle.end { - left: calc(var(--vscode-sash-size) * -0.5); - bottom: calc(var(--vscode-sash-size) * -1); -} -.monaco-sash.horizontal > .orthogonal-drag-handle.start { - top: calc(var(--vscode-sash-size) * -0.5); - left: calc(var(--vscode-sash-size) * -1); -} -.monaco-sash.horizontal > .orthogonal-drag-handle.end { - top: calc(var(--vscode-sash-size) * -0.5); - right: calc(var(--vscode-sash-size) * -1); -} - -.monaco-sash:before { - content: ''; - pointer-events: none; - position: absolute; - width: 100%; - height: 100%; - background: transparent; -} - -.monaco-workbench:not(.reduce-motion) .monaco-sash:before { - transition: background-color 0.1s ease-out; -} - -.monaco-sash.hover:before, -.monaco-sash.active:before { - background: var(--vscode-sash-hoverBorder); -} - -.monaco-sash.vertical:before { - width: var(--vscode-sash-hover-size); - left: calc(50% - (var(--vscode-sash-hover-size) / 2)); -} - -.monaco-sash.horizontal:before { - height: var(--vscode-sash-hover-size); - top: calc(50% - (var(--vscode-sash-hover-size) / 2)); -} - -.pointer-events-disabled { - pointer-events: none !important; -} - -/** Debug **/ - -.monaco-sash.debug { - background: cyan; -} - -.monaco-sash.debug.disabled { - background: rgba(0, 255, 255, 0.2); -} - -.monaco-sash.debug:not(.disabled) > .orthogonal-drag-handle { - background: red; -} diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts deleted file mode 100644 index f4695109..00000000 --- a/src/vs/base/browser/ui/sash/sash.ts +++ /dev/null @@ -1,688 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { $, append, createStyleSheet, EventHelper, EventLike, getWindow, isHTMLElement } from 'vs/base/browser/dom'; -import { DomEmitter } from 'vs/base/browser/event'; -import { EventType, Gesture } from 'vs/base/browser/touch'; -import { Delayer } from 'vs/base/common/async'; -import { memoize } from 'vs/base/common/decorators'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -import { isMacintosh } from 'vs/base/common/platform'; -// import 'vs/css!./sash'; - -/** - * Allow the sashes to be visible at runtime. - * @remark Use for development purposes only. - */ -const DEBUG = false; -// DEBUG = Boolean("true"); // done "weirdly" so that a lint warning prevents you from pushing this - -/** - * A vertical sash layout provider provides position and height for a sash. - */ -export interface IVerticalSashLayoutProvider { - getVerticalSashLeft(sash: Sash): number; - getVerticalSashTop?(sash: Sash): number; - getVerticalSashHeight?(sash: Sash): number; -} - -/** - * A vertical sash layout provider provides position and width for a sash. - */ -export interface IHorizontalSashLayoutProvider { - getHorizontalSashTop(sash: Sash): number; - getHorizontalSashLeft?(sash: Sash): number; - getHorizontalSashWidth?(sash: Sash): number; -} - -type ISashLayoutProvider = IVerticalSashLayoutProvider | IHorizontalSashLayoutProvider; - -export interface ISashEvent { - readonly startX: number; - readonly currentX: number; - readonly startY: number; - readonly currentY: number; - readonly altKey: boolean; -} - -export enum OrthogonalEdge { - North = 'north', - South = 'south', - East = 'east', - West = 'west' -} - -export interface IBoundarySashes { - readonly top?: Sash; - readonly right?: Sash; - readonly bottom?: Sash; - readonly left?: Sash; -} - -export interface ISashOptions { - - /** - * Whether a sash is horizontal or vertical. - */ - readonly orientation: Orientation; - - /** - * The width or height of a vertical or horizontal sash, respectively. - */ - readonly size?: number; - - /** - * A reference to another sash, perpendicular to this one, which - * aligns at the start of this one. A corner sash will be created - * automatically at that location. - * - * The start of a horizontal sash is its left-most position. - * The start of a vertical sash is its top-most position. - */ - readonly orthogonalStartSash?: Sash; - - /** - * A reference to another sash, perpendicular to this one, which - * aligns at the end of this one. A corner sash will be created - * automatically at that location. - * - * The end of a horizontal sash is its right-most position. - * The end of a vertical sash is its bottom-most position. - */ - readonly orthogonalEndSash?: Sash; - - /** - * Provides a hint as to what mouse cursor to use whenever the user - * hovers over a corner sash provided by this and an orthogonal sash. - */ - readonly orthogonalEdge?: OrthogonalEdge; -} - -export interface IVerticalSashOptions extends ISashOptions { - readonly orientation: Orientation.VERTICAL; -} - -export interface IHorizontalSashOptions extends ISashOptions { - readonly orientation: Orientation.HORIZONTAL; -} - -export const enum Orientation { - VERTICAL, - HORIZONTAL -} - -export const enum SashState { - - /** - * Disable any UI interaction. - */ - Disabled, - - /** - * Allow dragging down or to the right, depending on the sash orientation. - * - * Some OSs allow customizing the mouse cursor differently whenever - * some resizable component can't be any smaller, but can be larger. - */ - AtMinimum, - - /** - * Allow dragging up or to the left, depending on the sash orientation. - * - * Some OSs allow customizing the mouse cursor differently whenever - * some resizable component can't be any larger, but can be smaller. - */ - AtMaximum, - - /** - * Enable dragging. - */ - Enabled -} - -let globalSize = 4; -const onDidChangeGlobalSize = new Emitter(); -export function setGlobalSashSize(size: number): void { - globalSize = size; - onDidChangeGlobalSize.fire(size); -} - -let globalHoverDelay = 300; -const onDidChangeHoverDelay = new Emitter(); -export function setGlobalHoverDelay(size: number): void { - globalHoverDelay = size; - onDidChangeHoverDelay.fire(size); -} - -interface PointerEvent extends EventLike { - readonly pageX: number; - readonly pageY: number; - readonly altKey: boolean; - readonly target: EventTarget | null; - readonly initialTarget?: EventTarget | undefined; -} - -interface IPointerEventFactory { - readonly onPointerMove: Event; - readonly onPointerUp: Event; - dispose(): void; -} - -class MouseEventFactory implements IPointerEventFactory { - - private readonly disposables = new DisposableStore(); - - constructor(private el: HTMLElement) { } - - @memoize - get onPointerMove(): Event { - return this.disposables.add(new DomEmitter(getWindow(this.el), 'mousemove')).event; - } - - @memoize - get onPointerUp(): Event { - return this.disposables.add(new DomEmitter(getWindow(this.el), 'mouseup')).event; - } - - dispose(): void { - this.disposables.dispose(); - } -} - -class GestureEventFactory implements IPointerEventFactory { - - private readonly disposables = new DisposableStore(); - - @memoize - get onPointerMove(): Event { - return this.disposables.add(new DomEmitter(this.el, EventType.Change)).event; - } - - @memoize - get onPointerUp(): Event { - return this.disposables.add(new DomEmitter(this.el, EventType.End)).event; - } - - constructor(private el: HTMLElement) { } - - dispose(): void { - this.disposables.dispose(); - } -} - -class OrthogonalPointerEventFactory implements IPointerEventFactory { - - @memoize - get onPointerMove(): Event { - return this.factory.onPointerMove; - } - - @memoize - get onPointerUp(): Event { - return this.factory.onPointerUp; - } - - constructor(private factory: IPointerEventFactory) { } - - dispose(): void { - // noop - } -} - -const PointerEventsDisabledCssClass = 'pointer-events-disabled'; - -/** - * The {@link Sash} is the UI component which allows the user to resize other - * components. It's usually an invisible horizontal or vertical line which, when - * hovered, becomes highlighted and can be dragged along the perpendicular dimension - * to its direction. - * - * Features: - * - Touch event handling - * - Corner sash support - * - Hover with different mouse cursor support - * - Configurable hover size - * - Linked sash support, for 2x2 corner sashes - */ -export class Sash extends Disposable { - - private el: HTMLElement; - private layoutProvider: ISashLayoutProvider; - private orientation: Orientation; - private size: number; - private hoverDelay = globalHoverDelay; - private hoverDelayer = this._register(new Delayer(this.hoverDelay)); - - private _state: SashState = SashState.Enabled; - private readonly onDidEnablementChange = this._register(new Emitter()); - private readonly _onDidStart = this._register(new Emitter()); - private readonly _onDidChange = this._register(new Emitter()); - private readonly _onDidReset = this._register(new Emitter()); - private readonly _onDidEnd = this._register(new Emitter()); - private readonly orthogonalStartSashDisposables = this._register(new DisposableStore()); - private _orthogonalStartSash: Sash | undefined; - private readonly orthogonalStartDragHandleDisposables = this._register(new DisposableStore()); - private _orthogonalStartDragHandle: HTMLElement | undefined; - private readonly orthogonalEndSashDisposables = this._register(new DisposableStore()); - private _orthogonalEndSash: Sash | undefined; - private readonly orthogonalEndDragHandleDisposables = this._register(new DisposableStore()); - private _orthogonalEndDragHandle: HTMLElement | undefined; - - get state(): SashState { return this._state; } - get orthogonalStartSash(): Sash | undefined { return this._orthogonalStartSash; } - get orthogonalEndSash(): Sash | undefined { return this._orthogonalEndSash; } - - /** - * The state of a sash defines whether it can be interacted with by the user - * as well as what mouse cursor to use, when hovered. - */ - set state(state: SashState) { - if (this._state === state) { - return; - } - - this.el.classList.toggle('disabled', state === SashState.Disabled); - this.el.classList.toggle('minimum', state === SashState.AtMinimum); - this.el.classList.toggle('maximum', state === SashState.AtMaximum); - - this._state = state; - this.onDidEnablementChange.fire(state); - } - - /** - * An event which fires whenever the user starts dragging this sash. - */ - readonly onDidStart: Event = this._onDidStart.event; - - /** - * An event which fires whenever the user moves the mouse while - * dragging this sash. - */ - readonly onDidChange: Event = this._onDidChange.event; - - /** - * An event which fires whenever the user double clicks this sash. - */ - readonly onDidReset: Event = this._onDidReset.event; - - /** - * An event which fires whenever the user stops dragging this sash. - */ - readonly onDidEnd: Event = this._onDidEnd.event; - - /** - * A linked sash will be forwarded the same user interactions and events - * so it moves exactly the same way as this sash. - * - * Useful in 2x2 grids. Not meant for widespread usage. - */ - linkedSash: Sash | undefined = undefined; - - /** - * A reference to another sash, perpendicular to this one, which - * aligns at the start of this one. A corner sash will be created - * automatically at that location. - * - * The start of a horizontal sash is its left-most position. - * The start of a vertical sash is its top-most position. - */ - set orthogonalStartSash(sash: Sash | undefined) { - if (this._orthogonalStartSash === sash) { - return; - } - - this.orthogonalStartDragHandleDisposables.clear(); - this.orthogonalStartSashDisposables.clear(); - - if (sash) { - const onChange = (state: SashState) => { - this.orthogonalStartDragHandleDisposables.clear(); - - if (state !== SashState.Disabled) { - this._orthogonalStartDragHandle = append(this.el, $('.orthogonal-drag-handle.start')); - this.orthogonalStartDragHandleDisposables.add(toDisposable(() => this._orthogonalStartDragHandle!.remove())); - this.orthogonalStartDragHandleDisposables.add(new DomEmitter(this._orthogonalStartDragHandle, 'mouseenter')).event - (() => Sash.onMouseEnter(sash), undefined, this.orthogonalStartDragHandleDisposables); - this.orthogonalStartDragHandleDisposables.add(new DomEmitter(this._orthogonalStartDragHandle, 'mouseleave')).event - (() => Sash.onMouseLeave(sash), undefined, this.orthogonalStartDragHandleDisposables); - } - }; - - this.orthogonalStartSashDisposables.add(sash.onDidEnablementChange.event(onChange, this)); - onChange(sash.state); - } - - this._orthogonalStartSash = sash; - } - - /** - * A reference to another sash, perpendicular to this one, which - * aligns at the end of this one. A corner sash will be created - * automatically at that location. - * - * The end of a horizontal sash is its right-most position. - * The end of a vertical sash is its bottom-most position. - */ - - set orthogonalEndSash(sash: Sash | undefined) { - if (this._orthogonalEndSash === sash) { - return; - } - - this.orthogonalEndDragHandleDisposables.clear(); - this.orthogonalEndSashDisposables.clear(); - - if (sash) { - const onChange = (state: SashState) => { - this.orthogonalEndDragHandleDisposables.clear(); - - if (state !== SashState.Disabled) { - this._orthogonalEndDragHandle = append(this.el, $('.orthogonal-drag-handle.end')); - this.orthogonalEndDragHandleDisposables.add(toDisposable(() => this._orthogonalEndDragHandle!.remove())); - this.orthogonalEndDragHandleDisposables.add(new DomEmitter(this._orthogonalEndDragHandle, 'mouseenter')).event - (() => Sash.onMouseEnter(sash), undefined, this.orthogonalEndDragHandleDisposables); - this.orthogonalEndDragHandleDisposables.add(new DomEmitter(this._orthogonalEndDragHandle, 'mouseleave')).event - (() => Sash.onMouseLeave(sash), undefined, this.orthogonalEndDragHandleDisposables); - } - }; - - this.orthogonalEndSashDisposables.add(sash.onDidEnablementChange.event(onChange, this)); - onChange(sash.state); - } - - this._orthogonalEndSash = sash; - } - - /** - * Create a new vertical sash. - * - * @param container A DOM node to append the sash to. - * @param verticalLayoutProvider A vertical layout provider. - * @param options The options. - */ - constructor(container: HTMLElement, verticalLayoutProvider: IVerticalSashLayoutProvider, options: IVerticalSashOptions); - - /** - * Create a new horizontal sash. - * - * @param container A DOM node to append the sash to. - * @param horizontalLayoutProvider A horizontal layout provider. - * @param options The options. - */ - constructor(container: HTMLElement, horizontalLayoutProvider: IHorizontalSashLayoutProvider, options: IHorizontalSashOptions); - constructor(container: HTMLElement, layoutProvider: ISashLayoutProvider, options: ISashOptions) { - super(); - - this.el = append(container, $('.monaco-sash')); - - if (options.orthogonalEdge) { - this.el.classList.add(`orthogonal-edge-${options.orthogonalEdge}`); - } - - if (isMacintosh) { - this.el.classList.add('mac'); - } - - const onMouseDown = this._register(new DomEmitter(this.el, 'mousedown')).event; - this._register(onMouseDown(e => this.onPointerStart(e, new MouseEventFactory(container)), this)); - const onMouseDoubleClick = this._register(new DomEmitter(this.el, 'dblclick')).event; - this._register(onMouseDoubleClick(this.onPointerDoublePress, this)); - const onMouseEnter = this._register(new DomEmitter(this.el, 'mouseenter')).event; - this._register(onMouseEnter(() => Sash.onMouseEnter(this))); - const onMouseLeave = this._register(new DomEmitter(this.el, 'mouseleave')).event; - this._register(onMouseLeave(() => Sash.onMouseLeave(this))); - - this._register(Gesture.addTarget(this.el)); - - const onTouchStart = this._register(new DomEmitter(this.el, EventType.Start)).event; - this._register(onTouchStart(e => this.onPointerStart(e, new GestureEventFactory(this.el)), this)); - const onTap = this._register(new DomEmitter(this.el, EventType.Tap)).event; - - let doubleTapTimeout: any = undefined; - this._register(onTap(event => { - if (doubleTapTimeout) { - clearTimeout(doubleTapTimeout); - doubleTapTimeout = undefined; - this.onPointerDoublePress(event); - return; - } - - clearTimeout(doubleTapTimeout); - doubleTapTimeout = setTimeout(() => doubleTapTimeout = undefined, 250); - }, this)); - - if (typeof options.size === 'number') { - this.size = options.size; - - if (options.orientation === Orientation.VERTICAL) { - this.el.style.width = `${this.size}px`; - } else { - this.el.style.height = `${this.size}px`; - } - } else { - this.size = globalSize; - this._register(onDidChangeGlobalSize.event(size => { - this.size = size; - this.layout(); - })); - } - - this._register(onDidChangeHoverDelay.event(delay => this.hoverDelay = delay)); - - this.layoutProvider = layoutProvider; - - this.orthogonalStartSash = options.orthogonalStartSash; - this.orthogonalEndSash = options.orthogonalEndSash; - - this.orientation = options.orientation || Orientation.VERTICAL; - - if (this.orientation === Orientation.HORIZONTAL) { - this.el.classList.add('horizontal'); - this.el.classList.remove('vertical'); - } else { - this.el.classList.remove('horizontal'); - this.el.classList.add('vertical'); - } - - this.el.classList.toggle('debug', DEBUG); - - this.layout(); - } - - private onPointerStart(event: PointerEvent, pointerEventFactory: IPointerEventFactory): void { - EventHelper.stop(event); - - let isMultisashResize = false; - - if (!(event as any).__orthogonalSashEvent) { - const orthogonalSash = this.getOrthogonalSash(event); - - if (orthogonalSash) { - isMultisashResize = true; - (event as any).__orthogonalSashEvent = true; - orthogonalSash.onPointerStart(event, new OrthogonalPointerEventFactory(pointerEventFactory)); - } - } - - if (this.linkedSash && !(event as any).__linkedSashEvent) { - (event as any).__linkedSashEvent = true; - this.linkedSash.onPointerStart(event, new OrthogonalPointerEventFactory(pointerEventFactory)); - } - - if (!this.state) { - return; - } - - const iframes = this.el.ownerDocument.getElementsByTagName('iframe'); - for (const iframe of iframes) { - iframe.classList.add(PointerEventsDisabledCssClass); // disable mouse events on iframes as long as we drag the sash - } - - const startX = event.pageX; - const startY = event.pageY; - const altKey = event.altKey; - const startEvent: ISashEvent = { startX, currentX: startX, startY, currentY: startY, altKey }; - - this.el.classList.add('active'); - this._onDidStart.fire(startEvent); - - // fix https://github.com/microsoft/vscode/issues/21675 - const style = createStyleSheet(this.el); - const updateStyle = () => { - let cursor = ''; - - if (isMultisashResize) { - cursor = 'all-scroll'; - } else if (this.orientation === Orientation.HORIZONTAL) { - if (this.state === SashState.AtMinimum) { - cursor = 's-resize'; - } else if (this.state === SashState.AtMaximum) { - cursor = 'n-resize'; - } else { - cursor = isMacintosh ? 'row-resize' : 'ns-resize'; - } - } else { - if (this.state === SashState.AtMinimum) { - cursor = 'e-resize'; - } else if (this.state === SashState.AtMaximum) { - cursor = 'w-resize'; - } else { - cursor = isMacintosh ? 'col-resize' : 'ew-resize'; - } - } - - style.textContent = `* { cursor: ${cursor} !important; }`; - }; - - const disposables = new DisposableStore(); - - updateStyle(); - - if (!isMultisashResize) { - this.onDidEnablementChange.event(updateStyle, null, disposables); - } - - const onPointerMove = (e: PointerEvent) => { - EventHelper.stop(e, false); - const event: ISashEvent = { startX, currentX: e.pageX, startY, currentY: e.pageY, altKey }; - - this._onDidChange.fire(event); - }; - - const onPointerUp = (e: PointerEvent) => { - EventHelper.stop(e, false); - - style.remove(); - - this.el.classList.remove('active'); - this._onDidEnd.fire(); - - disposables.dispose(); - - for (const iframe of iframes) { - iframe.classList.remove(PointerEventsDisabledCssClass); - } - }; - - pointerEventFactory.onPointerMove(onPointerMove, null, disposables); - pointerEventFactory.onPointerUp(onPointerUp, null, disposables); - disposables.add(pointerEventFactory); - } - - private onPointerDoublePress(e: MouseEvent): void { - const orthogonalSash = this.getOrthogonalSash(e); - - if (orthogonalSash) { - orthogonalSash._onDidReset.fire(); - } - - if (this.linkedSash) { - this.linkedSash._onDidReset.fire(); - } - - this._onDidReset.fire(); - } - - private static onMouseEnter(sash: Sash, fromLinkedSash: boolean = false): void { - if (sash.el.classList.contains('active')) { - sash.hoverDelayer.cancel(); - sash.el.classList.add('hover'); - } else { - sash.hoverDelayer.trigger(() => sash.el.classList.add('hover'), sash.hoverDelay).then(undefined, () => { }); - } - - if (!fromLinkedSash && sash.linkedSash) { - Sash.onMouseEnter(sash.linkedSash, true); - } - } - - private static onMouseLeave(sash: Sash, fromLinkedSash: boolean = false): void { - sash.hoverDelayer.cancel(); - sash.el.classList.remove('hover'); - - if (!fromLinkedSash && sash.linkedSash) { - Sash.onMouseLeave(sash.linkedSash, true); - } - } - - /** - * Forcefully stop any user interactions with this sash. - * Useful when hiding a parent component, while the user is still - * interacting with the sash. - */ - clearSashHoverState(): void { - Sash.onMouseLeave(this); - } - - /** - * Layout the sash. The sash will size and position itself - * based on its provided {@link ISashLayoutProvider layout provider}. - */ - layout(): void { - if (this.orientation === Orientation.VERTICAL) { - const verticalProvider = (this.layoutProvider); - this.el.style.left = verticalProvider.getVerticalSashLeft(this) - (this.size / 2) + 'px'; - - if (verticalProvider.getVerticalSashTop) { - this.el.style.top = verticalProvider.getVerticalSashTop(this) + 'px'; - } - - if (verticalProvider.getVerticalSashHeight) { - this.el.style.height = verticalProvider.getVerticalSashHeight(this) + 'px'; - } - } else { - const horizontalProvider = (this.layoutProvider); - this.el.style.top = horizontalProvider.getHorizontalSashTop(this) - (this.size / 2) + 'px'; - - if (horizontalProvider.getHorizontalSashLeft) { - this.el.style.left = horizontalProvider.getHorizontalSashLeft(this) + 'px'; - } - - if (horizontalProvider.getHorizontalSashWidth) { - this.el.style.width = horizontalProvider.getHorizontalSashWidth(this) + 'px'; - } - } - } - - private getOrthogonalSash(e: PointerEvent): Sash | undefined { - const target = e.initialTarget ?? e.target; - - if (!target || !(isHTMLElement(target))) { - return undefined; - } - - if (target.classList.contains('orthogonal-drag-handle')) { - return target.classList.contains('start') ? this.orthogonalStartSash : this.orthogonalEndSash; - } - - return undefined; - } - - override dispose(): void { - super.dispose(); - this.el.remove(); - } -} diff --git a/src/vs/base/browser/ui/splitview/paneview.css b/src/vs/base/browser/ui/splitview/paneview.css deleted file mode 100644 index 62cb3fe6..00000000 --- a/src/vs/base/browser/ui/splitview/paneview.css +++ /dev/null @@ -1,152 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-pane-view { - width: 100%; - height: 100%; -} - -.monaco-pane-view .pane { - overflow: hidden; - width: 100%; - height: 100%; - display: flex; - flex-direction: column; -} - -.monaco-pane-view .pane.horizontal:not(.expanded) { - flex-direction: row; -} - -.monaco-pane-view .pane > .pane-header { - height: 22px; - font-size: 11px; - font-weight: bold; - overflow: hidden; - display: flex; - cursor: pointer; - align-items: center; - box-sizing: border-box; -} - -.monaco-pane-view .pane > .pane-header.not-collapsible { - cursor: default; -} - -.monaco-pane-view .pane > .pane-header > .title { - text-transform: uppercase; -} - -.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header { - flex-direction: column; - height: 100%; - width: 22px; -} - -.monaco-pane-view .pane > .pane-header > .codicon:first-of-type { - margin: 0 2px; -} - -.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header > .codicon:first-of-type { - margin: 2px; -} - -/* TODO: actions should be part of the pane, but they aren't yet */ -.monaco-pane-view .pane > .pane-header > .actions { - display: none; - margin-left: auto; -} - -.monaco-pane-view .pane > .pane-header > .actions .action-item { - margin-right: 4px; -} - -.monaco-pane-view .pane > .pane-header > .actions .action-label { - padding: 2px; -} - -/* TODO: actions should be part of the pane, but they aren't yet */ -.monaco-pane-view .pane:hover > .pane-header.expanded > .actions, -.monaco-pane-view .pane:focus-within > .pane-header.expanded > .actions, -.monaco-pane-view .pane > .pane-header.actions-always-visible.expanded > .actions, -.monaco-pane-view .pane > .pane-header.focused.expanded > .actions { - display: initial; -} - -.monaco-pane-view .pane > .pane-header .monaco-action-bar .action-item.select-container { - cursor: default; -} - -.monaco-pane-view .pane > .pane-header .action-item .monaco-select-box { - cursor: pointer; - min-width: 110px; - min-height: 18px; - padding: 2px 23px 2px 8px; -} - -.linux .monaco-pane-view .pane > .pane-header .action-item .monaco-select-box, -.windows .monaco-pane-view .pane > .pane-header .action-item .monaco-select-box { - padding: 0px 23px 0px 8px; -} - -/* Bold font style does not go well with CJK fonts */ -.monaco-pane-view:lang(zh-Hans) .pane > .pane-header, -.monaco-pane-view:lang(zh-Hant) .pane > .pane-header, -.monaco-pane-view:lang(ja) .pane > .pane-header, -.monaco-pane-view:lang(ko) .pane > .pane-header { - font-weight: normal; -} - -.monaco-pane-view .pane > .pane-header.hidden { - display: none; -} - -.monaco-pane-view .pane > .pane-body { - overflow: hidden; - flex: 1; -} - -/* Animation */ - -.monaco-pane-view.animated .split-view-view { - transition-duration: 0.15s; - transition-timing-function: ease-out; -} - -.reduce-motion .monaco-pane-view .split-view-view { - transition-duration: 0s !important; -} - -.monaco-pane-view.animated.vertical .split-view-view { - transition-property: height; -} - -.monaco-pane-view.animated.horizontal .split-view-view { - transition-property: width; -} - -#monaco-pane-drop-overlay { - position: absolute; - z-index: 10000; - width: 100%; - height: 100%; - left: 0; - box-sizing: border-box; -} - -#monaco-pane-drop-overlay > .pane-overlay-indicator { - position: absolute; - width: 100%; - height: 100%; - min-height: 22px; - min-width: 19px; - - pointer-events: none; /* very important to not take events away from the parent */ - transition: opacity 150ms ease-out; -} - -#monaco-pane-drop-overlay > .pane-overlay-indicator.overlay-move-transition { - transition: top 70ms ease-out, left 70ms ease-out, width 70ms ease-out, height 70ms ease-out, opacity 150ms ease-out; -} diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts deleted file mode 100644 index 464bcdde..00000000 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ /dev/null @@ -1,681 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { isFirefox } from 'vs/base/browser/browser'; -import { DataTransfers } from 'vs/base/browser/dnd'; -import { $, addDisposableListener, append, clearNode, EventHelper, EventType, getWindow, isHTMLElement, trackFocus } from 'vs/base/browser/dom'; -import { DomEmitter } from 'vs/base/browser/event'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch'; -import { IBoundarySashes, Orientation } from 'vs/base/browser/ui/sash/sash'; -import { Color, RGBA } from 'vs/base/common/color'; -import { Emitter, Event } from 'vs/base/common/event'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { ScrollEvent } from 'vs/base/common/scrollable'; -// import 'vs/css!./paneview'; -import { localize } from 'vs/nls'; -import { IView, Sizing, SplitView } from './splitview'; - -export interface IPaneOptions { - minimumBodySize?: number; - maximumBodySize?: number; - expanded?: boolean; - orientation?: Orientation; - title: string; - titleDescription?: string; -} - -export interface IPaneStyles { - readonly dropBackground: string | undefined; - readonly headerForeground: string | undefined; - readonly headerBackground: string | undefined; - readonly headerBorder: string | undefined; - readonly leftBorder: string | undefined; -} - -/** - * A Pane is a structured SplitView view. - * - * WARNING: You must call `render()` after you construct it. - * It can't be done automatically at the end of the ctor - * because of the order of property initialization in TypeScript. - * Subclasses wouldn't be able to set own properties - * before the `render()` call, thus forbidding their use. - */ -export abstract class Pane extends Disposable implements IView { - - private static readonly HEADER_SIZE = 22; - - readonly element: HTMLElement; - private header!: HTMLElement; - private body!: HTMLElement; - - protected _expanded: boolean; - protected _orientation: Orientation; - - private expandedSize: number | undefined = undefined; - private _headerVisible = true; - private _collapsible = true; - private _bodyRendered = false; - private _minimumBodySize: number; - private _maximumBodySize: number; - private _ariaHeaderLabel: string; - private styles: IPaneStyles = { - dropBackground: undefined, - headerBackground: undefined, - headerBorder: undefined, - headerForeground: undefined, - leftBorder: undefined - }; - private animationTimer: number | undefined = undefined; - - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChange: Event = this._onDidChange.event; - - private readonly _onDidChangeExpansionState = this._register(new Emitter()); - readonly onDidChangeExpansionState: Event = this._onDidChangeExpansionState.event; - - get ariaHeaderLabel(): string { - return this._ariaHeaderLabel; - } - - set ariaHeaderLabel(newLabel: string) { - this._ariaHeaderLabel = newLabel; - this.header.setAttribute('aria-label', this.ariaHeaderLabel); - } - - get draggableElement(): HTMLElement { - return this.header; - } - - get dropTargetElement(): HTMLElement { - return this.element; - } - - get dropBackground(): string | undefined { - return this.styles.dropBackground; - } - - get minimumBodySize(): number { - return this._minimumBodySize; - } - - set minimumBodySize(size: number) { - this._minimumBodySize = size; - this._onDidChange.fire(undefined); - } - - get maximumBodySize(): number { - return this._maximumBodySize; - } - - set maximumBodySize(size: number) { - this._maximumBodySize = size; - this._onDidChange.fire(undefined); - } - - private get headerSize(): number { - return this.headerVisible ? Pane.HEADER_SIZE : 0; - } - - get minimumSize(): number { - const headerSize = this.headerSize; - const expanded = !this.headerVisible || this.isExpanded(); - const minimumBodySize = expanded ? this.minimumBodySize : 0; - - return headerSize + minimumBodySize; - } - - get maximumSize(): number { - const headerSize = this.headerSize; - const expanded = !this.headerVisible || this.isExpanded(); - const maximumBodySize = expanded ? this.maximumBodySize : 0; - - return headerSize + maximumBodySize; - } - - orthogonalSize: number = 0; - - constructor(options: IPaneOptions) { - super(); - this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded; - this._orientation = typeof options.orientation === 'undefined' ? Orientation.VERTICAL : options.orientation; - this._ariaHeaderLabel = localize('viewSection', "{0} Section", options.title); - this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : this._orientation === Orientation.HORIZONTAL ? 200 : 120; - this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY; - - this.element = $('.pane'); - } - - isExpanded(): boolean { - return this._expanded; - } - - setExpanded(expanded: boolean): boolean { - if (!expanded && !this.collapsible) { - return false; - } - - if (this._expanded === !!expanded) { - return false; - } - - this.element?.classList.toggle('expanded', expanded); - - this._expanded = !!expanded; - this.updateHeader(); - - if (expanded) { - if (!this._bodyRendered) { - this.renderBody(this.body); - this._bodyRendered = true; - } - - if (typeof this.animationTimer === 'number') { - getWindow(this.element).clearTimeout(this.animationTimer); - } - append(this.element, this.body); - } else { - this.animationTimer = getWindow(this.element).setTimeout(() => { - this.body.remove(); - }, 200); - } - - this._onDidChangeExpansionState.fire(expanded); - this._onDidChange.fire(expanded ? this.expandedSize : undefined); - return true; - } - - get headerVisible(): boolean { - return this._headerVisible; - } - - set headerVisible(visible: boolean) { - if (this._headerVisible === !!visible) { - return; - } - - this._headerVisible = !!visible; - this.updateHeader(); - this._onDidChange.fire(undefined); - } - - get collapsible(): boolean { - return this._collapsible; - } - - set collapsible(collapsible: boolean) { - if (this._collapsible === !!collapsible) { - return; - } - - this._collapsible = !!collapsible; - this.updateHeader(); - } - - get orientation(): Orientation { - return this._orientation; - } - - set orientation(orientation: Orientation) { - if (this._orientation === orientation) { - return; - } - - this._orientation = orientation; - - if (this.element) { - this.element.classList.toggle('horizontal', this.orientation === Orientation.HORIZONTAL); - this.element.classList.toggle('vertical', this.orientation === Orientation.VERTICAL); - } - - if (this.header) { - this.updateHeader(); - } - } - - render(): void { - this.element.classList.toggle('expanded', this.isExpanded()); - this.element.classList.toggle('horizontal', this.orientation === Orientation.HORIZONTAL); - this.element.classList.toggle('vertical', this.orientation === Orientation.VERTICAL); - - this.header = $('.pane-header'); - append(this.element, this.header); - this.header.setAttribute('tabindex', '0'); - // Use role button so the aria-expanded state gets read https://github.com/microsoft/vscode/issues/95996 - this.header.setAttribute('role', 'button'); - this.header.setAttribute('aria-label', this.ariaHeaderLabel); - this.renderHeader(this.header); - - const focusTracker = trackFocus(this.header); - this._register(focusTracker); - this._register(focusTracker.onDidFocus(() => this.header.classList.add('focused'), null)); - this._register(focusTracker.onDidBlur(() => this.header.classList.remove('focused'), null)); - - this.updateHeader(); - - const eventDisposables = this._register(new DisposableStore()); - const onKeyDown = this._register(new DomEmitter(this.header, 'keydown')); - const onHeaderKeyDown = Event.map(onKeyDown.event, e => new StandardKeyboardEvent(e), eventDisposables); - - this._register(Event.filter(onHeaderKeyDown, e => e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space, eventDisposables)(() => this.setExpanded(!this.isExpanded()), null)); - - this._register(Event.filter(onHeaderKeyDown, e => e.keyCode === KeyCode.LeftArrow, eventDisposables)(() => this.setExpanded(false), null)); - - this._register(Event.filter(onHeaderKeyDown, e => e.keyCode === KeyCode.RightArrow, eventDisposables)(() => this.setExpanded(true), null)); - - this._register(Gesture.addTarget(this.header)); - - [EventType.CLICK, TouchEventType.Tap].forEach(eventType => { - this._register(addDisposableListener(this.header, eventType, e => { - if (!e.defaultPrevented) { - this.setExpanded(!this.isExpanded()); - } - })); - }); - - this.body = append(this.element, $('.pane-body')); - - // Only render the body if it will be visible - // Otherwise, render it when the pane is expanded - if (!this._bodyRendered && this.isExpanded()) { - this.renderBody(this.body); - this._bodyRendered = true; - } - - if (!this.isExpanded()) { - this.body.remove(); - } - } - - layout(size: number): void { - const headerSize = this.headerVisible ? Pane.HEADER_SIZE : 0; - - const width = this._orientation === Orientation.VERTICAL ? this.orthogonalSize : size; - const height = this._orientation === Orientation.VERTICAL ? size - headerSize : this.orthogonalSize - headerSize; - - if (this.isExpanded()) { - this.body.classList.toggle('wide', width >= 600); - this.layoutBody(height, width); - this.expandedSize = size; - } - } - - style(styles: IPaneStyles): void { - this.styles = styles; - - if (!this.header) { - return; - } - - this.updateHeader(); - } - - protected updateHeader(): void { - const expanded = !this.headerVisible || this.isExpanded(); - - if (this.collapsible) { - this.header.setAttribute('tabindex', '0'); - this.header.setAttribute('role', 'button'); - } else { - this.header.removeAttribute('tabindex'); - this.header.removeAttribute('role'); - } - - this.header.style.lineHeight = `${this.headerSize}px`; - this.header.classList.toggle('hidden', !this.headerVisible); - this.header.classList.toggle('expanded', expanded); - this.header.classList.toggle('not-collapsible', !this.collapsible); - this.header.setAttribute('aria-expanded', String(expanded)); - - this.header.style.color = this.collapsible ? this.styles.headerForeground ?? '' : ''; - this.header.style.backgroundColor = (this.collapsible ? this.styles.headerBackground : 'transparent') ?? ''; - this.header.style.borderTop = this.styles.headerBorder && this.orientation === Orientation.VERTICAL ? `1px solid ${this.styles.headerBorder}` : ''; - this.element.style.borderLeft = this.styles.leftBorder && this.orientation === Orientation.HORIZONTAL ? `1px solid ${this.styles.leftBorder}` : ''; - } - - protected abstract renderHeader(container: HTMLElement): void; - protected abstract renderBody(container: HTMLElement): void; - protected abstract layoutBody(height: number, width: number): void; -} - -interface IDndContext { - draggable: PaneDraggable | null; -} - -class PaneDraggable extends Disposable { - - private static readonly DefaultDragOverBackgroundColor = new Color(new RGBA(128, 128, 128, 0.5)); - - private dragOverCounter = 0; // see https://github.com/microsoft/vscode/issues/14470 - - private _onDidDrop = this._register(new Emitter<{ from: Pane; to: Pane }>()); - readonly onDidDrop = this._onDidDrop.event; - - constructor(private pane: Pane, private dnd: IPaneDndController, private context: IDndContext) { - super(); - - pane.draggableElement.draggable = true; - this._register(addDisposableListener(pane.draggableElement, 'dragstart', e => this.onDragStart(e))); - this._register(addDisposableListener(pane.dropTargetElement, 'dragenter', e => this.onDragEnter(e))); - this._register(addDisposableListener(pane.dropTargetElement, 'dragleave', e => this.onDragLeave(e))); - this._register(addDisposableListener(pane.dropTargetElement, 'dragend', e => this.onDragEnd(e))); - this._register(addDisposableListener(pane.dropTargetElement, 'drop', e => this.onDrop(e))); - } - - private onDragStart(e: DragEvent): void { - if (!this.dnd.canDrag(this.pane) || !e.dataTransfer) { - e.preventDefault(); - e.stopPropagation(); - return; - } - - e.dataTransfer.effectAllowed = 'move'; - - if (isFirefox) { - // Firefox: requires to set a text data transfer to get going - e.dataTransfer?.setData(DataTransfers.TEXT, this.pane.draggableElement.textContent || ''); - } - - const dragImage = append(this.pane.element.ownerDocument.body, $('.monaco-drag-image', {}, this.pane.draggableElement.textContent || '')); - e.dataTransfer.setDragImage(dragImage, -10, -10); - setTimeout(() => dragImage.remove(), 0); - - this.context.draggable = this; - } - - private onDragEnter(e: DragEvent): void { - if (!this.context.draggable || this.context.draggable === this) { - return; - } - - if (!this.dnd.canDrop(this.context.draggable.pane, this.pane)) { - return; - } - - this.dragOverCounter++; - this.render(); - } - - private onDragLeave(e: DragEvent): void { - if (!this.context.draggable || this.context.draggable === this) { - return; - } - - if (!this.dnd.canDrop(this.context.draggable.pane, this.pane)) { - return; - } - - this.dragOverCounter--; - - if (this.dragOverCounter === 0) { - this.render(); - } - } - - private onDragEnd(e: DragEvent): void { - if (!this.context.draggable) { - return; - } - - this.dragOverCounter = 0; - this.render(); - this.context.draggable = null; - } - - private onDrop(e: DragEvent): void { - if (!this.context.draggable) { - return; - } - - EventHelper.stop(e); - - this.dragOverCounter = 0; - this.render(); - - if (this.dnd.canDrop(this.context.draggable.pane, this.pane) && this.context.draggable !== this) { - this._onDidDrop.fire({ from: this.context.draggable.pane, to: this.pane }); - } - - this.context.draggable = null; - } - - private render(): void { - let backgroundColor: string | null = null; - - if (this.dragOverCounter > 0) { - backgroundColor = this.pane.dropBackground ?? PaneDraggable.DefaultDragOverBackgroundColor.toString(); - } - - this.pane.dropTargetElement.style.backgroundColor = backgroundColor || ''; - } -} - -export interface IPaneDndController { - canDrag(pane: Pane): boolean; - canDrop(pane: Pane, overPane: Pane): boolean; -} - -export class DefaultPaneDndController implements IPaneDndController { - - canDrag(pane: Pane): boolean { - return true; - } - - canDrop(pane: Pane, overPane: Pane): boolean { - return true; - } -} - -export interface IPaneViewOptions { - dnd?: IPaneDndController; - orientation?: Orientation; -} - -interface IPaneItem { - pane: Pane; - disposable: IDisposable; -} - -export class PaneView extends Disposable { - - private dnd: IPaneDndController | undefined; - private dndContext: IDndContext = { draggable: null }; - readonly element: HTMLElement; - private paneItems: IPaneItem[] = []; - private orthogonalSize: number = 0; - private size: number = 0; - private splitview: SplitView; - private animationTimer: number | undefined = undefined; - - private _onDidDrop = this._register(new Emitter<{ from: Pane; to: Pane }>()); - readonly onDidDrop: Event<{ from: Pane; to: Pane }> = this._onDidDrop.event; - - orientation: Orientation; - private boundarySashes: IBoundarySashes | undefined; - readonly onDidSashChange: Event; - readonly onDidSashReset: Event; - readonly onDidScroll: Event; - - constructor(container: HTMLElement, options: IPaneViewOptions = {}) { - super(); - - this.dnd = options.dnd; - this.orientation = options.orientation ?? Orientation.VERTICAL; - this.element = append(container, $('.monaco-pane-view')); - this.splitview = this._register(new SplitView(this.element, { orientation: this.orientation })); - this.onDidSashReset = this.splitview.onDidSashReset; - this.onDidSashChange = this.splitview.onDidSashChange; - this.onDidScroll = this.splitview.onDidScroll; - - const eventDisposables = this._register(new DisposableStore()); - const onKeyDown = this._register(new DomEmitter(this.element, 'keydown')); - const onHeaderKeyDown = Event.map(Event.filter(onKeyDown.event, e => isHTMLElement(e.target) && e.target.classList.contains('pane-header'), eventDisposables), e => new StandardKeyboardEvent(e), eventDisposables); - - this._register(Event.filter(onHeaderKeyDown, e => e.keyCode === KeyCode.UpArrow, eventDisposables)(() => this.focusPrevious())); - this._register(Event.filter(onHeaderKeyDown, e => e.keyCode === KeyCode.DownArrow, eventDisposables)(() => this.focusNext())); - } - - addPane(pane: Pane, size: number, index = this.splitview.length): void { - const disposables = new DisposableStore(); - pane.onDidChangeExpansionState(this.setupAnimation, this, disposables); - - const paneItem = { pane: pane, disposable: disposables }; - this.paneItems.splice(index, 0, paneItem); - pane.orientation = this.orientation; - pane.orthogonalSize = this.orthogonalSize; - this.splitview.addView(pane, size, index); - - if (this.dnd) { - const draggable = new PaneDraggable(pane, this.dnd, this.dndContext); - disposables.add(draggable); - disposables.add(draggable.onDidDrop(this._onDidDrop.fire, this._onDidDrop)); - } - } - - removePane(pane: Pane): void { - const index = this.paneItems.findIndex(item => item.pane === pane); - - if (index === -1) { - return; - } - - this.splitview.removeView(index, pane.isExpanded() ? Sizing.Distribute : undefined); - const paneItem = this.paneItems.splice(index, 1)[0]; - paneItem.disposable.dispose(); - } - - movePane(from: Pane, to: Pane): void { - const fromIndex = this.paneItems.findIndex(item => item.pane === from); - const toIndex = this.paneItems.findIndex(item => item.pane === to); - - if (fromIndex === -1 || toIndex === -1) { - return; - } - - const [paneItem] = this.paneItems.splice(fromIndex, 1); - this.paneItems.splice(toIndex, 0, paneItem); - - this.splitview.moveView(fromIndex, toIndex); - } - - resizePane(pane: Pane, size: number): void { - const index = this.paneItems.findIndex(item => item.pane === pane); - - if (index === -1) { - return; - } - - this.splitview.resizeView(index, size); - } - - getPaneSize(pane: Pane): number { - const index = this.paneItems.findIndex(item => item.pane === pane); - - if (index === -1) { - return -1; - } - - return this.splitview.getViewSize(index); - } - - layout(height: number, width: number): void { - this.orthogonalSize = this.orientation === Orientation.VERTICAL ? width : height; - this.size = this.orientation === Orientation.HORIZONTAL ? width : height; - - for (const paneItem of this.paneItems) { - paneItem.pane.orthogonalSize = this.orthogonalSize; - } - - this.splitview.layout(this.size); - } - - setBoundarySashes(sashes: IBoundarySashes) { - this.boundarySashes = sashes; - this.updateSplitviewOrthogonalSashes(sashes); - } - - private updateSplitviewOrthogonalSashes(sashes: IBoundarySashes | undefined) { - if (this.orientation === Orientation.VERTICAL) { - this.splitview.orthogonalStartSash = sashes?.left; - this.splitview.orthogonalEndSash = sashes?.right; - } else { - this.splitview.orthogonalEndSash = sashes?.bottom; - } - } - - flipOrientation(height: number, width: number): void { - this.orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL; - const paneSizes = this.paneItems.map(pane => this.getPaneSize(pane.pane)); - - this.splitview.dispose(); - clearNode(this.element); - - this.splitview = this._register(new SplitView(this.element, { orientation: this.orientation })); - this.updateSplitviewOrthogonalSashes(this.boundarySashes); - - const newOrthogonalSize = this.orientation === Orientation.VERTICAL ? width : height; - const newSize = this.orientation === Orientation.HORIZONTAL ? width : height; - - this.paneItems.forEach((pane, index) => { - pane.pane.orthogonalSize = newOrthogonalSize; - pane.pane.orientation = this.orientation; - - const viewSize = this.size === 0 ? 0 : (newSize * paneSizes[index]) / this.size; - this.splitview.addView(pane.pane, viewSize, index); - }); - - this.size = newSize; - this.orthogonalSize = newOrthogonalSize; - - this.splitview.layout(this.size); - } - - private setupAnimation(): void { - if (typeof this.animationTimer === 'number') { - getWindow(this.element).clearTimeout(this.animationTimer); - } - - this.element.classList.add('animated'); - - this.animationTimer = getWindow(this.element).setTimeout(() => { - this.animationTimer = undefined; - this.element.classList.remove('animated'); - }, 200); - } - - private getPaneHeaderElements(): HTMLElement[] { - return [...this.element.querySelectorAll('.pane-header')] as HTMLElement[]; - } - - private focusPrevious(): void { - const headers = this.getPaneHeaderElements(); - const index = headers.indexOf(this.element.ownerDocument.activeElement as HTMLElement); - - if (index === -1) { - return; - } - - headers[Math.max(index - 1, 0)].focus(); - } - - private focusNext(): void { - const headers = this.getPaneHeaderElements(); - const index = headers.indexOf(this.element.ownerDocument.activeElement as HTMLElement); - - if (index === -1) { - return; - } - - headers[Math.min(index + 1, headers.length - 1)].focus(); - } - - override dispose(): void { - super.dispose(); - - this.paneItems.forEach(i => i.disposable.dispose()); - } -} diff --git a/src/vs/base/browser/ui/splitview/splitview.css b/src/vs/base/browser/ui/splitview/splitview.css deleted file mode 100644 index 3af3e906..00000000 --- a/src/vs/base/browser/ui/splitview/splitview.css +++ /dev/null @@ -1,70 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-split-view2 { - position: relative; - width: 100%; - height: 100%; -} - -.monaco-split-view2 > .sash-container { - position: absolute; - width: 100%; - height: 100%; - pointer-events: none; -} - -.monaco-split-view2 > .sash-container > .monaco-sash { - pointer-events: initial; -} - -.monaco-split-view2 > .monaco-scrollable-element { - width: 100%; - height: 100%; -} - -.monaco-split-view2 > .monaco-scrollable-element > .split-view-container { - width: 100%; - height: 100%; - white-space: nowrap; - position: relative; -} - -.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view { - white-space: initial; - position: absolute; -} - -.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view:not(.visible) { - display: none; -} - -.monaco-split-view2.vertical > .monaco-scrollable-element > .split-view-container > .split-view-view { - width: 100%; -} - -.monaco-split-view2.horizontal > .monaco-scrollable-element > .split-view-container > .split-view-view { - height: 100%; -} - -.monaco-split-view2.separator-border > .monaco-scrollable-element > .split-view-container > .split-view-view:not(:first-child)::before { - content: ' '; - position: absolute; - top: 0; - left: 0; - z-index: 5; - pointer-events: none; - background-color: var(--separator-border); -} - -.monaco-split-view2.separator-border.horizontal > .monaco-scrollable-element > .split-view-container > .split-view-view:not(:first-child)::before { - height: 100%; - width: 1px; -} - -.monaco-split-view2.separator-border.vertical > .monaco-scrollable-element > .split-view-container > .split-view-view:not(:first-child)::before { - height: 1px; - width: 100%; -} diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts deleted file mode 100644 index e07e76e7..00000000 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ /dev/null @@ -1,1504 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { $, addDisposableListener, append, getWindow, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; -import { DomEmitter } from 'vs/base/browser/event'; -import { ISashEvent as IBaseSashEvent, Orientation, Sash, SashState } from 'vs/base/browser/ui/sash/sash'; -import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; -import { pushToEnd, pushToStart, range } from 'vs/base/common/arrays'; -import { Color } from 'vs/base/common/color'; -import { Emitter, Event } from 'vs/base/common/event'; -import { combinedDisposable, Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { clamp } from 'vs/base/common/numbers'; -import { Scrollable, ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; -import * as types from 'vs/base/common/types'; -// import 'vs/css!./splitview'; -export { Orientation } from 'vs/base/browser/ui/sash/sash'; - -export interface ISplitViewStyles { - readonly separatorBorder: Color; -} - -const defaultStyles: ISplitViewStyles = { - separatorBorder: Color.transparent -}; - -export const enum LayoutPriority { - Normal, - Low, - High -} - -/** - * The interface to implement for views within a {@link SplitView}. - * - * An optional {@link TLayoutContext layout context type} may be used in order to - * pass along layout contextual data from the {@link SplitView.layout} method down - * to each view's {@link IView.layout} calls. - */ -export interface IView { - - /** - * The DOM element for this view. - */ - readonly element: HTMLElement; - - /** - * A minimum size for this view. - * - * @remarks If none, set it to `0`. - */ - readonly minimumSize: number; - - /** - * A maximum size for this view. - * - * @remarks If none, set it to `Number.POSITIVE_INFINITY`. - */ - readonly maximumSize: number; - - /** - * The priority of the view when the {@link SplitView.resize layout} algorithm - * runs. Views with higher priority will be resized first. - * - * @remarks Only used when `proportionalLayout` is false. - */ - readonly priority?: LayoutPriority; - - /** - * If the {@link SplitView} supports {@link ISplitViewOptions.proportionalLayout proportional layout}, - * this property allows for finer control over the proportional layout algorithm, per view. - * - * @defaultValue `true` - */ - readonly proportionalLayout?: boolean; - - /** - * Whether the view will snap whenever the user reaches its minimum size or - * attempts to grow it beyond the minimum size. - * - * @defaultValue `false` - */ - readonly snap?: boolean; - - /** - * View instances are supposed to fire the {@link IView.onDidChange} event whenever - * any of the constraint properties have changed: - * - * - {@link IView.minimumSize} - * - {@link IView.maximumSize} - * - {@link IView.priority} - * - {@link IView.snap} - * - * The SplitView will relayout whenever that happens. The event can optionally emit - * the view's preferred size for that relayout. - */ - readonly onDidChange: Event; - - /** - * This will be called by the {@link SplitView} during layout. A view meant to - * pass along the layout information down to its descendants. - * - * @param size The size of this view, in pixels. - * @param offset The offset of this view, relative to the start of the {@link SplitView}. - * @param context The optional {@link IView layout context} passed to {@link SplitView.layout}. - */ - layout(size: number, offset: number, context: TLayoutContext | undefined): void; - - /** - * This will be called by the {@link SplitView} whenever this view is made - * visible or hidden. - * - * @param visible Whether the view becomes visible. - */ - setVisible?(visible: boolean): void; -} - -/** - * A descriptor for a {@link SplitView} instance. - */ -export interface ISplitViewDescriptor = IView> { - - /** - * The layout size of the {@link SplitView}. - */ - readonly size: number; - - /** - * Descriptors for each {@link IView view}. - */ - readonly views: { - - /** - * Whether the {@link IView view} is visible. - * - * @defaultValue `true` - */ - readonly visible?: boolean; - - /** - * The size of the {@link IView view}. - * - * @defaultValue `true` - */ - readonly size: number; - - /** - * The size of the {@link IView view}. - * - * @defaultValue `true` - */ - readonly view: TView; - }[]; -} - -export interface ISplitViewOptions = IView> { - - /** - * Which axis the views align on. - * - * @defaultValue `Orientation.VERTICAL` - */ - readonly orientation?: Orientation; - - /** - * Styles overriding the {@link defaultStyles default ones}. - */ - readonly styles?: ISplitViewStyles; - - /** - * Make Alt-drag the default drag operation. - */ - readonly inverseAltBehavior?: boolean; - - /** - * Resize each view proportionally when resizing the SplitView. - * - * @defaultValue `true` - */ - readonly proportionalLayout?: boolean; - - /** - * An initial description of this {@link SplitView} instance, allowing - * to initialze all views within the ctor. - */ - readonly descriptor?: ISplitViewDescriptor; - - /** - * The scrollbar visibility setting for whenever the views within - * the {@link SplitView} overflow. - */ - readonly scrollbarVisibility?: ScrollbarVisibility; - - /** - * Override the orthogonal size of sashes. - */ - readonly getSashOrthogonalSize?: () => number; -} - -interface ISashEvent { - readonly sash: Sash; - readonly start: number; - readonly current: number; - readonly alt: boolean; -} - -type ViewItemSize = number | { cachedVisibleSize: number }; - -abstract class ViewItem> { - - private _size: number; - set size(size: number) { - this._size = size; - } - - get size(): number { - return this._size; - } - - private _cachedVisibleSize: number | undefined = undefined; - get cachedVisibleSize(): number | undefined { return this._cachedVisibleSize; } - - get visible(): boolean { - return typeof this._cachedVisibleSize === 'undefined'; - } - - setVisible(visible: boolean, size?: number): void { - if (visible === this.visible) { - return; - } - - if (visible) { - this.size = clamp(this._cachedVisibleSize!, this.viewMinimumSize, this.viewMaximumSize); - this._cachedVisibleSize = undefined; - } else { - this._cachedVisibleSize = typeof size === 'number' ? size : this.size; - this.size = 0; - } - - this.container.classList.toggle('visible', visible); - - try { - this.view.setVisible?.(visible); - } catch (e) { - console.error('Splitview: Failed to set visible view'); - console.error(e); - } - } - - get minimumSize(): number { return this.visible ? this.view.minimumSize : 0; } - get viewMinimumSize(): number { return this.view.minimumSize; } - - get maximumSize(): number { return this.visible ? this.view.maximumSize : 0; } - get viewMaximumSize(): number { return this.view.maximumSize; } - - get priority(): LayoutPriority | undefined { return this.view.priority; } - get proportionalLayout(): boolean { return this.view.proportionalLayout ?? true; } - get snap(): boolean { return !!this.view.snap; } - - set enabled(enabled: boolean) { - this.container.style.pointerEvents = enabled ? '' : 'none'; - } - - constructor( - protected container: HTMLElement, - readonly view: TView, - size: ViewItemSize, - private disposable: IDisposable - ) { - if (typeof size === 'number') { - this._size = size; - this._cachedVisibleSize = undefined; - container.classList.add('visible'); - } else { - this._size = 0; - this._cachedVisibleSize = size.cachedVisibleSize; - } - } - - layout(offset: number, layoutContext: TLayoutContext | undefined): void { - this.layoutContainer(offset); - - try { - this.view.layout(this.size, offset, layoutContext); - } catch (e) { - console.error('Splitview: Failed to layout view'); - console.error(e); - } - } - - abstract layoutContainer(offset: number): void; - - dispose(): void { - this.disposable.dispose(); - } -} - -class VerticalViewItem> extends ViewItem { - - layoutContainer(offset: number): void { - this.container.style.top = `${offset}px`; - this.container.style.height = `${this.size}px`; - } -} - -class HorizontalViewItem> extends ViewItem { - - layoutContainer(offset: number): void { - this.container.style.left = `${offset}px`; - this.container.style.width = `${this.size}px`; - } -} - -interface ISashItem { - sash: Sash; - disposable: IDisposable; -} - -interface ISashDragSnapState { - readonly index: number; - readonly limitDelta: number; - readonly size: number; -} - -interface ISashDragState { - index: number; - start: number; - current: number; - sizes: number[]; - minDelta: number; - maxDelta: number; - alt: boolean; - snapBefore: ISashDragSnapState | undefined; - snapAfter: ISashDragSnapState | undefined; - disposable: IDisposable; -} - -enum State { - Idle, - Busy -} - -/** - * When adding or removing views, uniformly distribute the entire split view space among - * all views. - */ -export type DistributeSizing = { type: 'distribute' }; - -/** - * When adding a view, make space for it by reducing the size of another view, - * indexed by the provided `index`. - */ -export type SplitSizing = { type: 'split'; index: number }; - -/** - * When adding a view, use DistributeSizing when all pre-existing views are - * distributed evenly, otherwise use SplitSizing. - */ -export type AutoSizing = { type: 'auto'; index: number }; - -/** - * When adding or removing views, assume the view is invisible. - */ -export type InvisibleSizing = { type: 'invisible'; cachedVisibleSize: number }; - -/** - * When adding or removing views, the sizing provides fine grained - * control over how other views get resized. - */ -export type Sizing = DistributeSizing | SplitSizing | AutoSizing | InvisibleSizing; - -export namespace Sizing { - - /** - * When adding or removing views, distribute the delta space among - * all other views. - */ - export const Distribute: DistributeSizing = { type: 'distribute' }; - - /** - * When adding or removing views, split the delta space with another - * specific view, indexed by the provided `index`. - */ - export function Split(index: number): SplitSizing { return { type: 'split', index }; } - - /** - * When adding a view, use DistributeSizing when all pre-existing views are - * distributed evenly, otherwise use SplitSizing. - */ - export function Auto(index: number): AutoSizing { return { type: 'auto', index }; } - - /** - * When adding or removing views, assume the view is invisible. - */ - export function Invisible(cachedVisibleSize: number): InvisibleSizing { return { type: 'invisible', cachedVisibleSize }; } -} - -/** - * The {@link SplitView} is the UI component which implements a one dimensional - * flex-like layout algorithm for a collection of {@link IView} instances, which - * are essentially HTMLElement instances with the following size constraints: - * - * - {@link IView.minimumSize} - * - {@link IView.maximumSize} - * - {@link IView.priority} - * - {@link IView.snap} - * - * In case the SplitView doesn't have enough size to fit all views, it will overflow - * its content with a scrollbar. - * - * In between each pair of views there will be a {@link Sash} allowing the user - * to resize the views, making sure the constraints are respected. - * - * An optional {@link TLayoutContext layout context type} may be used in order to - * pass along layout contextual data from the {@link SplitView.layout} method down - * to each view's {@link IView.layout} calls. - * - * Features: - * - Flex-like layout algorithm - * - Snap support - * - Orthogonal sash support, for corner sashes - * - View hide/show support - * - View swap/move support - * - Alt key modifier behavior, macOS style - */ -export class SplitView = IView> extends Disposable { - - /** - * This {@link SplitView}'s orientation. - */ - readonly orientation: Orientation; - - /** - * The DOM element representing this {@link SplitView}. - */ - readonly el: HTMLElement; - - private sashContainer: HTMLElement; - private viewContainer: HTMLElement; - private scrollable: Scrollable; - private scrollableElement: SmoothScrollableElement; - private size = 0; - private layoutContext: TLayoutContext | undefined; - private _contentSize = 0; - private proportions: (number | undefined)[] | undefined = undefined; - private viewItems: ViewItem[] = []; - sashItems: ISashItem[] = []; // used in tests - private sashDragState: ISashDragState | undefined; - private state: State = State.Idle; - private inverseAltBehavior: boolean; - private proportionalLayout: boolean; - private readonly getSashOrthogonalSize: { (): number } | undefined; - - private _onDidSashChange = this._register(new Emitter()); - private _onDidSashReset = this._register(new Emitter()); - private _orthogonalStartSash: Sash | undefined; - private _orthogonalEndSash: Sash | undefined; - private _startSnappingEnabled = true; - private _endSnappingEnabled = true; - - /** - * The sum of all views' sizes. - */ - get contentSize(): number { return this._contentSize; } - - /** - * Fires whenever the user resizes a {@link Sash sash}. - */ - readonly onDidSashChange = this._onDidSashChange.event; - - /** - * Fires whenever the user double clicks a {@link Sash sash}. - */ - readonly onDidSashReset = this._onDidSashReset.event; - - /** - * Fires whenever the split view is scrolled. - */ - readonly onDidScroll: Event; - - /** - * The amount of views in this {@link SplitView}. - */ - get length(): number { - return this.viewItems.length; - } - - /** - * The minimum size of this {@link SplitView}. - */ - get minimumSize(): number { - return this.viewItems.reduce((r, item) => r + item.minimumSize, 0); - } - - /** - * The maximum size of this {@link SplitView}. - */ - get maximumSize(): number { - return this.length === 0 ? Number.POSITIVE_INFINITY : this.viewItems.reduce((r, item) => r + item.maximumSize, 0); - } - - get orthogonalStartSash(): Sash | undefined { return this._orthogonalStartSash; } - get orthogonalEndSash(): Sash | undefined { return this._orthogonalEndSash; } - get startSnappingEnabled(): boolean { return this._startSnappingEnabled; } - get endSnappingEnabled(): boolean { return this._endSnappingEnabled; } - - /** - * A reference to a sash, perpendicular to all sashes in this {@link SplitView}, - * located at the left- or top-most side of the SplitView. - * Corner sashes will be created automatically at the intersections. - */ - set orthogonalStartSash(sash: Sash | undefined) { - for (const sashItem of this.sashItems) { - sashItem.sash.orthogonalStartSash = sash; - } - - this._orthogonalStartSash = sash; - } - - /** - * A reference to a sash, perpendicular to all sashes in this {@link SplitView}, - * located at the right- or bottom-most side of the SplitView. - * Corner sashes will be created automatically at the intersections. - */ - set orthogonalEndSash(sash: Sash | undefined) { - for (const sashItem of this.sashItems) { - sashItem.sash.orthogonalEndSash = sash; - } - - this._orthogonalEndSash = sash; - } - - /** - * The internal sashes within this {@link SplitView}. - */ - get sashes(): readonly Sash[] { - return this.sashItems.map(s => s.sash); - } - - /** - * Enable/disable snapping at the beginning of this {@link SplitView}. - */ - set startSnappingEnabled(startSnappingEnabled: boolean) { - if (this._startSnappingEnabled === startSnappingEnabled) { - return; - } - - this._startSnappingEnabled = startSnappingEnabled; - this.updateSashEnablement(); - } - - /** - * Enable/disable snapping at the end of this {@link SplitView}. - */ - set endSnappingEnabled(endSnappingEnabled: boolean) { - if (this._endSnappingEnabled === endSnappingEnabled) { - return; - } - - this._endSnappingEnabled = endSnappingEnabled; - this.updateSashEnablement(); - } - - /** - * Create a new {@link SplitView} instance. - */ - constructor(container: HTMLElement, options: ISplitViewOptions = {}) { - super(); - - this.orientation = options.orientation ?? Orientation.VERTICAL; - this.inverseAltBehavior = options.inverseAltBehavior ?? false; - this.proportionalLayout = options.proportionalLayout ?? true; - this.getSashOrthogonalSize = options.getSashOrthogonalSize; - - this.el = document.createElement('div'); - this.el.classList.add('monaco-split-view2'); - this.el.classList.add(this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal'); - container.appendChild(this.el); - - this.sashContainer = append(this.el, $('.sash-container')); - this.viewContainer = $('.split-view-container'); - - this.scrollable = this._register(new Scrollable({ - forceIntegerValues: true, - smoothScrollDuration: 125, - scheduleAtNextAnimationFrame: callback => scheduleAtNextAnimationFrame(getWindow(this.el), callback), - })); - this.scrollableElement = this._register(new SmoothScrollableElement(this.viewContainer, { - vertical: this.orientation === Orientation.VERTICAL ? (options.scrollbarVisibility ?? ScrollbarVisibility.Auto) : ScrollbarVisibility.Hidden, - horizontal: this.orientation === Orientation.HORIZONTAL ? (options.scrollbarVisibility ?? ScrollbarVisibility.Auto) : ScrollbarVisibility.Hidden - }, this.scrollable)); - - // https://github.com/microsoft/vscode/issues/157737 - const onDidScrollViewContainer = this._register(new DomEmitter(this.viewContainer, 'scroll')).event; - this._register(onDidScrollViewContainer(_ => { - const position = this.scrollableElement.getScrollPosition(); - const scrollLeft = Math.abs(this.viewContainer.scrollLeft - position.scrollLeft) <= 1 ? undefined : this.viewContainer.scrollLeft; - const scrollTop = Math.abs(this.viewContainer.scrollTop - position.scrollTop) <= 1 ? undefined : this.viewContainer.scrollTop; - - if (scrollLeft !== undefined || scrollTop !== undefined) { - this.scrollableElement.setScrollPosition({ scrollLeft, scrollTop }); - } - })); - - this.onDidScroll = this.scrollableElement.onScroll; - this._register(this.onDidScroll(e => { - if (e.scrollTopChanged) { - this.viewContainer.scrollTop = e.scrollTop; - } - - if (e.scrollLeftChanged) { - this.viewContainer.scrollLeft = e.scrollLeft; - } - })); - - append(this.el, this.scrollableElement.getDomNode()); - - this.style(options.styles || defaultStyles); - - // We have an existing set of view, add them now - if (options.descriptor) { - this.size = options.descriptor.size; - options.descriptor.views.forEach((viewDescriptor, index) => { - const sizing = types.isUndefined(viewDescriptor.visible) || viewDescriptor.visible ? viewDescriptor.size : { type: 'invisible', cachedVisibleSize: viewDescriptor.size } satisfies InvisibleSizing; - - const view = viewDescriptor.view; - this.doAddView(view, sizing, index, true); - }); - - // Initialize content size and proportions for first layout - this._contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); - this.saveProportions(); - } - } - - style(styles: ISplitViewStyles): void { - if (styles.separatorBorder.isTransparent()) { - this.el.classList.remove('separator-border'); - this.el.style.removeProperty('--separator-border'); - } else { - this.el.classList.add('separator-border'); - this.el.style.setProperty('--separator-border', styles.separatorBorder.toString()); - } - } - - /** - * Add a {@link IView view} to this {@link SplitView}. - * - * @param view The view to add. - * @param size Either a fixed size, or a dynamic {@link Sizing} strategy. - * @param index The index to insert the view on. - * @param skipLayout Whether layout should be skipped. - */ - addView(view: TView, size: number | Sizing, index = this.viewItems.length, skipLayout?: boolean): void { - this.doAddView(view, size, index, skipLayout); - } - - /** - * Remove a {@link IView view} from this {@link SplitView}. - * - * @param index The index where the {@link IView view} is located. - * @param sizing Whether to distribute other {@link IView view}'s sizes. - */ - removeView(index: number, sizing?: Sizing): TView { - if (index < 0 || index >= this.viewItems.length) { - throw new Error('Index out of bounds'); - } - - if (this.state !== State.Idle) { - throw new Error('Cant modify splitview'); - } - - this.state = State.Busy; - - try { - if (sizing?.type === 'auto') { - if (this.areViewsDistributed()) { - sizing = { type: 'distribute' }; - } else { - sizing = { type: 'split', index: sizing.index }; - } - } - - // Save referene view, in case of `split` sizing - const referenceViewItem = sizing?.type === 'split' ? this.viewItems[sizing.index] : undefined; - - // Remove view - const viewItemToRemove = this.viewItems.splice(index, 1)[0]; - - // Resize reference view, in case of `split` sizing - if (referenceViewItem) { - referenceViewItem.size += viewItemToRemove.size; - } - - // Remove sash - if (this.viewItems.length >= 1) { - const sashIndex = Math.max(index - 1, 0); - const sashItem = this.sashItems.splice(sashIndex, 1)[0]; - sashItem.disposable.dispose(); - } - - this.relayout(); - - if (sizing?.type === 'distribute') { - this.distributeViewSizes(); - } - - const result = viewItemToRemove.view; - viewItemToRemove.dispose(); - return result; - - } finally { - this.state = State.Idle; - } - } - - removeAllViews(): TView[] { - if (this.state !== State.Idle) { - throw new Error('Cant modify splitview'); - } - - this.state = State.Busy; - - try { - const viewItems = this.viewItems.splice(0, this.viewItems.length); - - for (const viewItem of viewItems) { - viewItem.dispose(); - } - - const sashItems = this.sashItems.splice(0, this.sashItems.length); - - for (const sashItem of sashItems) { - sashItem.disposable.dispose(); - } - - this.relayout(); - return viewItems.map(i => i.view); - - } finally { - this.state = State.Idle; - } - } - - /** - * Move a {@link IView view} to a different index. - * - * @param from The source index. - * @param to The target index. - */ - moveView(from: number, to: number): void { - if (this.state !== State.Idle) { - throw new Error('Cant modify splitview'); - } - - const cachedVisibleSize = this.getViewCachedVisibleSize(from); - const sizing = typeof cachedVisibleSize === 'undefined' ? this.getViewSize(from) : Sizing.Invisible(cachedVisibleSize); - const view = this.removeView(from); - this.addView(view, sizing, to); - } - - - /** - * Swap two {@link IView views}. - * - * @param from The source index. - * @param to The target index. - */ - swapViews(from: number, to: number): void { - if (this.state !== State.Idle) { - throw new Error('Cant modify splitview'); - } - - if (from > to) { - return this.swapViews(to, from); - } - - const fromSize = this.getViewSize(from); - const toSize = this.getViewSize(to); - const toView = this.removeView(to); - const fromView = this.removeView(from); - - this.addView(toView, fromSize, from); - this.addView(fromView, toSize, to); - } - - /** - * Returns whether the {@link IView view} is visible. - * - * @param index The {@link IView view} index. - */ - isViewVisible(index: number): boolean { - if (index < 0 || index >= this.viewItems.length) { - throw new Error('Index out of bounds'); - } - - const viewItem = this.viewItems[index]; - return viewItem.visible; - } - - /** - * Set a {@link IView view}'s visibility. - * - * @param index The {@link IView view} index. - * @param visible Whether the {@link IView view} should be visible. - */ - setViewVisible(index: number, visible: boolean): void { - if (index < 0 || index >= this.viewItems.length) { - throw new Error('Index out of bounds'); - } - - const viewItem = this.viewItems[index]; - viewItem.setVisible(visible); - - this.distributeEmptySpace(index); - this.layoutViews(); - this.saveProportions(); - } - - /** - * Returns the {@link IView view}'s size previously to being hidden. - * - * @param index The {@link IView view} index. - */ - getViewCachedVisibleSize(index: number): number | undefined { - if (index < 0 || index >= this.viewItems.length) { - throw new Error('Index out of bounds'); - } - - const viewItem = this.viewItems[index]; - return viewItem.cachedVisibleSize; - } - - /** - * Layout the {@link SplitView}. - * - * @param size The entire size of the {@link SplitView}. - * @param layoutContext An optional layout context to pass along to {@link IView views}. - */ - layout(size: number, layoutContext?: TLayoutContext): void { - const previousSize = Math.max(this.size, this._contentSize); - this.size = size; - this.layoutContext = layoutContext; - - if (!this.proportions) { - const indexes = range(this.viewItems.length); - const lowPriorityIndexes = indexes.filter(i => this.viewItems[i].priority === LayoutPriority.Low); - const highPriorityIndexes = indexes.filter(i => this.viewItems[i].priority === LayoutPriority.High); - - this.resize(this.viewItems.length - 1, size - previousSize, undefined, lowPriorityIndexes, highPriorityIndexes); - } else { - let total = 0; - - for (let i = 0; i < this.viewItems.length; i++) { - const item = this.viewItems[i]; - const proportion = this.proportions[i]; - - if (typeof proportion === 'number') { - total += proportion; - } else { - size -= item.size; - } - } - - for (let i = 0; i < this.viewItems.length; i++) { - const item = this.viewItems[i]; - const proportion = this.proportions[i]; - - if (typeof proportion === 'number' && total > 0) { - item.size = clamp(Math.round(proportion * size / total), item.minimumSize, item.maximumSize); - } - } - } - - this.distributeEmptySpace(); - this.layoutViews(); - } - - private saveProportions(): void { - if (this.proportionalLayout && this._contentSize > 0) { - this.proportions = this.viewItems.map(v => v.proportionalLayout && v.visible ? v.size / this._contentSize : undefined); - } - } - - private onSashStart({ sash, start, alt }: ISashEvent): void { - for (const item of this.viewItems) { - item.enabled = false; - } - - const index = this.sashItems.findIndex(item => item.sash === sash); - - // This way, we can press Alt while we resize a sash, macOS style! - const disposable = combinedDisposable( - addDisposableListener(this.el.ownerDocument.body, 'keydown', e => resetSashDragState(this.sashDragState!.current, e.altKey)), - addDisposableListener(this.el.ownerDocument.body, 'keyup', () => resetSashDragState(this.sashDragState!.current, false)) - ); - - const resetSashDragState = (start: number, alt: boolean) => { - const sizes = this.viewItems.map(i => i.size); - let minDelta = Number.NEGATIVE_INFINITY; - let maxDelta = Number.POSITIVE_INFINITY; - - if (this.inverseAltBehavior) { - alt = !alt; - } - - if (alt) { - // When we're using the last sash with Alt, we're resizing - // the view to the left/up, instead of right/down as usual - // Thus, we must do the inverse of the usual - const isLastSash = index === this.sashItems.length - 1; - - if (isLastSash) { - const viewItem = this.viewItems[index]; - minDelta = (viewItem.minimumSize - viewItem.size) / 2; - maxDelta = (viewItem.maximumSize - viewItem.size) / 2; - } else { - const viewItem = this.viewItems[index + 1]; - minDelta = (viewItem.size - viewItem.maximumSize) / 2; - maxDelta = (viewItem.size - viewItem.minimumSize) / 2; - } - } - - let snapBefore: ISashDragSnapState | undefined; - let snapAfter: ISashDragSnapState | undefined; - - if (!alt) { - const upIndexes = range(index, -1); - const downIndexes = range(index + 1, this.viewItems.length); - const minDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].minimumSize - sizes[i]), 0); - const maxDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].viewMaximumSize - sizes[i]), 0); - const maxDeltaDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].minimumSize), 0); - const minDeltaDown = downIndexes.length === 0 ? Number.NEGATIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].viewMaximumSize), 0); - const minDelta = Math.max(minDeltaUp, minDeltaDown); - const maxDelta = Math.min(maxDeltaDown, maxDeltaUp); - const snapBeforeIndex = this.findFirstSnapIndex(upIndexes); - const snapAfterIndex = this.findFirstSnapIndex(downIndexes); - - if (typeof snapBeforeIndex === 'number') { - const viewItem = this.viewItems[snapBeforeIndex]; - const halfSize = Math.floor(viewItem.viewMinimumSize / 2); - - snapBefore = { - index: snapBeforeIndex, - limitDelta: viewItem.visible ? minDelta - halfSize : minDelta + halfSize, - size: viewItem.size - }; - } - - if (typeof snapAfterIndex === 'number') { - const viewItem = this.viewItems[snapAfterIndex]; - const halfSize = Math.floor(viewItem.viewMinimumSize / 2); - - snapAfter = { - index: snapAfterIndex, - limitDelta: viewItem.visible ? maxDelta + halfSize : maxDelta - halfSize, - size: viewItem.size - }; - } - } - - this.sashDragState = { start, current: start, index, sizes, minDelta, maxDelta, alt, snapBefore, snapAfter, disposable }; - }; - - resetSashDragState(start, alt); - } - - private onSashChange({ current }: ISashEvent): void { - const { index, start, sizes, alt, minDelta, maxDelta, snapBefore, snapAfter } = this.sashDragState!; - this.sashDragState!.current = current; - - const delta = current - start; - const newDelta = this.resize(index, delta, sizes, undefined, undefined, minDelta, maxDelta, snapBefore, snapAfter); - - if (alt) { - const isLastSash = index === this.sashItems.length - 1; - const newSizes = this.viewItems.map(i => i.size); - const viewItemIndex = isLastSash ? index : index + 1; - const viewItem = this.viewItems[viewItemIndex]; - const newMinDelta = viewItem.size - viewItem.maximumSize; - const newMaxDelta = viewItem.size - viewItem.minimumSize; - const resizeIndex = isLastSash ? index - 1 : index + 1; - - this.resize(resizeIndex, -newDelta, newSizes, undefined, undefined, newMinDelta, newMaxDelta); - } - - this.distributeEmptySpace(); - this.layoutViews(); - } - - private onSashEnd(index: number): void { - this._onDidSashChange.fire(index); - this.sashDragState!.disposable.dispose(); - this.saveProportions(); - - for (const item of this.viewItems) { - item.enabled = true; - } - } - - private onViewChange(item: ViewItem, size: number | undefined): void { - const index = this.viewItems.indexOf(item); - - if (index < 0 || index >= this.viewItems.length) { - return; - } - - size = typeof size === 'number' ? size : item.size; - size = clamp(size, item.minimumSize, item.maximumSize); - - if (this.inverseAltBehavior && index > 0) { - // In this case, we want the view to grow or shrink both sides equally - // so we just resize the "left" side by half and let `resize` do the clamping magic - this.resize(index - 1, Math.floor((item.size - size) / 2)); - this.distributeEmptySpace(); - this.layoutViews(); - } else { - item.size = size; - this.relayout([index], undefined); - } - } - - /** - * Resize a {@link IView view} within the {@link SplitView}. - * - * @param index The {@link IView view} index. - * @param size The {@link IView view} size. - */ - resizeView(index: number, size: number): void { - if (index < 0 || index >= this.viewItems.length) { - return; - } - - if (this.state !== State.Idle) { - throw new Error('Cant modify splitview'); - } - - this.state = State.Busy; - - try { - const indexes = range(this.viewItems.length).filter(i => i !== index); - const lowPriorityIndexes = [...indexes.filter(i => this.viewItems[i].priority === LayoutPriority.Low), index]; - const highPriorityIndexes = indexes.filter(i => this.viewItems[i].priority === LayoutPriority.High); - - const item = this.viewItems[index]; - size = Math.round(size); - size = clamp(size, item.minimumSize, Math.min(item.maximumSize, this.size)); - - item.size = size; - this.relayout(lowPriorityIndexes, highPriorityIndexes); - } finally { - this.state = State.Idle; - } - } - - /** - * Returns whether all other {@link IView views} are at their minimum size. - */ - isViewExpanded(index: number): boolean { - if (index < 0 || index >= this.viewItems.length) { - return false; - } - - for (const item of this.viewItems) { - if (item !== this.viewItems[index] && item.size > item.minimumSize) { - return false; - } - } - - return true; - } - - /** - * Distribute the entire {@link SplitView} size among all {@link IView views}. - */ - distributeViewSizes(): void { - const flexibleViewItems: ViewItem[] = []; - let flexibleSize = 0; - - for (const item of this.viewItems) { - if (item.maximumSize - item.minimumSize > 0) { - flexibleViewItems.push(item); - flexibleSize += item.size; - } - } - - const size = Math.floor(flexibleSize / flexibleViewItems.length); - - for (const item of flexibleViewItems) { - item.size = clamp(size, item.minimumSize, item.maximumSize); - } - - const indexes = range(this.viewItems.length); - const lowPriorityIndexes = indexes.filter(i => this.viewItems[i].priority === LayoutPriority.Low); - const highPriorityIndexes = indexes.filter(i => this.viewItems[i].priority === LayoutPriority.High); - - this.relayout(lowPriorityIndexes, highPriorityIndexes); - } - - /** - * Returns the size of a {@link IView view}. - */ - getViewSize(index: number): number { - if (index < 0 || index >= this.viewItems.length) { - return -1; - } - - return this.viewItems[index].size; - } - - private doAddView(view: TView, size: number | Sizing, index = this.viewItems.length, skipLayout?: boolean): void { - if (this.state !== State.Idle) { - throw new Error('Cant modify splitview'); - } - - this.state = State.Busy; - - try { - // Add view - const container = $('.split-view-view'); - - if (index === this.viewItems.length) { - this.viewContainer.appendChild(container); - } else { - this.viewContainer.insertBefore(container, this.viewContainer.children.item(index)); - } - - const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size)); - const containerDisposable = toDisposable(() => container.remove()); - const disposable = combinedDisposable(onChangeDisposable, containerDisposable); - - let viewSize: ViewItemSize; - - if (typeof size === 'number') { - viewSize = size; - } else { - if (size.type === 'auto') { - if (this.areViewsDistributed()) { - size = { type: 'distribute' }; - } else { - size = { type: 'split', index: size.index }; - } - } - - if (size.type === 'split') { - viewSize = this.getViewSize(size.index) / 2; - } else if (size.type === 'invisible') { - viewSize = { cachedVisibleSize: size.cachedVisibleSize }; - } else { - viewSize = view.minimumSize; - } - } - - const item = this.orientation === Orientation.VERTICAL - ? new VerticalViewItem(container, view, viewSize, disposable) - : new HorizontalViewItem(container, view, viewSize, disposable); - - this.viewItems.splice(index, 0, item); - - // Add sash - if (this.viewItems.length > 1) { - const opts = { orthogonalStartSash: this.orthogonalStartSash, orthogonalEndSash: this.orthogonalEndSash }; - - const sash = this.orientation === Orientation.VERTICAL - ? new Sash(this.sashContainer, { getHorizontalSashTop: s => this.getSashPosition(s), getHorizontalSashWidth: this.getSashOrthogonalSize }, { ...opts, orientation: Orientation.HORIZONTAL }) - : new Sash(this.sashContainer, { getVerticalSashLeft: s => this.getSashPosition(s), getVerticalSashHeight: this.getSashOrthogonalSize }, { ...opts, orientation: Orientation.VERTICAL }); - - const sashEventMapper = this.orientation === Orientation.VERTICAL - ? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey }) - : (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX, alt: e.altKey }); - - const onStart = Event.map(sash.onDidStart, sashEventMapper); - const onStartDisposable = onStart(this.onSashStart, this); - const onChange = Event.map(sash.onDidChange, sashEventMapper); - const onChangeDisposable = onChange(this.onSashChange, this); - const onEnd = Event.map(sash.onDidEnd, () => this.sashItems.findIndex(item => item.sash === sash)); - const onEndDisposable = onEnd(this.onSashEnd, this); - - const onDidResetDisposable = sash.onDidReset(() => { - const index = this.sashItems.findIndex(item => item.sash === sash); - const upIndexes = range(index, -1); - const downIndexes = range(index + 1, this.viewItems.length); - const snapBeforeIndex = this.findFirstSnapIndex(upIndexes); - const snapAfterIndex = this.findFirstSnapIndex(downIndexes); - - if (typeof snapBeforeIndex === 'number' && !this.viewItems[snapBeforeIndex].visible) { - return; - } - - if (typeof snapAfterIndex === 'number' && !this.viewItems[snapAfterIndex].visible) { - return; - } - - this._onDidSashReset.fire(index); - }); - - const disposable = combinedDisposable(onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash); - const sashItem: ISashItem = { sash, disposable }; - - this.sashItems.splice(index - 1, 0, sashItem); - } - - container.appendChild(view.element); - - let highPriorityIndexes: number[] | undefined; - - if (typeof size !== 'number' && size.type === 'split') { - highPriorityIndexes = [size.index]; - } - - if (!skipLayout) { - this.relayout([index], highPriorityIndexes); - } - - - if (!skipLayout && typeof size !== 'number' && size.type === 'distribute') { - this.distributeViewSizes(); - } - - } finally { - this.state = State.Idle; - } - } - - private relayout(lowPriorityIndexes?: number[], highPriorityIndexes?: number[]): void { - const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); - - this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndexes, highPriorityIndexes); - this.distributeEmptySpace(); - this.layoutViews(); - this.saveProportions(); - } - - private resize( - index: number, - delta: number, - sizes = this.viewItems.map(i => i.size), - lowPriorityIndexes?: number[], - highPriorityIndexes?: number[], - overloadMinDelta: number = Number.NEGATIVE_INFINITY, - overloadMaxDelta: number = Number.POSITIVE_INFINITY, - snapBefore?: ISashDragSnapState, - snapAfter?: ISashDragSnapState - ): number { - if (index < 0 || index >= this.viewItems.length) { - return 0; - } - - const upIndexes = range(index, -1); - const downIndexes = range(index + 1, this.viewItems.length); - - if (highPriorityIndexes) { - for (const index of highPriorityIndexes) { - pushToStart(upIndexes, index); - pushToStart(downIndexes, index); - } - } - - if (lowPriorityIndexes) { - for (const index of lowPriorityIndexes) { - pushToEnd(upIndexes, index); - pushToEnd(downIndexes, index); - } - } - - const upItems = upIndexes.map(i => this.viewItems[i]); - const upSizes = upIndexes.map(i => sizes[i]); - - const downItems = downIndexes.map(i => this.viewItems[i]); - const downSizes = downIndexes.map(i => sizes[i]); - - const minDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].minimumSize - sizes[i]), 0); - const maxDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].maximumSize - sizes[i]), 0); - const maxDeltaDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].minimumSize), 0); - const minDeltaDown = downIndexes.length === 0 ? Number.NEGATIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].maximumSize), 0); - const minDelta = Math.max(minDeltaUp, minDeltaDown, overloadMinDelta); - const maxDelta = Math.min(maxDeltaDown, maxDeltaUp, overloadMaxDelta); - - let snapped = false; - - if (snapBefore) { - const snapView = this.viewItems[snapBefore.index]; - const visible = delta >= snapBefore.limitDelta; - snapped = visible !== snapView.visible; - snapView.setVisible(visible, snapBefore.size); - } - - if (!snapped && snapAfter) { - const snapView = this.viewItems[snapAfter.index]; - const visible = delta < snapAfter.limitDelta; - snapped = visible !== snapView.visible; - snapView.setVisible(visible, snapAfter.size); - } - - if (snapped) { - return this.resize(index, delta, sizes, lowPriorityIndexes, highPriorityIndexes, overloadMinDelta, overloadMaxDelta); - } - - delta = clamp(delta, minDelta, maxDelta); - - for (let i = 0, deltaUp = delta; i < upItems.length; i++) { - const item = upItems[i]; - const size = clamp(upSizes[i] + deltaUp, item.minimumSize, item.maximumSize); - const viewDelta = size - upSizes[i]; - - deltaUp -= viewDelta; - item.size = size; - } - - for (let i = 0, deltaDown = delta; i < downItems.length; i++) { - const item = downItems[i]; - const size = clamp(downSizes[i] - deltaDown, item.minimumSize, item.maximumSize); - const viewDelta = size - downSizes[i]; - - deltaDown += viewDelta; - item.size = size; - } - - return delta; - } - - private distributeEmptySpace(lowPriorityIndex?: number): void { - const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); - let emptyDelta = this.size - contentSize; - - const indexes = range(this.viewItems.length - 1, -1); - const lowPriorityIndexes = indexes.filter(i => this.viewItems[i].priority === LayoutPriority.Low); - const highPriorityIndexes = indexes.filter(i => this.viewItems[i].priority === LayoutPriority.High); - - for (const index of highPriorityIndexes) { - pushToStart(indexes, index); - } - - for (const index of lowPriorityIndexes) { - pushToEnd(indexes, index); - } - - if (typeof lowPriorityIndex === 'number') { - pushToEnd(indexes, lowPriorityIndex); - } - - for (let i = 0; emptyDelta !== 0 && i < indexes.length; i++) { - const item = this.viewItems[indexes[i]]; - const size = clamp(item.size + emptyDelta, item.minimumSize, item.maximumSize); - const viewDelta = size - item.size; - - emptyDelta -= viewDelta; - item.size = size; - } - } - - private layoutViews(): void { - // Save new content size - this._contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); - - // Layout views - let offset = 0; - - for (const viewItem of this.viewItems) { - viewItem.layout(offset, this.layoutContext); - offset += viewItem.size; - } - - // Layout sashes - this.sashItems.forEach(item => item.sash.layout()); - this.updateSashEnablement(); - this.updateScrollableElement(); - } - - private updateScrollableElement(): void { - if (this.orientation === Orientation.VERTICAL) { - this.scrollableElement.setScrollDimensions({ - height: this.size, - scrollHeight: this._contentSize - }); - } else { - this.scrollableElement.setScrollDimensions({ - width: this.size, - scrollWidth: this._contentSize - }); - } - } - - private updateSashEnablement(): void { - let previous = false; - const collapsesDown = this.viewItems.map(i => previous = (i.size - i.minimumSize > 0) || previous); - - previous = false; - const expandsDown = this.viewItems.map(i => previous = (i.maximumSize - i.size > 0) || previous); - - const reverseViews = [...this.viewItems].reverse(); - previous = false; - const collapsesUp = reverseViews.map(i => previous = (i.size - i.minimumSize > 0) || previous).reverse(); - - previous = false; - const expandsUp = reverseViews.map(i => previous = (i.maximumSize - i.size > 0) || previous).reverse(); - - let position = 0; - for (let index = 0; index < this.sashItems.length; index++) { - const { sash } = this.sashItems[index]; - const viewItem = this.viewItems[index]; - position += viewItem.size; - - const min = !(collapsesDown[index] && expandsUp[index + 1]); - const max = !(expandsDown[index] && collapsesUp[index + 1]); - - if (min && max) { - const upIndexes = range(index, -1); - const downIndexes = range(index + 1, this.viewItems.length); - const snapBeforeIndex = this.findFirstSnapIndex(upIndexes); - const snapAfterIndex = this.findFirstSnapIndex(downIndexes); - - const snappedBefore = typeof snapBeforeIndex === 'number' && !this.viewItems[snapBeforeIndex].visible; - const snappedAfter = typeof snapAfterIndex === 'number' && !this.viewItems[snapAfterIndex].visible; - - if (snappedBefore && collapsesUp[index] && (position > 0 || this.startSnappingEnabled)) { - sash.state = SashState.AtMinimum; - } else if (snappedAfter && collapsesDown[index] && (position < this._contentSize || this.endSnappingEnabled)) { - sash.state = SashState.AtMaximum; - } else { - sash.state = SashState.Disabled; - } - } else if (min && !max) { - sash.state = SashState.AtMinimum; - } else if (!min && max) { - sash.state = SashState.AtMaximum; - } else { - sash.state = SashState.Enabled; - } - } - } - - private getSashPosition(sash: Sash): number { - let position = 0; - - for (let i = 0; i < this.sashItems.length; i++) { - position += this.viewItems[i].size; - - if (this.sashItems[i].sash === sash) { - return position; - } - } - - return 0; - } - - private findFirstSnapIndex(indexes: number[]): number | undefined { - // visible views first - for (const index of indexes) { - const viewItem = this.viewItems[index]; - - if (!viewItem.visible) { - continue; - } - - if (viewItem.snap) { - return index; - } - } - - // then, hidden views - for (const index of indexes) { - const viewItem = this.viewItems[index]; - - if (viewItem.visible && viewItem.maximumSize - viewItem.minimumSize > 0) { - return undefined; - } - - if (!viewItem.visible && viewItem.snap) { - return index; - } - } - - return undefined; - } - - private areViewsDistributed() { - let min = undefined, max = undefined; - - for (const view of this.viewItems) { - min = min === undefined ? view.size : Math.min(min, view.size); - max = max === undefined ? view.size : Math.max(max, view.size); - - if (max - min > 2) { - return false; - } - } - - return true; - } - - override dispose(): void { - this.sashDragState?.disposable.dispose(); - - dispose(this.viewItems); - this.viewItems = []; - - this.sashItems.forEach(i => i.disposable.dispose()); - this.sashItems = []; - - super.dispose(); - } -} diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts deleted file mode 100644 index a1fd3249..00000000 --- a/src/vs/base/common/actions.ts +++ /dev/null @@ -1,271 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import * as nls from 'vs/nls'; - -export interface ITelemetryData { - readonly from?: string; - readonly target?: string; - [key: string]: unknown; -} - -export type WorkbenchActionExecutedClassification = { - id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the action that was run.' }; - from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the component the action was run from.' }; - detail?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Optional details about how the action was run, e.g which keybinding was used.' }; - owner: 'bpasero'; - comment: 'Provides insight into actions that are executed within the workbench.'; -}; - -export type WorkbenchActionExecutedEvent = { - id: string; - from: string; - detail?: string; -}; - -export interface IAction { - readonly id: string; - label: string; - tooltip: string; - class: string | undefined; - enabled: boolean; - checked?: boolean; - run(...args: unknown[]): unknown; -} - -export interface IActionRunner extends IDisposable { - readonly onDidRun: Event; - readonly onWillRun: Event; - - run(action: IAction, context?: unknown): unknown; -} - -export interface IActionChangeEvent { - readonly label?: string; - readonly tooltip?: string; - readonly class?: string; - readonly enabled?: boolean; - readonly checked?: boolean; -} - -export class Action extends Disposable implements IAction { - - protected _onDidChange = this._register(new Emitter()); - readonly onDidChange = this._onDidChange.event; - - protected readonly _id: string; - protected _label: string; - protected _tooltip: string | undefined; - protected _cssClass: string | undefined; - protected _enabled: boolean = true; - protected _checked?: boolean; - protected readonly _actionCallback?: (event?: unknown) => unknown; - - constructor(id: string, label: string = '', cssClass: string = '', enabled: boolean = true, actionCallback?: (event?: unknown) => unknown) { - super(); - this._id = id; - this._label = label; - this._cssClass = cssClass; - this._enabled = enabled; - this._actionCallback = actionCallback; - } - - get id(): string { - return this._id; - } - - get label(): string { - return this._label; - } - - set label(value: string) { - this._setLabel(value); - } - - private _setLabel(value: string): void { - if (this._label !== value) { - this._label = value; - this._onDidChange.fire({ label: value }); - } - } - - get tooltip(): string { - return this._tooltip || ''; - } - - set tooltip(value: string) { - this._setTooltip(value); - } - - protected _setTooltip(value: string): void { - if (this._tooltip !== value) { - this._tooltip = value; - this._onDidChange.fire({ tooltip: value }); - } - } - - get class(): string | undefined { - return this._cssClass; - } - - set class(value: string | undefined) { - this._setClass(value); - } - - protected _setClass(value: string | undefined): void { - if (this._cssClass !== value) { - this._cssClass = value; - this._onDidChange.fire({ class: value }); - } - } - - get enabled(): boolean { - return this._enabled; - } - - set enabled(value: boolean) { - this._setEnabled(value); - } - - protected _setEnabled(value: boolean): void { - if (this._enabled !== value) { - this._enabled = value; - this._onDidChange.fire({ enabled: value }); - } - } - - get checked(): boolean | undefined { - return this._checked; - } - - set checked(value: boolean | undefined) { - this._setChecked(value); - } - - protected _setChecked(value: boolean | undefined): void { - if (this._checked !== value) { - this._checked = value; - this._onDidChange.fire({ checked: value }); - } - } - - async run(event?: unknown, data?: ITelemetryData): Promise { - if (this._actionCallback) { - await this._actionCallback(event); - } - } -} - -export interface IRunEvent { - readonly action: IAction; - readonly error?: Error; -} - -export class ActionRunner extends Disposable implements IActionRunner { - - private readonly _onWillRun = this._register(new Emitter()); - readonly onWillRun = this._onWillRun.event; - - private readonly _onDidRun = this._register(new Emitter()); - readonly onDidRun = this._onDidRun.event; - - async run(action: IAction, context?: unknown): Promise { - if (!action.enabled) { - return; - } - - this._onWillRun.fire({ action }); - - let error: Error | undefined = undefined; - try { - await this.runAction(action, context); - } catch (e) { - error = e; - } - - this._onDidRun.fire({ action, error }); - } - - protected async runAction(action: IAction, context?: unknown): Promise { - await action.run(context); - } -} - -export class Separator implements IAction { - - /** - * Joins all non-empty lists of actions with separators. - */ - public static join(...actionLists: readonly IAction[][]) { - let out: IAction[] = []; - for (const list of actionLists) { - if (!list.length) { - // skip - } else if (out.length) { - out = [...out, new Separator(), ...list]; - } else { - out = list; - } - } - - return out; - } - - static readonly ID = 'vs.actions.separator'; - - readonly id: string = Separator.ID; - - readonly label: string = ''; - readonly tooltip: string = ''; - readonly class: string = 'separator'; - readonly enabled: boolean = false; - readonly checked: boolean = false; - async run() { } -} - -export class SubmenuAction implements IAction { - - readonly id: string; - readonly label: string; - readonly class: string | undefined; - readonly tooltip: string = ''; - readonly enabled: boolean = true; - readonly checked: undefined = undefined; - - private readonly _actions: readonly IAction[]; - get actions(): readonly IAction[] { return this._actions; } - - constructor(id: string, label: string, actions: readonly IAction[], cssClass?: string) { - this.id = id; - this.label = label; - this.class = cssClass; - this._actions = actions; - } - - async run(): Promise { } -} - -export class EmptySubmenuAction extends Action { - - static readonly ID = 'vs.actions.empty'; - - constructor() { - super(EmptySubmenuAction.ID, nls.localize('submenu.empty', '(empty)'), undefined, false); - } -} - -export function toAction(props: { id: string; label: string; tooltip?: string; enabled?: boolean; checked?: boolean; class?: string; run: Function }): IAction { - return { - id: props.id, - label: props.label, - tooltip: props.tooltip ?? props.label, - class: props.class, - enabled: props.enabled ?? true, - checked: props.checked, - run: async (...args: unknown[]) => props.run(...args), - }; -} diff --git a/src/vs/base/common/amd.ts b/src/vs/base/common/amd.ts deleted file mode 100644 index 6d228840..00000000 --- a/src/vs/base/common/amd.ts +++ /dev/null @@ -1,163 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// ESM-comment-begin -export const isESM = false; -// ESM-comment-end -// ESM-uncomment-begin -// export const isESM = true; -// ESM-uncomment-end - -export const enum LoaderEventType { - LoaderAvailable = 1, - - BeginLoadingScript = 10, - EndLoadingScriptOK = 11, - EndLoadingScriptError = 12, - - BeginInvokeFactory = 21, - EndInvokeFactory = 22, - - NodeBeginEvaluatingScript = 31, - NodeEndEvaluatingScript = 32, - - NodeBeginNativeRequire = 33, - NodeEndNativeRequire = 34, - - CachedDataFound = 60, - CachedDataMissed = 61, - CachedDataRejected = 62, - CachedDataCreated = 63, -} - -export abstract class LoaderStats { - abstract get amdLoad(): [string, number][]; - abstract get amdInvoke(): [string, number][]; - abstract get nodeRequire(): [string, number][]; - abstract get nodeEval(): [string, number][]; - abstract get nodeRequireTotal(): number; - - static get(): LoaderStats { - const amdLoadScript = new Map(); - const amdInvokeFactory = new Map(); - const nodeRequire = new Map(); - const nodeEval = new Map(); - - function mark(map: Map, stat: LoaderEvent) { - if (map.has(stat.detail)) { - // console.warn('BAD events, DOUBLE start', stat); - // map.delete(stat.detail); - return; - } - map.set(stat.detail, -stat.timestamp); - } - - function diff(map: Map, stat: LoaderEvent) { - const duration = map.get(stat.detail); - if (!duration) { - // console.warn('BAD events, end WITHOUT start', stat); - // map.delete(stat.detail); - return; - } - if (duration >= 0) { - // console.warn('BAD events, DOUBLE end', stat); - // map.delete(stat.detail); - return; - } - map.set(stat.detail, duration + stat.timestamp); - } - - let stats: readonly LoaderEvent[] = []; - if (typeof require === 'function' && typeof require.getStats === 'function') { - stats = require.getStats().slice(0).sort((a, b) => a.timestamp - b.timestamp); - } - - for (const stat of stats) { - switch (stat.type) { - case LoaderEventType.BeginLoadingScript: - mark(amdLoadScript, stat); - break; - case LoaderEventType.EndLoadingScriptOK: - case LoaderEventType.EndLoadingScriptError: - diff(amdLoadScript, stat); - break; - - case LoaderEventType.BeginInvokeFactory: - mark(amdInvokeFactory, stat); - break; - case LoaderEventType.EndInvokeFactory: - diff(amdInvokeFactory, stat); - break; - - case LoaderEventType.NodeBeginNativeRequire: - mark(nodeRequire, stat); - break; - case LoaderEventType.NodeEndNativeRequire: - diff(nodeRequire, stat); - break; - - case LoaderEventType.NodeBeginEvaluatingScript: - mark(nodeEval, stat); - break; - case LoaderEventType.NodeEndEvaluatingScript: - diff(nodeEval, stat); - break; - } - } - - let nodeRequireTotal = 0; - nodeRequire.forEach(value => nodeRequireTotal += value); - - function to2dArray(map: Map): [string, number][] { - const res: [string, number][] = []; - map.forEach((value, index) => res.push([index, value])); - return res; - } - - return { - amdLoad: to2dArray(amdLoadScript), - amdInvoke: to2dArray(amdInvokeFactory), - nodeRequire: to2dArray(nodeRequire), - nodeEval: to2dArray(nodeEval), - nodeRequireTotal - }; - } - - static toMarkdownTable(header: string[], rows: Array>): string { - let result = ''; - - const lengths: number[] = []; - header.forEach((cell, ci) => { - lengths[ci] = cell.length; - }); - rows.forEach(row => { - row.forEach((cell, ci) => { - if (typeof cell === 'undefined') { - cell = row[ci] = '-'; - } - const len = cell.toString().length; - lengths[ci] = Math.max(len, lengths[ci]); - }); - }); - - // header - header.forEach((cell, ci) => { result += `| ${cell + ' '.repeat(lengths[ci] - cell.toString().length)} `; }); - result += '|\n'; - header.forEach((_cell, ci) => { result += `| ${'-'.repeat(lengths[ci])} `; }); - result += '|\n'; - - // cells - rows.forEach(row => { - row.forEach((cell, ci) => { - if (typeof cell !== 'undefined') { - result += `| ${cell + ' '.repeat(lengths[ci] - cell.toString().length)} `; - } - }); - result += '|\n'; - }); - - return result; - } -} diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts deleted file mode 100644 index 08736ab8..00000000 --- a/src/vs/base/common/buffer.ts +++ /dev/null @@ -1,441 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Lazy } from 'vs/base/common/lazy'; -import * as streams from 'vs/base/common/stream'; - -declare const Buffer: any; - -const hasBuffer = (typeof Buffer !== 'undefined'); -const indexOfTable = new Lazy(() => new Uint8Array(256)); - -let textEncoder: TextEncoder | null; -let textDecoder: TextDecoder | null; - -export class VSBuffer { - - /** - * When running in a nodejs context, the backing store for the returned `VSBuffer` instance - * might use a nodejs Buffer allocated from node's Buffer pool, which is not transferrable. - */ - static alloc(byteLength: number): VSBuffer { - if (hasBuffer) { - return new VSBuffer(Buffer.allocUnsafe(byteLength)); - } else { - return new VSBuffer(new Uint8Array(byteLength)); - } - } - - /** - * When running in a nodejs context, if `actual` is not a nodejs Buffer, the backing store for - * the returned `VSBuffer` instance might use a nodejs Buffer allocated from node's Buffer pool, - * which is not transferrable. - */ - static wrap(actual: Uint8Array): VSBuffer { - if (hasBuffer && !(Buffer.isBuffer(actual))) { - // https://nodejs.org/dist/latest-v10.x/docs/api/buffer.html#buffer_class_method_buffer_from_arraybuffer_byteoffset_length - // Create a zero-copy Buffer wrapper around the ArrayBuffer pointed to by the Uint8Array - actual = Buffer.from(actual.buffer, actual.byteOffset, actual.byteLength); - } - return new VSBuffer(actual); - } - - /** - * When running in a nodejs context, the backing store for the returned `VSBuffer` instance - * might use a nodejs Buffer allocated from node's Buffer pool, which is not transferrable. - */ - static fromString(source: string, options?: { dontUseNodeBuffer?: boolean }): VSBuffer { - const dontUseNodeBuffer = options?.dontUseNodeBuffer || false; - if (!dontUseNodeBuffer && hasBuffer) { - return new VSBuffer(Buffer.from(source)); - } else { - if (!textEncoder) { - textEncoder = new TextEncoder(); - } - return new VSBuffer(textEncoder.encode(source)); - } - } - - /** - * When running in a nodejs context, the backing store for the returned `VSBuffer` instance - * might use a nodejs Buffer allocated from node's Buffer pool, which is not transferrable. - */ - static fromByteArray(source: number[]): VSBuffer { - const result = VSBuffer.alloc(source.length); - for (let i = 0, len = source.length; i < len; i++) { - result.buffer[i] = source[i]; - } - return result; - } - - /** - * When running in a nodejs context, the backing store for the returned `VSBuffer` instance - * might use a nodejs Buffer allocated from node's Buffer pool, which is not transferrable. - */ - static concat(buffers: VSBuffer[], totalLength?: number): VSBuffer { - if (typeof totalLength === 'undefined') { - totalLength = 0; - for (let i = 0, len = buffers.length; i < len; i++) { - totalLength += buffers[i].byteLength; - } - } - - const ret = VSBuffer.alloc(totalLength); - let offset = 0; - for (let i = 0, len = buffers.length; i < len; i++) { - const element = buffers[i]; - ret.set(element, offset); - offset += element.byteLength; - } - - return ret; - } - - readonly buffer: Uint8Array; - readonly byteLength: number; - - private constructor(buffer: Uint8Array) { - this.buffer = buffer; - this.byteLength = this.buffer.byteLength; - } - - /** - * When running in a nodejs context, the backing store for the returned `VSBuffer` instance - * might use a nodejs Buffer allocated from node's Buffer pool, which is not transferrable. - */ - clone(): VSBuffer { - const result = VSBuffer.alloc(this.byteLength); - result.set(this); - return result; - } - - toString(): string { - if (hasBuffer) { - return this.buffer.toString(); - } else { - if (!textDecoder) { - textDecoder = new TextDecoder(); - } - return textDecoder.decode(this.buffer); - } - } - - slice(start?: number, end?: number): VSBuffer { - // IMPORTANT: use subarray instead of slice because TypedArray#slice - // creates shallow copy and NodeBuffer#slice doesn't. The use of subarray - // ensures the same, performance, behaviour. - return new VSBuffer(this.buffer.subarray(start, end)); - } - - set(array: VSBuffer, offset?: number): void; - set(array: Uint8Array, offset?: number): void; - set(array: ArrayBuffer, offset?: number): void; - set(array: ArrayBufferView, offset?: number): void; - set(array: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView, offset?: number): void; - set(array: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView, offset?: number): void { - if (array instanceof VSBuffer) { - this.buffer.set(array.buffer, offset); - } else if (array instanceof Uint8Array) { - this.buffer.set(array, offset); - } else if (array instanceof ArrayBuffer) { - this.buffer.set(new Uint8Array(array), offset); - } else if (ArrayBuffer.isView(array)) { - this.buffer.set(new Uint8Array(array.buffer, array.byteOffset, array.byteLength), offset); - } else { - throw new Error(`Unknown argument 'array'`); - } - } - - readUInt32BE(offset: number): number { - return readUInt32BE(this.buffer, offset); - } - - writeUInt32BE(value: number, offset: number): void { - writeUInt32BE(this.buffer, value, offset); - } - - readUInt32LE(offset: number): number { - return readUInt32LE(this.buffer, offset); - } - - writeUInt32LE(value: number, offset: number): void { - writeUInt32LE(this.buffer, value, offset); - } - - readUInt8(offset: number): number { - return readUInt8(this.buffer, offset); - } - - writeUInt8(value: number, offset: number): void { - writeUInt8(this.buffer, value, offset); - } - - indexOf(subarray: VSBuffer | Uint8Array, offset = 0) { - return binaryIndexOf(this.buffer, subarray instanceof VSBuffer ? subarray.buffer : subarray, offset); - } -} - -/** - * Like String.indexOf, but works on Uint8Arrays. - * Uses the boyer-moore-horspool algorithm to be reasonably speedy. - */ -export function binaryIndexOf(haystack: Uint8Array, needle: Uint8Array, offset = 0): number { - const needleLen = needle.byteLength; - const haystackLen = haystack.byteLength; - - if (needleLen === 0) { - return 0; - } - - if (needleLen === 1) { - return haystack.indexOf(needle[0]); - } - - if (needleLen > haystackLen - offset) { - return -1; - } - - // find index of the subarray using boyer-moore-horspool algorithm - const table = indexOfTable.value; - table.fill(needle.length); - for (let i = 0; i < needle.length; i++) { - table[needle[i]] = needle.length - i - 1; - } - - let i = offset + needle.length - 1; - let j = i; - let result = -1; - while (i < haystackLen) { - if (haystack[i] === needle[j]) { - if (j === 0) { - result = i; - break; - } - - i--; - j--; - } else { - i += Math.max(needle.length - j, table[haystack[i]]); - j = needle.length - 1; - } - } - - return result; -} - -export function readUInt16LE(source: Uint8Array, offset: number): number { - return ( - ((source[offset + 0] << 0) >>> 0) | - ((source[offset + 1] << 8) >>> 0) - ); -} - -export function writeUInt16LE(destination: Uint8Array, value: number, offset: number): void { - destination[offset + 0] = (value & 0b11111111); - value = value >>> 8; - destination[offset + 1] = (value & 0b11111111); -} - -export function readUInt32BE(source: Uint8Array, offset: number): number { - return ( - source[offset] * 2 ** 24 - + source[offset + 1] * 2 ** 16 - + source[offset + 2] * 2 ** 8 - + source[offset + 3] - ); -} - -export function writeUInt32BE(destination: Uint8Array, value: number, offset: number): void { - destination[offset + 3] = value; - value = value >>> 8; - destination[offset + 2] = value; - value = value >>> 8; - destination[offset + 1] = value; - value = value >>> 8; - destination[offset] = value; -} - -export function readUInt32LE(source: Uint8Array, offset: number): number { - return ( - ((source[offset + 0] << 0) >>> 0) | - ((source[offset + 1] << 8) >>> 0) | - ((source[offset + 2] << 16) >>> 0) | - ((source[offset + 3] << 24) >>> 0) - ); -} - -export function writeUInt32LE(destination: Uint8Array, value: number, offset: number): void { - destination[offset + 0] = (value & 0b11111111); - value = value >>> 8; - destination[offset + 1] = (value & 0b11111111); - value = value >>> 8; - destination[offset + 2] = (value & 0b11111111); - value = value >>> 8; - destination[offset + 3] = (value & 0b11111111); -} - -export function readUInt8(source: Uint8Array, offset: number): number { - return source[offset]; -} - -export function writeUInt8(destination: Uint8Array, value: number, offset: number): void { - destination[offset] = value; -} - -export interface VSBufferReadable extends streams.Readable { } - -export interface VSBufferReadableStream extends streams.ReadableStream { } - -export interface VSBufferWriteableStream extends streams.WriteableStream { } - -export interface VSBufferReadableBufferedStream extends streams.ReadableBufferedStream { } - -export function readableToBuffer(readable: VSBufferReadable): VSBuffer { - return streams.consumeReadable(readable, chunks => VSBuffer.concat(chunks)); -} - -export function bufferToReadable(buffer: VSBuffer): VSBufferReadable { - return streams.toReadable(buffer); -} - -export function streamToBuffer(stream: streams.ReadableStream): Promise { - return streams.consumeStream(stream, chunks => VSBuffer.concat(chunks)); -} - -export async function bufferedStreamToBuffer(bufferedStream: streams.ReadableBufferedStream): Promise { - if (bufferedStream.ended) { - return VSBuffer.concat(bufferedStream.buffer); - } - - return VSBuffer.concat([ - - // Include already read chunks... - ...bufferedStream.buffer, - - // ...and all additional chunks - await streamToBuffer(bufferedStream.stream) - ]); -} - -export function bufferToStream(buffer: VSBuffer): streams.ReadableStream { - return streams.toStream(buffer, chunks => VSBuffer.concat(chunks)); -} - -export function streamToBufferReadableStream(stream: streams.ReadableStreamEvents): streams.ReadableStream { - return streams.transform(stream, { data: data => typeof data === 'string' ? VSBuffer.fromString(data) : VSBuffer.wrap(data) }, chunks => VSBuffer.concat(chunks)); -} - -export function newWriteableBufferStream(options?: streams.WriteableStreamOptions): streams.WriteableStream { - return streams.newWriteableStream(chunks => VSBuffer.concat(chunks), options); -} - -export function prefixedBufferReadable(prefix: VSBuffer, readable: VSBufferReadable): VSBufferReadable { - return streams.prefixedReadable(prefix, readable, chunks => VSBuffer.concat(chunks)); -} - -export function prefixedBufferStream(prefix: VSBuffer, stream: VSBufferReadableStream): VSBufferReadableStream { - return streams.prefixedStream(prefix, stream, chunks => VSBuffer.concat(chunks)); -} - -/** Decodes base64 to a uint8 array. URL-encoded and unpadded base64 is allowed. */ -export function decodeBase64(encoded: string) { - let building = 0; - let remainder = 0; - let bufi = 0; - - // The simpler way to do this is `Uint8Array.from(atob(str), c => c.charCodeAt(0))`, - // but that's about 10-20x slower than this function in current Chromium versions. - - const buffer = new Uint8Array(Math.floor(encoded.length / 4 * 3)); - const append = (value: number) => { - switch (remainder) { - case 3: - buffer[bufi++] = building | value; - remainder = 0; - break; - case 2: - buffer[bufi++] = building | (value >>> 2); - building = value << 6; - remainder = 3; - break; - case 1: - buffer[bufi++] = building | (value >>> 4); - building = value << 4; - remainder = 2; - break; - default: - building = value << 2; - remainder = 1; - } - }; - - for (let i = 0; i < encoded.length; i++) { - const code = encoded.charCodeAt(i); - // See https://datatracker.ietf.org/doc/html/rfc4648#section-4 - // This branchy code is about 3x faster than an indexOf on a base64 char string. - if (code >= 65 && code <= 90) { - append(code - 65); // A-Z starts ranges from char code 65 to 90 - } else if (code >= 97 && code <= 122) { - append(code - 97 + 26); // a-z starts ranges from char code 97 to 122, starting at byte 26 - } else if (code >= 48 && code <= 57) { - append(code - 48 + 52); // 0-9 starts ranges from char code 48 to 58, starting at byte 52 - } else if (code === 43 || code === 45) { - append(62); // "+" or "-" for URLS - } else if (code === 47 || code === 95) { - append(63); // "/" or "_" for URLS - } else if (code === 61) { - break; // "=" - } else { - throw new SyntaxError(`Unexpected base64 character ${encoded[i]}`); - } - } - - const unpadded = bufi; - while (remainder > 0) { - append(0); - } - - // slice is needed to account for overestimation due to padding - return VSBuffer.wrap(buffer).slice(0, unpadded); -} - -const base64Alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; -const base64UrlSafeAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; - -/** Encodes a buffer to a base64 string. */ -export function encodeBase64({ buffer }: VSBuffer, padded = true, urlSafe = false) { - const dictionary = urlSafe ? base64UrlSafeAlphabet : base64Alphabet; - let output = ''; - - const remainder = buffer.byteLength % 3; - - let i = 0; - for (; i < buffer.byteLength - remainder; i += 3) { - const a = buffer[i + 0]; - const b = buffer[i + 1]; - const c = buffer[i + 2]; - - output += dictionary[a >>> 2]; - output += dictionary[(a << 4 | b >>> 4) & 0b111111]; - output += dictionary[(b << 2 | c >>> 6) & 0b111111]; - output += dictionary[c & 0b111111]; - } - - if (remainder === 1) { - const a = buffer[i + 0]; - output += dictionary[a >>> 2]; - output += dictionary[(a << 4) & 0b111111]; - if (padded) { output += '=='; } - } else if (remainder === 2) { - const a = buffer[i + 0]; - const b = buffer[i + 1]; - output += dictionary[a >>> 2]; - output += dictionary[(a << 4 | b >>> 4) & 0b111111]; - output += dictionary[(b << 2) & 0b111111]; - if (padded) { output += '='; } - } - - return output; -} diff --git a/src/vs/base/common/cache.ts b/src/vs/base/common/cache.ts deleted file mode 100644 index 03abbaf8..00000000 --- a/src/vs/base/common/cache.ts +++ /dev/null @@ -1,120 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { IDisposable } from 'vs/base/common/lifecycle'; - -export interface CacheResult extends IDisposable { - promise: Promise; -} - -export class Cache { - - private result: CacheResult | null = null; - constructor(private task: (ct: CancellationToken) => Promise) { } - - get(): CacheResult { - if (this.result) { - return this.result; - } - - const cts = new CancellationTokenSource(); - const promise = this.task(cts.token); - - this.result = { - promise, - dispose: () => { - this.result = null; - cts.cancel(); - cts.dispose(); - } - }; - - return this.result; - } -} - -export function identity(t: T): T { - return t; -} - -interface ICacheOptions { - /** - * The cache key is used to identify the cache entry. - * Strict equality is used to compare cache keys. - */ - getCacheKey: (arg: TArg) => unknown; -} - -/** - * Uses a LRU cache to make a given parametrized function cached. - * Caches just the last key/value. -*/ -export class LRUCachedFunction { - private lastCache: TComputed | undefined = undefined; - private lastArgKey: unknown | undefined = undefined; - - private readonly _fn: (arg: TArg) => TComputed; - private readonly _computeKey: (arg: TArg) => unknown; - - constructor(fn: (arg: TArg) => TComputed); - constructor(options: ICacheOptions, fn: (arg: TArg) => TComputed); - constructor(arg1: ICacheOptions | ((arg: TArg) => TComputed), arg2?: (arg: TArg) => TComputed) { - if (typeof arg1 === 'function') { - this._fn = arg1; - this._computeKey = identity; - } else { - this._fn = arg2!; - this._computeKey = arg1.getCacheKey; - } - } - - public get(arg: TArg): TComputed { - const key = this._computeKey(arg); - if (this.lastArgKey !== key) { - this.lastArgKey = key; - this.lastCache = this._fn(arg); - } - return this.lastCache!; - } -} - -/** - * Uses an unbounded cache to memoize the results of the given function. -*/ -export class CachedFunction { - private readonly _map = new Map(); - private readonly _map2 = new Map(); - public get cachedValues(): ReadonlyMap { - return this._map; - } - - private readonly _fn: (arg: TArg) => TComputed; - private readonly _computeKey: (arg: TArg) => unknown; - - constructor(fn: (arg: TArg) => TComputed); - constructor(options: ICacheOptions, fn: (arg: TArg) => TComputed); - constructor(arg1: ICacheOptions | ((arg: TArg) => TComputed), arg2?: (arg: TArg) => TComputed) { - if (typeof arg1 === 'function') { - this._fn = arg1; - this._computeKey = identity; - } else { - this._fn = arg2!; - this._computeKey = arg1.getCacheKey; - } - } - - public get(arg: TArg): TComputed { - const key = this._computeKey(arg); - if (this._map2.has(key)) { - return this._map2.get(key)!; - } - - const value = this._fn(arg); - this._map.set(arg, value); - this._map2.set(key, value); - return value; - } -} diff --git a/src/vs/base/common/codiconsUtil.ts b/src/vs/base/common/codiconsUtil.ts deleted file mode 100644 index ce7f9b2d..00000000 --- a/src/vs/base/common/codiconsUtil.ts +++ /dev/null @@ -1,28 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { ThemeIcon } from 'vs/base/common/themables'; -import { isString } from 'vs/base/common/types'; - - -const _codiconFontCharacters: { [id: string]: number } = Object.create(null); - -export function register(id: string, fontCharacter: number | string): ThemeIcon { - if (isString(fontCharacter)) { - const val = _codiconFontCharacters[fontCharacter]; - if (val === undefined) { - throw new Error(`${id} references an unknown codicon: ${fontCharacter}`); - } - fontCharacter = val; - } - _codiconFontCharacters[id] = fontCharacter; - return { id }; -} - -/** - * Only to be used by the iconRegistry. - */ -export function getCodiconFontCharacters(): { [id: string]: number } { - return _codiconFontCharacters; -} diff --git a/src/vs/base/common/color.ts b/src/vs/base/common/color.ts deleted file mode 100644 index 750cfe7d..00000000 --- a/src/vs/base/common/color.ts +++ /dev/null @@ -1,633 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CharCode } from 'vs/base/common/charCode'; - -function roundFloat(number: number, decimalPoints: number): number { - const decimal = Math.pow(10, decimalPoints); - return Math.round(number * decimal) / decimal; -} - -export class RGBA { - _rgbaBrand: void = undefined; - - /** - * Red: integer in [0-255] - */ - readonly r: number; - - /** - * Green: integer in [0-255] - */ - readonly g: number; - - /** - * Blue: integer in [0-255] - */ - readonly b: number; - - /** - * Alpha: float in [0-1] - */ - readonly a: number; - - constructor(r: number, g: number, b: number, a: number = 1) { - this.r = Math.min(255, Math.max(0, r)) | 0; - this.g = Math.min(255, Math.max(0, g)) | 0; - this.b = Math.min(255, Math.max(0, b)) | 0; - this.a = roundFloat(Math.max(Math.min(1, a), 0), 3); - } - - static equals(a: RGBA, b: RGBA): boolean { - return a.r === b.r && a.g === b.g && a.b === b.b && a.a === b.a; - } -} - -export class HSLA { - - _hslaBrand: void = undefined; - - /** - * Hue: integer in [0, 360] - */ - readonly h: number; - - /** - * Saturation: float in [0, 1] - */ - readonly s: number; - - /** - * Luminosity: float in [0, 1] - */ - readonly l: number; - - /** - * Alpha: float in [0, 1] - */ - readonly a: number; - - constructor(h: number, s: number, l: number, a: number) { - this.h = Math.max(Math.min(360, h), 0) | 0; - this.s = roundFloat(Math.max(Math.min(1, s), 0), 3); - this.l = roundFloat(Math.max(Math.min(1, l), 0), 3); - this.a = roundFloat(Math.max(Math.min(1, a), 0), 3); - } - - static equals(a: HSLA, b: HSLA): boolean { - return a.h === b.h && a.s === b.s && a.l === b.l && a.a === b.a; - } - - /** - * Converts an RGB color value to HSL. Conversion formula - * adapted from http://en.wikipedia.org/wiki/HSL_color_space. - * Assumes r, g, and b are contained in the set [0, 255] and - * returns h in the set [0, 360], s, and l in the set [0, 1]. - */ - static fromRGBA(rgba: RGBA): HSLA { - const r = rgba.r / 255; - const g = rgba.g / 255; - const b = rgba.b / 255; - const a = rgba.a; - - const max = Math.max(r, g, b); - const min = Math.min(r, g, b); - let h = 0; - let s = 0; - const l = (min + max) / 2; - const chroma = max - min; - - if (chroma > 0) { - s = Math.min((l <= 0.5 ? chroma / (2 * l) : chroma / (2 - (2 * l))), 1); - - switch (max) { - case r: h = (g - b) / chroma + (g < b ? 6 : 0); break; - case g: h = (b - r) / chroma + 2; break; - case b: h = (r - g) / chroma + 4; break; - } - - h *= 60; - h = Math.round(h); - } - return new HSLA(h, s, l, a); - } - - private static _hue2rgb(p: number, q: number, t: number): number { - if (t < 0) { - t += 1; - } - if (t > 1) { - t -= 1; - } - if (t < 1 / 6) { - return p + (q - p) * 6 * t; - } - if (t < 1 / 2) { - return q; - } - if (t < 2 / 3) { - return p + (q - p) * (2 / 3 - t) * 6; - } - return p; - } - - /** - * Converts an HSL color value to RGB. Conversion formula - * adapted from http://en.wikipedia.org/wiki/HSL_color_space. - * Assumes h in the set [0, 360] s, and l are contained in the set [0, 1] and - * returns r, g, and b in the set [0, 255]. - */ - static toRGBA(hsla: HSLA): RGBA { - const h = hsla.h / 360; - const { s, l, a } = hsla; - let r: number, g: number, b: number; - - if (s === 0) { - r = g = b = l; // achromatic - } else { - const q = l < 0.5 ? l * (1 + s) : l + s - l * s; - const p = 2 * l - q; - r = HSLA._hue2rgb(p, q, h + 1 / 3); - g = HSLA._hue2rgb(p, q, h); - b = HSLA._hue2rgb(p, q, h - 1 / 3); - } - - return new RGBA(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), a); - } -} - -export class HSVA { - - _hsvaBrand: void = undefined; - - /** - * Hue: integer in [0, 360] - */ - readonly h: number; - - /** - * Saturation: float in [0, 1] - */ - readonly s: number; - - /** - * Value: float in [0, 1] - */ - readonly v: number; - - /** - * Alpha: float in [0, 1] - */ - readonly a: number; - - constructor(h: number, s: number, v: number, a: number) { - this.h = Math.max(Math.min(360, h), 0) | 0; - this.s = roundFloat(Math.max(Math.min(1, s), 0), 3); - this.v = roundFloat(Math.max(Math.min(1, v), 0), 3); - this.a = roundFloat(Math.max(Math.min(1, a), 0), 3); - } - - static equals(a: HSVA, b: HSVA): boolean { - return a.h === b.h && a.s === b.s && a.v === b.v && a.a === b.a; - } - - // from http://www.rapidtables.com/convert/color/rgb-to-hsv.htm - static fromRGBA(rgba: RGBA): HSVA { - const r = rgba.r / 255; - const g = rgba.g / 255; - const b = rgba.b / 255; - const cmax = Math.max(r, g, b); - const cmin = Math.min(r, g, b); - const delta = cmax - cmin; - const s = cmax === 0 ? 0 : (delta / cmax); - let m: number; - - if (delta === 0) { - m = 0; - } else if (cmax === r) { - m = ((((g - b) / delta) % 6) + 6) % 6; - } else if (cmax === g) { - m = ((b - r) / delta) + 2; - } else { - m = ((r - g) / delta) + 4; - } - - return new HSVA(Math.round(m * 60), s, cmax, rgba.a); - } - - // from http://www.rapidtables.com/convert/color/hsv-to-rgb.htm - static toRGBA(hsva: HSVA): RGBA { - const { h, s, v, a } = hsva; - const c = v * s; - const x = c * (1 - Math.abs((h / 60) % 2 - 1)); - const m = v - c; - let [r, g, b] = [0, 0, 0]; - - if (h < 60) { - r = c; - g = x; - } else if (h < 120) { - r = x; - g = c; - } else if (h < 180) { - g = c; - b = x; - } else if (h < 240) { - g = x; - b = c; - } else if (h < 300) { - r = x; - b = c; - } else if (h <= 360) { - r = c; - b = x; - } - - r = Math.round((r + m) * 255); - g = Math.round((g + m) * 255); - b = Math.round((b + m) * 255); - - return new RGBA(r, g, b, a); - } -} - -export class Color { - - static fromHex(hex: string): Color { - return Color.Format.CSS.parseHex(hex) || Color.red; - } - - static equals(a: Color | null, b: Color | null): boolean { - if (!a && !b) { - return true; - } - if (!a || !b) { - return false; - } - return a.equals(b); - } - - readonly rgba: RGBA; - private _hsla?: HSLA; - get hsla(): HSLA { - if (this._hsla) { - return this._hsla; - } else { - return HSLA.fromRGBA(this.rgba); - } - } - - private _hsva?: HSVA; - get hsva(): HSVA { - if (this._hsva) { - return this._hsva; - } - return HSVA.fromRGBA(this.rgba); - } - - constructor(arg: RGBA | HSLA | HSVA) { - if (!arg) { - throw new Error('Color needs a value'); - } else if (arg instanceof RGBA) { - this.rgba = arg; - } else if (arg instanceof HSLA) { - this._hsla = arg; - this.rgba = HSLA.toRGBA(arg); - } else if (arg instanceof HSVA) { - this._hsva = arg; - this.rgba = HSVA.toRGBA(arg); - } else { - throw new Error('Invalid color ctor argument'); - } - } - - equals(other: Color | null): boolean { - return !!other && RGBA.equals(this.rgba, other.rgba) && HSLA.equals(this.hsla, other.hsla) && HSVA.equals(this.hsva, other.hsva); - } - - /** - * http://www.w3.org/TR/WCAG20/#relativeluminancedef - * Returns the number in the set [0, 1]. O => Darkest Black. 1 => Lightest white. - */ - getRelativeLuminance(): number { - const R = Color._relativeLuminanceForComponent(this.rgba.r); - const G = Color._relativeLuminanceForComponent(this.rgba.g); - const B = Color._relativeLuminanceForComponent(this.rgba.b); - const luminance = 0.2126 * R + 0.7152 * G + 0.0722 * B; - - return roundFloat(luminance, 4); - } - - private static _relativeLuminanceForComponent(color: number): number { - const c = color / 255; - return (c <= 0.03928) ? c / 12.92 : Math.pow(((c + 0.055) / 1.055), 2.4); - } - - /** - * http://www.w3.org/TR/WCAG20/#contrast-ratiodef - * Returns the contrast ration number in the set [1, 21]. - */ - getContrastRatio(another: Color): number { - const lum1 = this.getRelativeLuminance(); - const lum2 = another.getRelativeLuminance(); - return lum1 > lum2 ? (lum1 + 0.05) / (lum2 + 0.05) : (lum2 + 0.05) / (lum1 + 0.05); - } - - /** - * http://24ways.org/2010/calculating-color-contrast - * Return 'true' if darker color otherwise 'false' - */ - isDarker(): boolean { - const yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000; - return yiq < 128; - } - - /** - * http://24ways.org/2010/calculating-color-contrast - * Return 'true' if lighter color otherwise 'false' - */ - isLighter(): boolean { - const yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000; - return yiq >= 128; - } - - isLighterThan(another: Color): boolean { - const lum1 = this.getRelativeLuminance(); - const lum2 = another.getRelativeLuminance(); - return lum1 > lum2; - } - - isDarkerThan(another: Color): boolean { - const lum1 = this.getRelativeLuminance(); - const lum2 = another.getRelativeLuminance(); - return lum1 < lum2; - } - - lighten(factor: number): Color { - return new Color(new HSLA(this.hsla.h, this.hsla.s, this.hsla.l + this.hsla.l * factor, this.hsla.a)); - } - - darken(factor: number): Color { - return new Color(new HSLA(this.hsla.h, this.hsla.s, this.hsla.l - this.hsla.l * factor, this.hsla.a)); - } - - transparent(factor: number): Color { - const { r, g, b, a } = this.rgba; - return new Color(new RGBA(r, g, b, a * factor)); - } - - isTransparent(): boolean { - return this.rgba.a === 0; - } - - isOpaque(): boolean { - return this.rgba.a === 1; - } - - opposite(): Color { - return new Color(new RGBA(255 - this.rgba.r, 255 - this.rgba.g, 255 - this.rgba.b, this.rgba.a)); - } - - blend(c: Color): Color { - const rgba = c.rgba; - - // Convert to 0..1 opacity - const thisA = this.rgba.a; - const colorA = rgba.a; - - const a = thisA + colorA * (1 - thisA); - if (a < 1e-6) { - return Color.transparent; - } - - const r = this.rgba.r * thisA / a + rgba.r * colorA * (1 - thisA) / a; - const g = this.rgba.g * thisA / a + rgba.g * colorA * (1 - thisA) / a; - const b = this.rgba.b * thisA / a + rgba.b * colorA * (1 - thisA) / a; - - return new Color(new RGBA(r, g, b, a)); - } - - makeOpaque(opaqueBackground: Color): Color { - if (this.isOpaque() || opaqueBackground.rgba.a !== 1) { - // only allow to blend onto a non-opaque color onto a opaque color - return this; - } - - const { r, g, b, a } = this.rgba; - - // https://stackoverflow.com/questions/12228548/finding-equivalent-color-with-opacity - return new Color(new RGBA( - opaqueBackground.rgba.r - a * (opaqueBackground.rgba.r - r), - opaqueBackground.rgba.g - a * (opaqueBackground.rgba.g - g), - opaqueBackground.rgba.b - a * (opaqueBackground.rgba.b - b), - 1 - )); - } - - flatten(...backgrounds: Color[]): Color { - const background = backgrounds.reduceRight((accumulator, color) => { - return Color._flatten(color, accumulator); - }); - return Color._flatten(this, background); - } - - private static _flatten(foreground: Color, background: Color) { - const backgroundAlpha = 1 - foreground.rgba.a; - return new Color(new RGBA( - backgroundAlpha * background.rgba.r + foreground.rgba.a * foreground.rgba.r, - backgroundAlpha * background.rgba.g + foreground.rgba.a * foreground.rgba.g, - backgroundAlpha * background.rgba.b + foreground.rgba.a * foreground.rgba.b - )); - } - - private _toString?: string; - toString(): string { - if (!this._toString) { - this._toString = Color.Format.CSS.format(this); - } - return this._toString; - } - - static getLighterColor(of: Color, relative: Color, factor?: number): Color { - if (of.isLighterThan(relative)) { - return of; - } - factor = factor ? factor : 0.5; - const lum1 = of.getRelativeLuminance(); - const lum2 = relative.getRelativeLuminance(); - factor = factor * (lum2 - lum1) / lum2; - return of.lighten(factor); - } - - static getDarkerColor(of: Color, relative: Color, factor?: number): Color { - if (of.isDarkerThan(relative)) { - return of; - } - factor = factor ? factor : 0.5; - const lum1 = of.getRelativeLuminance(); - const lum2 = relative.getRelativeLuminance(); - factor = factor * (lum1 - lum2) / lum1; - return of.darken(factor); - } - - static readonly white = new Color(new RGBA(255, 255, 255, 1)); - static readonly black = new Color(new RGBA(0, 0, 0, 1)); - static readonly red = new Color(new RGBA(255, 0, 0, 1)); - static readonly blue = new Color(new RGBA(0, 0, 255, 1)); - static readonly green = new Color(new RGBA(0, 255, 0, 1)); - static readonly cyan = new Color(new RGBA(0, 255, 255, 1)); - static readonly lightgrey = new Color(new RGBA(211, 211, 211, 1)); - static readonly transparent = new Color(new RGBA(0, 0, 0, 0)); -} - -export namespace Color { - export namespace Format { - export namespace CSS { - - export function formatRGB(color: Color): string { - if (color.rgba.a === 1) { - return `rgb(${color.rgba.r}, ${color.rgba.g}, ${color.rgba.b})`; - } - - return Color.Format.CSS.formatRGBA(color); - } - - export function formatRGBA(color: Color): string { - return `rgba(${color.rgba.r}, ${color.rgba.g}, ${color.rgba.b}, ${+(color.rgba.a).toFixed(2)})`; - } - - export function formatHSL(color: Color): string { - if (color.hsla.a === 1) { - return `hsl(${color.hsla.h}, ${(color.hsla.s * 100).toFixed(2)}%, ${(color.hsla.l * 100).toFixed(2)}%)`; - } - - return Color.Format.CSS.formatHSLA(color); - } - - export function formatHSLA(color: Color): string { - return `hsla(${color.hsla.h}, ${(color.hsla.s * 100).toFixed(2)}%, ${(color.hsla.l * 100).toFixed(2)}%, ${color.hsla.a.toFixed(2)})`; - } - - function _toTwoDigitHex(n: number): string { - const r = n.toString(16); - return r.length !== 2 ? '0' + r : r; - } - - /** - * Formats the color as #RRGGBB - */ - export function formatHex(color: Color): string { - return `#${_toTwoDigitHex(color.rgba.r)}${_toTwoDigitHex(color.rgba.g)}${_toTwoDigitHex(color.rgba.b)}`; - } - - /** - * Formats the color as #RRGGBBAA - * If 'compact' is set, colors without transparancy will be printed as #RRGGBB - */ - export function formatHexA(color: Color, compact = false): string { - if (compact && color.rgba.a === 1) { - return Color.Format.CSS.formatHex(color); - } - - return `#${_toTwoDigitHex(color.rgba.r)}${_toTwoDigitHex(color.rgba.g)}${_toTwoDigitHex(color.rgba.b)}${_toTwoDigitHex(Math.round(color.rgba.a * 255))}`; - } - - /** - * The default format will use HEX if opaque and RGBA otherwise. - */ - export function format(color: Color): string { - if (color.isOpaque()) { - return Color.Format.CSS.formatHex(color); - } - - return Color.Format.CSS.formatRGBA(color); - } - - /** - * Converts an Hex color value to a Color. - * returns r, g, and b are contained in the set [0, 255] - * @param hex string (#RGB, #RGBA, #RRGGBB or #RRGGBBAA). - */ - export function parseHex(hex: string): Color | null { - const length = hex.length; - - if (length === 0) { - // Invalid color - return null; - } - - if (hex.charCodeAt(0) !== CharCode.Hash) { - // Does not begin with a # - return null; - } - - if (length === 7) { - // #RRGGBB format - const r = 16 * _parseHexDigit(hex.charCodeAt(1)) + _parseHexDigit(hex.charCodeAt(2)); - const g = 16 * _parseHexDigit(hex.charCodeAt(3)) + _parseHexDigit(hex.charCodeAt(4)); - const b = 16 * _parseHexDigit(hex.charCodeAt(5)) + _parseHexDigit(hex.charCodeAt(6)); - return new Color(new RGBA(r, g, b, 1)); - } - - if (length === 9) { - // #RRGGBBAA format - const r = 16 * _parseHexDigit(hex.charCodeAt(1)) + _parseHexDigit(hex.charCodeAt(2)); - const g = 16 * _parseHexDigit(hex.charCodeAt(3)) + _parseHexDigit(hex.charCodeAt(4)); - const b = 16 * _parseHexDigit(hex.charCodeAt(5)) + _parseHexDigit(hex.charCodeAt(6)); - const a = 16 * _parseHexDigit(hex.charCodeAt(7)) + _parseHexDigit(hex.charCodeAt(8)); - return new Color(new RGBA(r, g, b, a / 255)); - } - - if (length === 4) { - // #RGB format - const r = _parseHexDigit(hex.charCodeAt(1)); - const g = _parseHexDigit(hex.charCodeAt(2)); - const b = _parseHexDigit(hex.charCodeAt(3)); - return new Color(new RGBA(16 * r + r, 16 * g + g, 16 * b + b)); - } - - if (length === 5) { - // #RGBA format - const r = _parseHexDigit(hex.charCodeAt(1)); - const g = _parseHexDigit(hex.charCodeAt(2)); - const b = _parseHexDigit(hex.charCodeAt(3)); - const a = _parseHexDigit(hex.charCodeAt(4)); - return new Color(new RGBA(16 * r + r, 16 * g + g, 16 * b + b, (16 * a + a) / 255)); - } - - // Invalid color - return null; - } - - function _parseHexDigit(charCode: CharCode): number { - switch (charCode) { - case CharCode.Digit0: return 0; - case CharCode.Digit1: return 1; - case CharCode.Digit2: return 2; - case CharCode.Digit3: return 3; - case CharCode.Digit4: return 4; - case CharCode.Digit5: return 5; - case CharCode.Digit6: return 6; - case CharCode.Digit7: return 7; - case CharCode.Digit8: return 8; - case CharCode.Digit9: return 9; - case CharCode.a: return 10; - case CharCode.A: return 10; - case CharCode.b: return 11; - case CharCode.B: return 11; - case CharCode.c: return 12; - case CharCode.C: return 12; - case CharCode.d: return 13; - case CharCode.D: return 13; - case CharCode.e: return 14; - case CharCode.E: return 14; - case CharCode.f: return 15; - case CharCode.F: return 15; - } - return 0; - } - } - } -} diff --git a/src/vs/base/common/comparers.ts b/src/vs/base/common/comparers.ts deleted file mode 100644 index e515abd6..00000000 --- a/src/vs/base/common/comparers.ts +++ /dev/null @@ -1,355 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Lazy } from 'vs/base/common/lazy'; -import { sep } from 'vs/base/common/path'; - -// When comparing large numbers of strings it's better for performance to create an -// Intl.Collator object and use the function provided by its compare property -// than it is to use String.prototype.localeCompare() - -// A collator with numeric sorting enabled, and no sensitivity to case, accents or diacritics. -const intlFileNameCollatorBaseNumeric: Lazy<{ collator: Intl.Collator; collatorIsNumeric: boolean }> = new Lazy(() => { - const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); - return { - collator, - collatorIsNumeric: collator.resolvedOptions().numeric - }; -}); - -// A collator with numeric sorting enabled. -const intlFileNameCollatorNumeric: Lazy<{ collator: Intl.Collator }> = new Lazy(() => { - const collator = new Intl.Collator(undefined, { numeric: true }); - return { - collator - }; -}); - -// A collator with numeric sorting enabled, and sensitivity to accents and diacritics but not case. -const intlFileNameCollatorNumericCaseInsensitive: Lazy<{ collator: Intl.Collator }> = new Lazy(() => { - const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'accent' }); - return { - collator - }; -}); - -/** Compares filenames without distinguishing the name from the extension. Disambiguates by unicode comparison. */ -export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number { - const a = one || ''; - const b = other || ''; - const result = intlFileNameCollatorBaseNumeric.value.collator.compare(a, b); - - // Using the numeric option will make compare(`foo1`, `foo01`) === 0. Disambiguate. - if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && a !== b) { - return a < b ? -1 : 1; - } - - return result; -} - -/** Compares full filenames without grouping by case. */ -export function compareFileNamesDefault(one: string | null, other: string | null): number { - const collatorNumeric = intlFileNameCollatorNumeric.value.collator; - one = one || ''; - other = other || ''; - - return compareAndDisambiguateByLength(collatorNumeric, one, other); -} - -/** Compares full filenames grouping uppercase names before lowercase. */ -export function compareFileNamesUpper(one: string | null, other: string | null) { - const collatorNumeric = intlFileNameCollatorNumeric.value.collator; - one = one || ''; - other = other || ''; - - return compareCaseUpperFirst(one, other) || compareAndDisambiguateByLength(collatorNumeric, one, other); -} - -/** Compares full filenames grouping lowercase names before uppercase. */ -export function compareFileNamesLower(one: string | null, other: string | null) { - const collatorNumeric = intlFileNameCollatorNumeric.value.collator; - one = one || ''; - other = other || ''; - - return compareCaseLowerFirst(one, other) || compareAndDisambiguateByLength(collatorNumeric, one, other); -} - -/** Compares full filenames by unicode value. */ -export function compareFileNamesUnicode(one: string | null, other: string | null) { - one = one || ''; - other = other || ''; - - if (one === other) { - return 0; - } - - return one < other ? -1 : 1; -} - -/** Compares filenames by extension, then by name. Disambiguates by unicode comparison. */ -export function compareFileExtensions(one: string | null, other: string | null): number { - const [oneName, oneExtension] = extractNameAndExtension(one); - const [otherName, otherExtension] = extractNameAndExtension(other); - - let result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneExtension, otherExtension); - - if (result === 0) { - // Using the numeric option will make compare(`foo1`, `foo01`) === 0. Disambiguate. - if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && oneExtension !== otherExtension) { - return oneExtension < otherExtension ? -1 : 1; - } - - // Extensions are equal, compare filenames - result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneName, otherName); - - if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && oneName !== otherName) { - return oneName < otherName ? -1 : 1; - } - } - - return result; -} - -/** Compares filenames by extension, then by full filename. Mixes uppercase and lowercase names together. */ -export function compareFileExtensionsDefault(one: string | null, other: string | null): number { - one = one || ''; - other = other || ''; - const oneExtension = extractExtension(one); - const otherExtension = extractExtension(other); - const collatorNumeric = intlFileNameCollatorNumeric.value.collator; - const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsensitive.value.collator; - - return compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension) || - compareAndDisambiguateByLength(collatorNumeric, one, other); -} - -/** Compares filenames by extension, then case, then full filename. Groups uppercase names before lowercase. */ -export function compareFileExtensionsUpper(one: string | null, other: string | null): number { - one = one || ''; - other = other || ''; - const oneExtension = extractExtension(one); - const otherExtension = extractExtension(other); - const collatorNumeric = intlFileNameCollatorNumeric.value.collator; - const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsensitive.value.collator; - - return compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension) || - compareCaseUpperFirst(one, other) || - compareAndDisambiguateByLength(collatorNumeric, one, other); -} - -/** Compares filenames by extension, then case, then full filename. Groups lowercase names before uppercase. */ -export function compareFileExtensionsLower(one: string | null, other: string | null): number { - one = one || ''; - other = other || ''; - const oneExtension = extractExtension(one); - const otherExtension = extractExtension(other); - const collatorNumeric = intlFileNameCollatorNumeric.value.collator; - const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsensitive.value.collator; - - return compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension) || - compareCaseLowerFirst(one, other) || - compareAndDisambiguateByLength(collatorNumeric, one, other); -} - -/** Compares filenames by case-insensitive extension unicode value, then by full filename unicode value. */ -export function compareFileExtensionsUnicode(one: string | null, other: string | null) { - one = one || ''; - other = other || ''; - const oneExtension = extractExtension(one).toLowerCase(); - const otherExtension = extractExtension(other).toLowerCase(); - - // Check for extension differences - if (oneExtension !== otherExtension) { - return oneExtension < otherExtension ? -1 : 1; - } - - // Check for full filename differences. - if (one !== other) { - return one < other ? -1 : 1; - } - - return 0; -} - -const FileNameMatch = /^(.*?)(\.([^.]*))?$/; - -/** Extracts the name and extension from a full filename, with optional special handling for dotfiles */ -function extractNameAndExtension(str?: string | null, dotfilesAsNames = false): [string, string] { - const match = str ? FileNameMatch.exec(str) as Array : ([] as Array); - - let result: [string, string] = [(match && match[1]) || '', (match && match[3]) || '']; - - // if the dotfilesAsNames option is selected, treat an empty filename with an extension - // or a filename that starts with a dot, as a dotfile name - if (dotfilesAsNames && (!result[0] && result[1] || result[0] && result[0].charAt(0) === '.')) { - result = [result[0] + '.' + result[1], '']; - } - - return result; -} - -/** Extracts the extension from a full filename. Treats dotfiles as names, not extensions. */ -function extractExtension(str?: string | null): string { - const match = str ? FileNameMatch.exec(str) as Array : ([] as Array); - - return (match && match[1] && match[1].charAt(0) !== '.' && match[3]) || ''; -} - -function compareAndDisambiguateByLength(collator: Intl.Collator, one: string, other: string) { - // Check for differences - const result = collator.compare(one, other); - if (result !== 0) { - return result; - } - - // In a numeric comparison, `foo1` and `foo01` will compare as equivalent. - // Disambiguate by sorting the shorter string first. - if (one.length !== other.length) { - return one.length < other.length ? -1 : 1; - } - - return 0; -} - -/** @returns `true` if the string is starts with a lowercase letter. Otherwise, `false`. */ -function startsWithLower(string: string) { - const character = string.charAt(0); - - return (character.toLocaleUpperCase() !== character) ? true : false; -} - -/** @returns `true` if the string starts with an uppercase letter. Otherwise, `false`. */ -function startsWithUpper(string: string) { - const character = string.charAt(0); - - return (character.toLocaleLowerCase() !== character) ? true : false; -} - -/** - * Compares the case of the provided strings - lowercase before uppercase - * - * @returns - * ```text - * -1 if one is lowercase and other is uppercase - * 1 if one is uppercase and other is lowercase - * 0 otherwise - * ``` - */ -function compareCaseLowerFirst(one: string, other: string): number { - if (startsWithLower(one) && startsWithUpper(other)) { - return -1; - } - return (startsWithUpper(one) && startsWithLower(other)) ? 1 : 0; -} - -/** - * Compares the case of the provided strings - uppercase before lowercase - * - * @returns - * ```text - * -1 if one is uppercase and other is lowercase - * 1 if one is lowercase and other is uppercase - * 0 otherwise - * ``` - */ -function compareCaseUpperFirst(one: string, other: string): number { - if (startsWithUpper(one) && startsWithLower(other)) { - return -1; - } - return (startsWithLower(one) && startsWithUpper(other)) ? 1 : 0; -} - -function comparePathComponents(one: string, other: string, caseSensitive = false): number { - if (!caseSensitive) { - one = one && one.toLowerCase(); - other = other && other.toLowerCase(); - } - - if (one === other) { - return 0; - } - - return one < other ? -1 : 1; -} - -export function comparePaths(one: string, other: string, caseSensitive = false): number { - const oneParts = one.split(sep); - const otherParts = other.split(sep); - - const lastOne = oneParts.length - 1; - const lastOther = otherParts.length - 1; - let endOne: boolean, endOther: boolean; - - for (let i = 0; ; i++) { - endOne = lastOne === i; - endOther = lastOther === i; - - if (endOne && endOther) { - return compareFileNames(oneParts[i], otherParts[i], caseSensitive); - } else if (endOne) { - return -1; - } else if (endOther) { - return 1; - } - - const result = comparePathComponents(oneParts[i], otherParts[i], caseSensitive); - - if (result !== 0) { - return result; - } - } -} - -export function compareAnything(one: string, other: string, lookFor: string): number { - const elementAName = one.toLowerCase(); - const elementBName = other.toLowerCase(); - - // Sort prefix matches over non prefix matches - const prefixCompare = compareByPrefix(one, other, lookFor); - if (prefixCompare) { - return prefixCompare; - } - - // Sort suffix matches over non suffix matches - const elementASuffixMatch = elementAName.endsWith(lookFor); - const elementBSuffixMatch = elementBName.endsWith(lookFor); - if (elementASuffixMatch !== elementBSuffixMatch) { - return elementASuffixMatch ? -1 : 1; - } - - // Understand file names - const r = compareFileNames(elementAName, elementBName); - if (r !== 0) { - return r; - } - - // Compare by name - return elementAName.localeCompare(elementBName); -} - -export function compareByPrefix(one: string, other: string, lookFor: string): number { - const elementAName = one.toLowerCase(); - const elementBName = other.toLowerCase(); - - // Sort prefix matches over non prefix matches - const elementAPrefixMatch = elementAName.startsWith(lookFor); - const elementBPrefixMatch = elementBName.startsWith(lookFor); - if (elementAPrefixMatch !== elementBPrefixMatch) { - return elementAPrefixMatch ? -1 : 1; - } - - // Same prefix: Sort shorter matches to the top to have those on top that match more precisely - else if (elementAPrefixMatch && elementBPrefixMatch) { - if (elementAName.length < elementBName.length) { - return -1; - } - - if (elementAName.length > elementBName.length) { - return 1; - } - } - - return 0; -} diff --git a/src/vs/base/common/controlFlow.ts b/src/vs/base/common/controlFlow.ts deleted file mode 100644 index 2c4d020d..00000000 --- a/src/vs/base/common/controlFlow.ts +++ /dev/null @@ -1,69 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { BugIndicatingError } from 'vs/base/common/errors'; - -/* - * This file contains helper classes to manage control flow. -*/ - -/** - * Prevents code from being re-entrant. -*/ -export class ReentrancyBarrier { - private _isOccupied = false; - - /** - * Calls `runner` if the barrier is not occupied. - * During the call, the barrier becomes occupied. - */ - public runExclusivelyOrSkip(runner: () => void): void { - if (this._isOccupied) { - return; - } - this._isOccupied = true; - try { - runner(); - } finally { - this._isOccupied = false; - } - } - - /** - * Calls `runner`. If the barrier is occupied, throws an error. - * During the call, the barrier becomes active. - */ - public runExclusivelyOrThrow(runner: () => void): void { - if (this._isOccupied) { - throw new BugIndicatingError(`ReentrancyBarrier: reentrant call detected!`); - } - this._isOccupied = true; - try { - runner(); - } finally { - this._isOccupied = false; - } - } - - /** - * Indicates if some runner occupies this barrier. - */ - public get isOccupied() { - return this._isOccupied; - } - - public makeExclusiveOrSkip(fn: TFunction): TFunction { - return ((...args: any[]) => { - if (this._isOccupied) { - return; - } - this._isOccupied = true; - try { - return fn(...args); - } finally { - this._isOccupied = false; - } - }) as any; - } -} diff --git a/src/vs/base/common/date.ts b/src/vs/base/common/date.ts deleted file mode 100644 index 2ae03a71..00000000 --- a/src/vs/base/common/date.ts +++ /dev/null @@ -1,242 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { localize } from 'vs/nls'; - -const minute = 60; -const hour = minute * 60; -const day = hour * 24; -const week = day * 7; -const month = day * 30; -const year = day * 365; - -/** - * Create a localized difference of the time between now and the specified date. - * @param date The date to generate the difference from. - * @param appendAgoLabel Whether to append the " ago" to the end. - * @param useFullTimeWords Whether to use full words (eg. seconds) instead of - * shortened (eg. secs). - * @param disallowNow Whether to disallow the string "now" when the difference - * is less than 30 seconds. - */ -export function fromNow(date: number | Date, appendAgoLabel?: boolean, useFullTimeWords?: boolean, disallowNow?: boolean): string { - if (typeof date !== 'number') { - date = date.getTime(); - } - - const seconds = Math.round((new Date().getTime() - date) / 1000); - if (seconds < -30) { - return localize('date.fromNow.in', 'in {0}', fromNow(new Date().getTime() + seconds * 1000, false)); - } - - if (!disallowNow && seconds < 30) { - return localize('date.fromNow.now', 'now'); - } - - let value: number; - if (seconds < minute) { - value = seconds; - - if (appendAgoLabel) { - if (value === 1) { - return useFullTimeWords - ? localize('date.fromNow.seconds.singular.ago.fullWord', '{0} second ago', value) - : localize('date.fromNow.seconds.singular.ago', '{0} sec ago', value); - } else { - return useFullTimeWords - ? localize('date.fromNow.seconds.plural.ago.fullWord', '{0} seconds ago', value) - : localize('date.fromNow.seconds.plural.ago', '{0} secs ago', value); - } - } else { - if (value === 1) { - return useFullTimeWords - ? localize('date.fromNow.seconds.singular.fullWord', '{0} second', value) - : localize('date.fromNow.seconds.singular', '{0} sec', value); - } else { - return useFullTimeWords - ? localize('date.fromNow.seconds.plural.fullWord', '{0} seconds', value) - : localize('date.fromNow.seconds.plural', '{0} secs', value); - } - } - } - - if (seconds < hour) { - value = Math.floor(seconds / minute); - if (appendAgoLabel) { - if (value === 1) { - return useFullTimeWords - ? localize('date.fromNow.minutes.singular.ago.fullWord', '{0} minute ago', value) - : localize('date.fromNow.minutes.singular.ago', '{0} min ago', value); - } else { - return useFullTimeWords - ? localize('date.fromNow.minutes.plural.ago.fullWord', '{0} minutes ago', value) - : localize('date.fromNow.minutes.plural.ago', '{0} mins ago', value); - } - } else { - if (value === 1) { - return useFullTimeWords - ? localize('date.fromNow.minutes.singular.fullWord', '{0} minute', value) - : localize('date.fromNow.minutes.singular', '{0} min', value); - } else { - return useFullTimeWords - ? localize('date.fromNow.minutes.plural.fullWord', '{0} minutes', value) - : localize('date.fromNow.minutes.plural', '{0} mins', value); - } - } - } - - if (seconds < day) { - value = Math.floor(seconds / hour); - if (appendAgoLabel) { - if (value === 1) { - return useFullTimeWords - ? localize('date.fromNow.hours.singular.ago.fullWord', '{0} hour ago', value) - : localize('date.fromNow.hours.singular.ago', '{0} hr ago', value); - } else { - return useFullTimeWords - ? localize('date.fromNow.hours.plural.ago.fullWord', '{0} hours ago', value) - : localize('date.fromNow.hours.plural.ago', '{0} hrs ago', value); - } - } else { - if (value === 1) { - return useFullTimeWords - ? localize('date.fromNow.hours.singular.fullWord', '{0} hour', value) - : localize('date.fromNow.hours.singular', '{0} hr', value); - } else { - return useFullTimeWords - ? localize('date.fromNow.hours.plural.fullWord', '{0} hours', value) - : localize('date.fromNow.hours.plural', '{0} hrs', value); - } - } - } - - if (seconds < week) { - value = Math.floor(seconds / day); - if (appendAgoLabel) { - return value === 1 - ? localize('date.fromNow.days.singular.ago', '{0} day ago', value) - : localize('date.fromNow.days.plural.ago', '{0} days ago', value); - } else { - return value === 1 - ? localize('date.fromNow.days.singular', '{0} day', value) - : localize('date.fromNow.days.plural', '{0} days', value); - } - } - - if (seconds < month) { - value = Math.floor(seconds / week); - if (appendAgoLabel) { - if (value === 1) { - return useFullTimeWords - ? localize('date.fromNow.weeks.singular.ago.fullWord', '{0} week ago', value) - : localize('date.fromNow.weeks.singular.ago', '{0} wk ago', value); - } else { - return useFullTimeWords - ? localize('date.fromNow.weeks.plural.ago.fullWord', '{0} weeks ago', value) - : localize('date.fromNow.weeks.plural.ago', '{0} wks ago', value); - } - } else { - if (value === 1) { - return useFullTimeWords - ? localize('date.fromNow.weeks.singular.fullWord', '{0} week', value) - : localize('date.fromNow.weeks.singular', '{0} wk', value); - } else { - return useFullTimeWords - ? localize('date.fromNow.weeks.plural.fullWord', '{0} weeks', value) - : localize('date.fromNow.weeks.plural', '{0} wks', value); - } - } - } - - if (seconds < year) { - value = Math.floor(seconds / month); - if (appendAgoLabel) { - if (value === 1) { - return useFullTimeWords - ? localize('date.fromNow.months.singular.ago.fullWord', '{0} month ago', value) - : localize('date.fromNow.months.singular.ago', '{0} mo ago', value); - } else { - return useFullTimeWords - ? localize('date.fromNow.months.plural.ago.fullWord', '{0} months ago', value) - : localize('date.fromNow.months.plural.ago', '{0} mos ago', value); - } - } else { - if (value === 1) { - return useFullTimeWords - ? localize('date.fromNow.months.singular.fullWord', '{0} month', value) - : localize('date.fromNow.months.singular', '{0} mo', value); - } else { - return useFullTimeWords - ? localize('date.fromNow.months.plural.fullWord', '{0} months', value) - : localize('date.fromNow.months.plural', '{0} mos', value); - } - } - } - - value = Math.floor(seconds / year); - if (appendAgoLabel) { - if (value === 1) { - return useFullTimeWords - ? localize('date.fromNow.years.singular.ago.fullWord', '{0} year ago', value) - : localize('date.fromNow.years.singular.ago', '{0} yr ago', value); - } else { - return useFullTimeWords - ? localize('date.fromNow.years.plural.ago.fullWord', '{0} years ago', value) - : localize('date.fromNow.years.plural.ago', '{0} yrs ago', value); - } - } else { - if (value === 1) { - return useFullTimeWords - ? localize('date.fromNow.years.singular.fullWord', '{0} year', value) - : localize('date.fromNow.years.singular', '{0} yr', value); - } else { - return useFullTimeWords - ? localize('date.fromNow.years.plural.fullWord', '{0} years', value) - : localize('date.fromNow.years.plural', '{0} yrs', value); - } - } -} - -/** - * Gets a readable duration with intelligent/lossy precision. For example "40ms" or "3.040s") - * @param ms The duration to get in milliseconds. - * @param useFullTimeWords Whether to use full words (eg. seconds) instead of - * shortened (eg. secs). - */ -export function getDurationString(ms: number, useFullTimeWords?: boolean) { - const seconds = Math.abs(ms / 1000); - if (seconds < 1) { - return useFullTimeWords - ? localize('duration.ms.full', '{0} milliseconds', ms) - : localize('duration.ms', '{0}ms', ms); - } - if (seconds < minute) { - return useFullTimeWords - ? localize('duration.s.full', '{0} seconds', Math.round(ms) / 1000) - : localize('duration.s', '{0}s', Math.round(ms) / 1000); - } - if (seconds < hour) { - return useFullTimeWords - ? localize('duration.m.full', '{0} minutes', Math.round(ms / (1000 * minute))) - : localize('duration.m', '{0} mins', Math.round(ms / (1000 * minute))); - } - if (seconds < day) { - return useFullTimeWords - ? localize('duration.h.full', '{0} hours', Math.round(ms / (1000 * hour))) - : localize('duration.h', '{0} hrs', Math.round(ms / (1000 * hour))); - } - return localize('duration.d', '{0} days', Math.round(ms / (1000 * day))); -} - -export function toLocalISOString(date: Date): string { - return date.getFullYear() + - '-' + String(date.getMonth() + 1).padStart(2, '0') + - '-' + String(date.getDate()).padStart(2, '0') + - 'T' + String(date.getHours()).padStart(2, '0') + - ':' + String(date.getMinutes()).padStart(2, '0') + - ':' + String(date.getSeconds()).padStart(2, '0') + - '.' + (date.getMilliseconds() / 1000).toFixed(3).slice(2, 5) + - 'Z'; -} diff --git a/src/vs/base/common/desktopEnvironmentInfo.ts b/src/vs/base/common/desktopEnvironmentInfo.ts deleted file mode 100644 index b6e4c107..00000000 --- a/src/vs/base/common/desktopEnvironmentInfo.ts +++ /dev/null @@ -1,101 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { env } from 'vs/base/common/process'; - -// Define the enumeration for Desktop Environments -enum DesktopEnvironment { - UNKNOWN = 'UNKNOWN', - CINNAMON = 'CINNAMON', - DEEPIN = 'DEEPIN', - GNOME = 'GNOME', - KDE3 = 'KDE3', - KDE4 = 'KDE4', - KDE5 = 'KDE5', - KDE6 = 'KDE6', - PANTHEON = 'PANTHEON', - UNITY = 'UNITY', - XFCE = 'XFCE', - UKUI = 'UKUI', - LXQT = 'LXQT', -} - -const kXdgCurrentDesktopEnvVar = 'XDG_CURRENT_DESKTOP'; -const kKDESessionEnvVar = 'KDE_SESSION_VERSION'; - -export function getDesktopEnvironment(): DesktopEnvironment { - const xdgCurrentDesktop = env[kXdgCurrentDesktopEnvVar]; - if (xdgCurrentDesktop) { - const values = xdgCurrentDesktop.split(':').map(value => value.trim()).filter(value => value.length > 0); - for (const value of values) { - switch (value) { - case 'Unity': { - const desktopSessionUnity = env['DESKTOP_SESSION']; - if (desktopSessionUnity && desktopSessionUnity.includes('gnome-fallback')) { - return DesktopEnvironment.GNOME; - } - - return DesktopEnvironment.UNITY; - } - case 'Deepin': - return DesktopEnvironment.DEEPIN; - case 'GNOME': - return DesktopEnvironment.GNOME; - case 'X-Cinnamon': - return DesktopEnvironment.CINNAMON; - case 'KDE': { - const kdeSession = env[kKDESessionEnvVar]; - if (kdeSession === '5') { return DesktopEnvironment.KDE5; } - if (kdeSession === '6') { return DesktopEnvironment.KDE6; } - return DesktopEnvironment.KDE4; - } - case 'Pantheon': - return DesktopEnvironment.PANTHEON; - case 'XFCE': - return DesktopEnvironment.XFCE; - case 'UKUI': - return DesktopEnvironment.UKUI; - case 'LXQt': - return DesktopEnvironment.LXQT; - } - } - } - - const desktopSession = env['DESKTOP_SESSION']; - if (desktopSession) { - switch (desktopSession) { - case 'deepin': - return DesktopEnvironment.DEEPIN; - case 'gnome': - case 'mate': - return DesktopEnvironment.GNOME; - case 'kde4': - case 'kde-plasma': - return DesktopEnvironment.KDE4; - case 'kde': - if (kKDESessionEnvVar in env) { - return DesktopEnvironment.KDE4; - } - return DesktopEnvironment.KDE3; - case 'xfce': - case 'xubuntu': - return DesktopEnvironment.XFCE; - case 'ukui': - return DesktopEnvironment.UKUI; - } - } - - if ('GNOME_DESKTOP_SESSION_ID' in env) { - return DesktopEnvironment.GNOME; - } - if ('KDE_FULL_SESSION' in env) { - if (kKDESessionEnvVar in env) { - return DesktopEnvironment.KDE4; - } - return DesktopEnvironment.KDE3; - } - - return DesktopEnvironment.UNKNOWN; -} diff --git a/src/vs/base/common/errorMessage.ts b/src/vs/base/common/errorMessage.ts deleted file mode 100644 index f16616da..00000000 --- a/src/vs/base/common/errorMessage.ts +++ /dev/null @@ -1,113 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as arrays from 'vs/base/common/arrays'; -import * as types from 'vs/base/common/types'; -import * as nls from 'vs/nls'; -import { IAction } from 'vs/base/common/actions'; - -function exceptionToErrorMessage(exception: any, verbose: boolean): string { - if (verbose && (exception.stack || exception.stacktrace)) { - return nls.localize('stackTrace.format', "{0}: {1}", detectSystemErrorMessage(exception), stackToString(exception.stack) || stackToString(exception.stacktrace)); - } - - return detectSystemErrorMessage(exception); -} - -function stackToString(stack: string[] | string | undefined): string | undefined { - if (Array.isArray(stack)) { - return stack.join('\n'); - } - - return stack; -} - -function detectSystemErrorMessage(exception: any): string { - - // Custom node.js error from us - if (exception.code === 'ERR_UNC_HOST_NOT_ALLOWED') { - return `${exception.message}. Please update the 'security.allowedUNCHosts' setting if you want to allow this host.`; - } - - // See https://nodejs.org/api/errors.html#errors_class_system_error - if (typeof exception.code === 'string' && typeof exception.errno === 'number' && typeof exception.syscall === 'string') { - return nls.localize('nodeExceptionMessage', "A system error occurred ({0})", exception.message); - } - - return exception.message || nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details."); -} - -/** - * Tries to generate a human readable error message out of the error. If the verbose parameter - * is set to true, the error message will include stacktrace details if provided. - * - * @returns A string containing the error message. - */ -export function toErrorMessage(error: any = null, verbose: boolean = false): string { - if (!error) { - return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details."); - } - - if (Array.isArray(error)) { - const errors: any[] = arrays.coalesce(error); - const msg = toErrorMessage(errors[0], verbose); - - if (errors.length > 1) { - return nls.localize('error.moreErrors', "{0} ({1} errors in total)", msg, errors.length); - } - - return msg; - } - - if (types.isString(error)) { - return error; - } - - if (error.detail) { - const detail = error.detail; - - if (detail.error) { - return exceptionToErrorMessage(detail.error, verbose); - } - - if (detail.exception) { - return exceptionToErrorMessage(detail.exception, verbose); - } - } - - if (error.stack) { - return exceptionToErrorMessage(error, verbose); - } - - if (error.message) { - return error.message; - } - - return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details."); -} - - -export interface IErrorWithActions extends Error { - actions: IAction[]; -} - -export function isErrorWithActions(obj: unknown): obj is IErrorWithActions { - const candidate = obj as IErrorWithActions | undefined; - - return candidate instanceof Error && Array.isArray(candidate.actions); -} - -export function createErrorWithActions(messageOrError: string | Error, actions: IAction[]): IErrorWithActions { - let error: IErrorWithActions; - if (typeof messageOrError === 'string') { - error = new Error(messageOrError) as IErrorWithActions; - } else { - error = messageOrError as IErrorWithActions; - } - - error.actions = actions; - - return error; -} diff --git a/src/vs/base/common/extpath.ts b/src/vs/base/common/extpath.ts deleted file mode 100644 index e0ee6968..00000000 --- a/src/vs/base/common/extpath.ts +++ /dev/null @@ -1,423 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CharCode } from 'vs/base/common/charCode'; -import { isAbsolute, join, normalize, posix, sep } from 'vs/base/common/path'; -import { isWindows } from 'vs/base/common/platform'; -import { equalsIgnoreCase, rtrim, startsWithIgnoreCase } from 'vs/base/common/strings'; -import { isNumber } from 'vs/base/common/types'; - -export function isPathSeparator(code: number) { - return code === CharCode.Slash || code === CharCode.Backslash; -} - -/** - * Takes a Windows OS path and changes backward slashes to forward slashes. - * This should only be done for OS paths from Windows (or user provided paths potentially from Windows). - * Using it on a Linux or MaxOS path might change it. - */ -export function toSlashes(osPath: string) { - return osPath.replace(/[\\/]/g, posix.sep); -} - -/** - * Takes a Windows OS path (using backward or forward slashes) and turns it into a posix path: - * - turns backward slashes into forward slashes - * - makes it absolute if it starts with a drive letter - * This should only be done for OS paths from Windows (or user provided paths potentially from Windows). - * Using it on a Linux or MaxOS path might change it. - */ -export function toPosixPath(osPath: string) { - if (osPath.indexOf('/') === -1) { - osPath = toSlashes(osPath); - } - if (/^[a-zA-Z]:(\/|$)/.test(osPath)) { // starts with a drive letter - osPath = '/' + osPath; - } - return osPath; -} - -/** - * Computes the _root_ this path, like `getRoot('c:\files') === c:\`, - * `getRoot('files:///files/path') === files:///`, - * or `getRoot('\\server\shares\path') === \\server\shares\` - */ -export function getRoot(path: string, sep: string = posix.sep): string { - if (!path) { - return ''; - } - - const len = path.length; - const firstLetter = path.charCodeAt(0); - if (isPathSeparator(firstLetter)) { - if (isPathSeparator(path.charCodeAt(1))) { - // UNC candidate \\localhost\shares\ddd - // ^^^^^^^^^^^^^^^^^^^ - if (!isPathSeparator(path.charCodeAt(2))) { - let pos = 3; - const start = pos; - for (; pos < len; pos++) { - if (isPathSeparator(path.charCodeAt(pos))) { - break; - } - } - if (start !== pos && !isPathSeparator(path.charCodeAt(pos + 1))) { - pos += 1; - for (; pos < len; pos++) { - if (isPathSeparator(path.charCodeAt(pos))) { - return path.slice(0, pos + 1) // consume this separator - .replace(/[\\/]/g, sep); - } - } - } - } - } - - // /user/far - // ^ - return sep; - - } else if (isWindowsDriveLetter(firstLetter)) { - // check for windows drive letter c:\ or c: - - if (path.charCodeAt(1) === CharCode.Colon) { - if (isPathSeparator(path.charCodeAt(2))) { - // C:\fff - // ^^^ - return path.slice(0, 2) + sep; - } else { - // C: - // ^^ - return path.slice(0, 2); - } - } - } - - // check for URI - // scheme://authority/path - // ^^^^^^^^^^^^^^^^^^^ - let pos = path.indexOf('://'); - if (pos !== -1) { - pos += 3; // 3 -> "://".length - for (; pos < len; pos++) { - if (isPathSeparator(path.charCodeAt(pos))) { - return path.slice(0, pos + 1); // consume this separator - } - } - } - - return ''; -} - -/** - * Check if the path follows this pattern: `\\hostname\sharename`. - * - * @see https://msdn.microsoft.com/en-us/library/gg465305.aspx - * @return A boolean indication if the path is a UNC path, on none-windows - * always false. - */ -export function isUNC(path: string): boolean { - if (!isWindows) { - // UNC is a windows concept - return false; - } - - if (!path || path.length < 5) { - // at least \\a\b - return false; - } - - let code = path.charCodeAt(0); - if (code !== CharCode.Backslash) { - return false; - } - - code = path.charCodeAt(1); - - if (code !== CharCode.Backslash) { - return false; - } - - let pos = 2; - const start = pos; - for (; pos < path.length; pos++) { - code = path.charCodeAt(pos); - if (code === CharCode.Backslash) { - break; - } - } - - if (start === pos) { - return false; - } - - code = path.charCodeAt(pos + 1); - - if (isNaN(code) || code === CharCode.Backslash) { - return false; - } - - return true; -} - -// Reference: https://en.wikipedia.org/wiki/Filename -const WINDOWS_INVALID_FILE_CHARS = /[\\/:\*\?"<>\|]/g; -const UNIX_INVALID_FILE_CHARS = /[/]/g; -const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])(\.(.*?))?$/i; -export function isValidBasename(name: string | null | undefined, isWindowsOS: boolean = isWindows): boolean { - const invalidFileChars = isWindowsOS ? WINDOWS_INVALID_FILE_CHARS : UNIX_INVALID_FILE_CHARS; - - if (!name || name.length === 0 || /^\s+$/.test(name)) { - return false; // require a name that is not just whitespace - } - - invalidFileChars.lastIndex = 0; // the holy grail of software development - if (invalidFileChars.test(name)) { - return false; // check for certain invalid file characters - } - - if (isWindowsOS && WINDOWS_FORBIDDEN_NAMES.test(name)) { - return false; // check for certain invalid file names - } - - if (name === '.' || name === '..') { - return false; // check for reserved values - } - - if (isWindowsOS && name[name.length - 1] === '.') { - return false; // Windows: file cannot end with a "." - } - - if (isWindowsOS && name.length !== name.trim().length) { - return false; // Windows: file cannot end with a whitespace - } - - if (name.length > 255) { - return false; // most file systems do not allow files > 255 length - } - - return true; -} - -/** - * @deprecated please use `IUriIdentityService.extUri.isEqual` instead. If you are - * in a context without services, consider to pass down the `extUri` from the outside - * or use `extUriBiasedIgnorePathCase` if you know what you are doing. - */ -export function isEqual(pathA: string, pathB: string, ignoreCase?: boolean): boolean { - const identityEquals = (pathA === pathB); - if (!ignoreCase || identityEquals) { - return identityEquals; - } - - if (!pathA || !pathB) { - return false; - } - - return equalsIgnoreCase(pathA, pathB); -} - -/** - * @deprecated please use `IUriIdentityService.extUri.isEqualOrParent` instead. If - * you are in a context without services, consider to pass down the `extUri` from the - * outside, or use `extUriBiasedIgnorePathCase` if you know what you are doing. - */ -export function isEqualOrParent(base: string, parentCandidate: string, ignoreCase?: boolean, separator = sep): boolean { - if (base === parentCandidate) { - return true; - } - - if (!base || !parentCandidate) { - return false; - } - - if (parentCandidate.length > base.length) { - return false; - } - - if (ignoreCase) { - const beginsWith = startsWithIgnoreCase(base, parentCandidate); - if (!beginsWith) { - return false; - } - - if (parentCandidate.length === base.length) { - return true; // same path, different casing - } - - let sepOffset = parentCandidate.length; - if (parentCandidate.charAt(parentCandidate.length - 1) === separator) { - sepOffset--; // adjust the expected sep offset in case our candidate already ends in separator character - } - - return base.charAt(sepOffset) === separator; - } - - if (parentCandidate.charAt(parentCandidate.length - 1) !== separator) { - parentCandidate += separator; - } - - return base.indexOf(parentCandidate) === 0; -} - -export function isWindowsDriveLetter(char0: number): boolean { - return char0 >= CharCode.A && char0 <= CharCode.Z || char0 >= CharCode.a && char0 <= CharCode.z; -} - -export function sanitizeFilePath(candidate: string, cwd: string): string { - - // Special case: allow to open a drive letter without trailing backslash - if (isWindows && candidate.endsWith(':')) { - candidate += sep; - } - - // Ensure absolute - if (!isAbsolute(candidate)) { - candidate = join(cwd, candidate); - } - - // Ensure normalized - candidate = normalize(candidate); - - // Ensure no trailing slash/backslash - return removeTrailingPathSeparator(candidate); -} - -export function removeTrailingPathSeparator(candidate: string): string { - if (isWindows) { - candidate = rtrim(candidate, sep); - - // Special case: allow to open drive root ('C:\') - if (candidate.endsWith(':')) { - candidate += sep; - } - - } else { - candidate = rtrim(candidate, sep); - - // Special case: allow to open root ('/') - if (!candidate) { - candidate = sep; - } - } - - return candidate; -} - -export function isRootOrDriveLetter(path: string): boolean { - const pathNormalized = normalize(path); - - if (isWindows) { - if (path.length > 3) { - return false; - } - - return hasDriveLetter(pathNormalized) && - (path.length === 2 || pathNormalized.charCodeAt(2) === CharCode.Backslash); - } - - return pathNormalized === posix.sep; -} - -export function hasDriveLetter(path: string, isWindowsOS: boolean = isWindows): boolean { - if (isWindowsOS) { - return isWindowsDriveLetter(path.charCodeAt(0)) && path.charCodeAt(1) === CharCode.Colon; - } - - return false; -} - -export function getDriveLetter(path: string, isWindowsOS: boolean = isWindows): string | undefined { - return hasDriveLetter(path, isWindowsOS) ? path[0] : undefined; -} - -export function indexOfPath(path: string, candidate: string, ignoreCase?: boolean): number { - if (candidate.length > path.length) { - return -1; - } - - if (path === candidate) { - return 0; - } - - if (ignoreCase) { - path = path.toLowerCase(); - candidate = candidate.toLowerCase(); - } - - return path.indexOf(candidate); -} - -export interface IPathWithLineAndColumn { - path: string; - line?: number; - column?: number; -} - -export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn { - const segments = rawPath.split(':'); // C:\file.txt:: - - let path: string | undefined = undefined; - let line: number | undefined = undefined; - let column: number | undefined = undefined; - - for (const segment of segments) { - const segmentAsNumber = Number(segment); - if (!isNumber(segmentAsNumber)) { - path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...) - } else if (line === undefined) { - line = segmentAsNumber; - } else if (column === undefined) { - column = segmentAsNumber; - } - } - - if (!path) { - throw new Error('Format for `--goto` should be: `FILE:LINE(:COLUMN)`'); - } - - return { - path, - line: line !== undefined ? line : undefined, - column: column !== undefined ? column : line !== undefined ? 1 : undefined // if we have a line, make sure column is also set - }; -} - -const pathChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; -const windowsSafePathFirstChars = 'BDEFGHIJKMOQRSTUVWXYZbdefghijkmoqrstuvwxyz0123456789'; - -export function randomPath(parent?: string, prefix?: string, randomLength = 8): string { - let suffix = ''; - for (let i = 0; i < randomLength; i++) { - let pathCharsTouse: string; - if (i === 0 && isWindows && !prefix && (randomLength === 3 || randomLength === 4)) { - - // Windows has certain reserved file names that cannot be used, such - // as AUX, CON, PRN, etc. We want to avoid generating a random name - // that matches that pattern, so we use a different set of characters - // for the first character of the name that does not include any of - // the reserved names first characters. - - pathCharsTouse = windowsSafePathFirstChars; - } else { - pathCharsTouse = pathChars; - } - - suffix += pathCharsTouse.charAt(Math.floor(Math.random() * pathCharsTouse.length)); - } - - let randomFileName: string; - if (prefix) { - randomFileName = `${prefix}-${suffix}`; - } else { - randomFileName = suffix; - } - - if (parent) { - return join(parent, randomFileName); - } - - return randomFileName; -} diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts deleted file mode 100644 index 2f24135e..00000000 --- a/src/vs/base/common/glob.ts +++ /dev/null @@ -1,737 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { equals } from 'vs/base/common/arrays'; -import { isThenable } from 'vs/base/common/async'; -import { CharCode } from 'vs/base/common/charCode'; -import { isEqualOrParent } from 'vs/base/common/extpath'; -import { basename, extname, posix, sep } from 'vs/base/common/path'; -import { isLinux } from 'vs/base/common/platform'; -import { escapeRegExpCharacters, ltrim } from 'vs/base/common/strings'; - -export interface IRelativePattern { - - /** - * A base file path to which this pattern will be matched against relatively. - */ - readonly base: string; - - /** - * A file glob pattern like `*.{ts,js}` that will be matched on file paths - * relative to the base path. - * - * Example: Given a base of `/home/work/folder` and a file path of `/home/work/folder/index.js`, - * the file glob pattern will match on `index.js`. - */ - readonly pattern: string; -} - -export interface IExpression { - [pattern: string]: boolean | SiblingClause; -} - -export function getEmptyExpression(): IExpression { - return Object.create(null); -} - -interface SiblingClause { - when: string; -} - -export const GLOBSTAR = '**'; -export const GLOB_SPLIT = '/'; - -const PATH_REGEX = '[/\\\\]'; // any slash or backslash -const NO_PATH_REGEX = '[^/\\\\]'; // any non-slash and non-backslash -const ALL_FORWARD_SLASHES = /\//g; - -function starsToRegExp(starCount: number, isLastPattern?: boolean): string { - switch (starCount) { - case 0: - return ''; - case 1: - return `${NO_PATH_REGEX}*?`; // 1 star matches any number of characters except path separator (/ and \) - non greedy (?) - default: - // Matches: (Path Sep OR Path Val followed by Path Sep) 0-many times except when it's the last pattern - // in which case also matches (Path Sep followed by Path Val) - // Group is non capturing because we don't need to capture at all (?:...) - // Overall we use non-greedy matching because it could be that we match too much - return `(?:${PATH_REGEX}|${NO_PATH_REGEX}+${PATH_REGEX}${isLastPattern ? `|${PATH_REGEX}${NO_PATH_REGEX}+` : ''})*?`; - } -} - -export function splitGlobAware(pattern: string, splitChar: string): string[] { - if (!pattern) { - return []; - } - - const segments: string[] = []; - - let inBraces = false; - let inBrackets = false; - - let curVal = ''; - for (const char of pattern) { - switch (char) { - case splitChar: - if (!inBraces && !inBrackets) { - segments.push(curVal); - curVal = ''; - - continue; - } - break; - case '{': - inBraces = true; - break; - case '}': - inBraces = false; - break; - case '[': - inBrackets = true; - break; - case ']': - inBrackets = false; - break; - } - - curVal += char; - } - - // Tail - if (curVal) { - segments.push(curVal); - } - - return segments; -} - -function parseRegExp(pattern: string): string { - if (!pattern) { - return ''; - } - - let regEx = ''; - - // Split up into segments for each slash found - const segments = splitGlobAware(pattern, GLOB_SPLIT); - - // Special case where we only have globstars - if (segments.every(segment => segment === GLOBSTAR)) { - regEx = '.*'; - } - - // Build regex over segments - else { - let previousSegmentWasGlobStar = false; - segments.forEach((segment, index) => { - - // Treat globstar specially - if (segment === GLOBSTAR) { - - // if we have more than one globstar after another, just ignore it - if (previousSegmentWasGlobStar) { - return; - } - - regEx += starsToRegExp(2, index === segments.length - 1); - } - - // Anything else, not globstar - else { - - // States - let inBraces = false; - let braceVal = ''; - - let inBrackets = false; - let bracketVal = ''; - - for (const char of segment) { - - // Support brace expansion - if (char !== '}' && inBraces) { - braceVal += char; - continue; - } - - // Support brackets - if (inBrackets && (char !== ']' || !bracketVal) /* ] is literally only allowed as first character in brackets to match it */) { - let res: string; - - // range operator - if (char === '-') { - res = char; - } - - // negation operator (only valid on first index in bracket) - else if ((char === '^' || char === '!') && !bracketVal) { - res = '^'; - } - - // glob split matching is not allowed within character ranges - // see http://man7.org/linux/man-pages/man7/glob.7.html - else if (char === GLOB_SPLIT) { - res = ''; - } - - // anything else gets escaped - else { - res = escapeRegExpCharacters(char); - } - - bracketVal += res; - continue; - } - - switch (char) { - case '{': - inBraces = true; - continue; - - case '[': - inBrackets = true; - continue; - - case '}': { - const choices = splitGlobAware(braceVal, ','); - - // Converts {foo,bar} => [foo|bar] - const braceRegExp = `(?:${choices.map(choice => parseRegExp(choice)).join('|')})`; - - regEx += braceRegExp; - - inBraces = false; - braceVal = ''; - - break; - } - - case ']': { - regEx += ('[' + bracketVal + ']'); - - inBrackets = false; - bracketVal = ''; - - break; - } - - case '?': - regEx += NO_PATH_REGEX; // 1 ? matches any single character except path separator (/ and \) - continue; - - case '*': - regEx += starsToRegExp(1); - continue; - - default: - regEx += escapeRegExpCharacters(char); - } - } - - // Tail: Add the slash we had split on if there is more to - // come and the remaining pattern is not a globstar - // For example if pattern: some/**/*.js we want the "/" after - // some to be included in the RegEx to prevent a folder called - // "something" to match as well. - if ( - index < segments.length - 1 && // more segments to come after this - ( - segments[index + 1] !== GLOBSTAR || // next segment is not **, or... - index + 2 < segments.length // ...next segment is ** but there is more segments after that - ) - ) { - regEx += PATH_REGEX; - } - } - - // update globstar state - previousSegmentWasGlobStar = (segment === GLOBSTAR); - }); - } - - return regEx; -} - -// regexes to check for trivial glob patterns that just check for String#endsWith -const T1 = /^\*\*\/\*\.[\w\.-]+$/; // **/*.something -const T2 = /^\*\*\/([\w\.-]+)\/?$/; // **/something -const T3 = /^{\*\*\/\*?[\w\.-]+\/?(,\*\*\/\*?[\w\.-]+\/?)*}$/; // {**/*.something,**/*.else} or {**/package.json,**/project.json} -const T3_2 = /^{\*\*\/\*?[\w\.-]+(\/(\*\*)?)?(,\*\*\/\*?[\w\.-]+(\/(\*\*)?)?)*}$/; // Like T3, with optional trailing /** -const T4 = /^\*\*((\/[\w\.-]+)+)\/?$/; // **/something/else -const T5 = /^([\w\.-]+(\/[\w\.-]+)*)\/?$/; // something/else - -export type ParsedPattern = (path: string, basename?: string) => boolean; - -// The `ParsedExpression` returns a `Promise` -// iff `hasSibling` returns a `Promise`. -export type ParsedExpression = (path: string, basename?: string, hasSibling?: (name: string) => boolean | Promise) => string | null | Promise /* the matching pattern */; - -interface IGlobOptions { - - /** - * Simplify patterns for use as exclusion filters during - * tree traversal to skip entire subtrees. Cannot be used - * outside of a tree traversal. - */ - trimForExclusions?: boolean; -} - -interface ParsedStringPattern { - (path: string, basename?: string): string | null | Promise /* the matching pattern */; - basenames?: string[]; - patterns?: string[]; - allBasenames?: string[]; - allPaths?: string[]; -} - -interface ParsedExpressionPattern { - (path: string, basename?: string, name?: string, hasSibling?: (name: string) => boolean | Promise): string | null | Promise /* the matching pattern */; - requiresSiblings?: boolean; - allBasenames?: string[]; - allPaths?: string[]; -} - -const FALSE = function () { - return false; -}; - -const NULL = function (): string | null { - return null; -}; - -function trimForExclusions(pattern: string, options: IGlobOptions): string { - return options.trimForExclusions && pattern.endsWith('/**') ? pattern.substr(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later -} - -// common pattern: **/*.txt just need endsWith check -function trivia1(base: string, pattern: string): ParsedStringPattern { - return function (path: string, basename?: string) { - return typeof path === 'string' && path.endsWith(base) ? pattern : null; - }; -} - -// common pattern: **/some.txt just need basename check -function trivia2(base: string, pattern: string): ParsedStringPattern { - const slashBase = `/${base}`; - const backslashBase = `\\${base}`; - - const parsedPattern: ParsedStringPattern = function (path: string, basename?: string) { - if (typeof path !== 'string') { - return null; - } - - if (basename) { - return basename === base ? pattern : null; - } - - return path === base || path.endsWith(slashBase) || path.endsWith(backslashBase) ? pattern : null; - }; - - const basenames = [base]; - parsedPattern.basenames = basenames; - parsedPattern.patterns = [pattern]; - parsedPattern.allBasenames = basenames; - - return parsedPattern; -} - -// repetition of common patterns (see above) {**/*.txt,**/*.png} -function trivia3(pattern: string, options: IGlobOptions): ParsedStringPattern { - const parsedPatterns = aggregateBasenameMatches(pattern.slice(1, -1) - .split(',') - .map(pattern => parsePattern(pattern, options)) - .filter(pattern => pattern !== NULL), pattern); - - const patternsLength = parsedPatterns.length; - if (!patternsLength) { - return NULL; - } - - if (patternsLength === 1) { - return parsedPatterns[0]; - } - - const parsedPattern: ParsedStringPattern = function (path: string, basename?: string) { - for (let i = 0, n = parsedPatterns.length; i < n; i++) { - if (parsedPatterns[i](path, basename)) { - return pattern; - } - } - - return null; - }; - - const withBasenames = parsedPatterns.find(pattern => !!pattern.allBasenames); - if (withBasenames) { - parsedPattern.allBasenames = withBasenames.allBasenames; - } - - const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, [] as string[]); - if (allPaths.length) { - parsedPattern.allPaths = allPaths; - } - - return parsedPattern; -} - -// common patterns: **/something/else just need endsWith check, something/else just needs and equals check -function trivia4and5(targetPath: string, pattern: string, matchPathEnds: boolean): ParsedStringPattern { - const usingPosixSep = sep === posix.sep; - const nativePath = usingPosixSep ? targetPath : targetPath.replace(ALL_FORWARD_SLASHES, sep); - const nativePathEnd = sep + nativePath; - const targetPathEnd = posix.sep + targetPath; - - let parsedPattern: ParsedStringPattern; - if (matchPathEnds) { - parsedPattern = function (path: string, basename?: string) { - return typeof path === 'string' && ((path === nativePath || path.endsWith(nativePathEnd)) || !usingPosixSep && (path === targetPath || path.endsWith(targetPathEnd))) ? pattern : null; - }; - } else { - parsedPattern = function (path: string, basename?: string) { - return typeof path === 'string' && (path === nativePath || (!usingPosixSep && path === targetPath)) ? pattern : null; - }; - } - - parsedPattern.allPaths = [(matchPathEnds ? '*/' : './') + targetPath]; - - return parsedPattern; -} - -function toRegExp(pattern: string): ParsedStringPattern { - try { - const regExp = new RegExp(`^${parseRegExp(pattern)}$`); - return function (path: string) { - regExp.lastIndex = 0; // reset RegExp to its initial state to reuse it! - - return typeof path === 'string' && regExp.test(path) ? pattern : null; - }; - } catch (error) { - return NULL; - } -} - -/** - * Simplified glob matching. Supports a subset of glob patterns: - * * `*` to match zero or more characters in a path segment - * * `?` to match on one character in a path segment - * * `**` to match any number of path segments, including none - * * `{}` to group conditions (e.g. *.{ts,js} matches all TypeScript and JavaScript files) - * * `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) - * * `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) - */ -export function match(pattern: string | IRelativePattern, path: string): boolean; -export function match(expression: IExpression, path: string, hasSibling?: (name: string) => boolean): string /* the matching pattern */; -export function match(arg1: string | IExpression | IRelativePattern, path: string, hasSibling?: (name: string) => boolean): boolean | string | null | Promise { - if (!arg1 || typeof path !== 'string') { - return false; - } - - return parse(arg1)(path, undefined, hasSibling); -} - -/** - * Simplified glob matching. Supports a subset of glob patterns: - * * `*` to match zero or more characters in a path segment - * * `?` to match on one character in a path segment - * * `**` to match any number of path segments, including none - * * `{}` to group conditions (e.g. *.{ts,js} matches all TypeScript and JavaScript files) - * * `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) - * * `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) - */ -export function parse(pattern: string | IRelativePattern, options?: IGlobOptions): ParsedPattern; -export function parse(expression: IExpression, options?: IGlobOptions): ParsedExpression; -export function parse(arg1: string | IExpression | IRelativePattern, options?: IGlobOptions): ParsedPattern | ParsedExpression; -export function parse(arg1: string | IExpression | IRelativePattern, options: IGlobOptions = {}): ParsedPattern | ParsedExpression { - if (!arg1) { - return FALSE; - } - - // Glob with String - if (typeof arg1 === 'string' || isRelativePattern(arg1)) { - const parsedPattern = parsePattern(arg1, options); - if (parsedPattern === NULL) { - return FALSE; - } - - const resultPattern: ParsedPattern & { allBasenames?: string[]; allPaths?: string[] } = function (path: string, basename?: string) { - return !!parsedPattern(path, basename); - }; - - if (parsedPattern.allBasenames) { - resultPattern.allBasenames = parsedPattern.allBasenames; - } - - if (parsedPattern.allPaths) { - resultPattern.allPaths = parsedPattern.allPaths; - } - - return resultPattern; - } - - // Glob with Expression - return parsedExpression(arg1, options); -} - -export function isRelativePattern(obj: unknown): obj is IRelativePattern { - const rp = obj as IRelativePattern | undefined | null; - if (!rp) { - return false; - } - - return typeof rp.base === 'string' && typeof rp.pattern === 'string'; -} - -export function getBasenameTerms(patternOrExpression: ParsedPattern | ParsedExpression): string[] { - return (patternOrExpression).allBasenames || []; -} - -export function getPathTerms(patternOrExpression: ParsedPattern | ParsedExpression): string[] { - return (patternOrExpression).allPaths || []; -} - -function parsedExpression(expression: IExpression, options: IGlobOptions): ParsedExpression { - const parsedPatterns = aggregateBasenameMatches(Object.getOwnPropertyNames(expression) - .map(pattern => parseExpressionPattern(pattern, expression[pattern], options)) - .filter(pattern => pattern !== NULL)); - - const patternsLength = parsedPatterns.length; - if (!patternsLength) { - return NULL; - } - - if (!parsedPatterns.some(parsedPattern => !!(parsedPattern).requiresSiblings)) { - if (patternsLength === 1) { - return parsedPatterns[0] as ParsedStringPattern; - } - - const resultExpression: ParsedStringPattern = function (path: string, basename?: string) { - let resultPromises: Promise[] | undefined = undefined; - - for (let i = 0, n = parsedPatterns.length; i < n; i++) { - const result = parsedPatterns[i](path, basename); - if (typeof result === 'string') { - return result; // immediately return as soon as the first expression matches - } - - // If the result is a promise, we have to keep it for - // later processing and await the result properly. - if (isThenable(result)) { - if (!resultPromises) { - resultPromises = []; - } - - resultPromises.push(result); - } - } - - // With result promises, we have to loop over each and - // await the result before we can return any result. - if (resultPromises) { - return (async () => { - for (const resultPromise of resultPromises) { - const result = await resultPromise; - if (typeof result === 'string') { - return result; - } - } - - return null; - })(); - } - - return null; - }; - - const withBasenames = parsedPatterns.find(pattern => !!pattern.allBasenames); - if (withBasenames) { - resultExpression.allBasenames = withBasenames.allBasenames; - } - - const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, [] as string[]); - if (allPaths.length) { - resultExpression.allPaths = allPaths; - } - - return resultExpression; - } - - const resultExpression: ParsedStringPattern = function (path: string, base?: string, hasSibling?: (name: string) => boolean | Promise) { - let name: string | undefined = undefined; - let resultPromises: Promise[] | undefined = undefined; - - for (let i = 0, n = parsedPatterns.length; i < n; i++) { - - // Pattern matches path - const parsedPattern = (parsedPatterns[i]); - if (parsedPattern.requiresSiblings && hasSibling) { - if (!base) { - base = basename(path); - } - - if (!name) { - name = base.substr(0, base.length - extname(path).length); - } - } - - const result = parsedPattern(path, base, name, hasSibling); - if (typeof result === 'string') { - return result; // immediately return as soon as the first expression matches - } - - // If the result is a promise, we have to keep it for - // later processing and await the result properly. - if (isThenable(result)) { - if (!resultPromises) { - resultPromises = []; - } - - resultPromises.push(result); - } - } - - // With result promises, we have to loop over each and - // await the result before we can return any result. - if (resultPromises) { - return (async () => { - for (const resultPromise of resultPromises) { - const result = await resultPromise; - if (typeof result === 'string') { - return result; - } - } - - return null; - })(); - } - - return null; - }; - - const withBasenames = parsedPatterns.find(pattern => !!pattern.allBasenames); - if (withBasenames) { - resultExpression.allBasenames = withBasenames.allBasenames; - } - - const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, [] as string[]); - if (allPaths.length) { - resultExpression.allPaths = allPaths; - } - - return resultExpression; -} - -function parseExpressionPattern(pattern: string, value: boolean | SiblingClause, options: IGlobOptions): (ParsedStringPattern | ParsedExpressionPattern) { - if (value === false) { - return NULL; // pattern is disabled - } - - const parsedPattern = parsePattern(pattern, options); - if (parsedPattern === NULL) { - return NULL; - } - - // Expression Pattern is - if (typeof value === 'boolean') { - return parsedPattern; - } - - // Expression Pattern is - if (value) { - const when = value.when; - if (typeof when === 'string') { - const result: ParsedExpressionPattern = (path: string, basename?: string, name?: string, hasSibling?: (name: string) => boolean | Promise) => { - if (!hasSibling || !parsedPattern(path, basename)) { - return null; - } - - const clausePattern = when.replace('$(basename)', () => name!); - const matched = hasSibling(clausePattern); - return isThenable(matched) ? - matched.then(match => match ? pattern : null) : - matched ? pattern : null; - }; - - result.requiresSiblings = true; - - return result; - } - } - - // Expression is anything - return parsedPattern; -} - -function aggregateBasenameMatches(parsedPatterns: Array, result?: string): Array { - const basenamePatterns = parsedPatterns.filter(parsedPattern => !!(parsedPattern).basenames); - if (basenamePatterns.length < 2) { - return parsedPatterns; - } - - const basenames = basenamePatterns.reduce((all, current) => { - const basenames = (current).basenames; - - return basenames ? all.concat(basenames) : all; - }, [] as string[]); - - let patterns: string[]; - if (result) { - patterns = []; - - for (let i = 0, n = basenames.length; i < n; i++) { - patterns.push(result); - } - } else { - patterns = basenamePatterns.reduce((all, current) => { - const patterns = (current).patterns; - - return patterns ? all.concat(patterns) : all; - }, [] as string[]); - } - - const aggregate: ParsedStringPattern = function (path: string, basename?: string) { - if (typeof path !== 'string') { - return null; - } - - if (!basename) { - let i: number; - for (i = path.length; i > 0; i--) { - const ch = path.charCodeAt(i - 1); - if (ch === CharCode.Slash || ch === CharCode.Backslash) { - break; - } - } - - basename = path.substr(i); - } - - const index = basenames.indexOf(basename); - return index !== -1 ? patterns[index] : null; - }; - - aggregate.basenames = basenames; - aggregate.patterns = patterns; - aggregate.allBasenames = basenames; - - const aggregatedPatterns = parsedPatterns.filter(parsedPattern => !(parsedPattern).basenames); - aggregatedPatterns.push(aggregate); - - return aggregatedPatterns; -} - -export function patternsEquals(patternsA: Array | undefined, patternsB: Array | undefined): boolean { - return equals(patternsA, patternsB, (a, b) => { - if (typeof a === 'string' && typeof b === 'string') { - return a === b; - } - - if (typeof a !== 'string' && typeof b !== 'string') { - return a.base === b.base && a.pattern === b.pattern; - } - - return false; - }); -} diff --git a/src/vs/base/common/hierarchicalKind.ts b/src/vs/base/common/hierarchicalKind.ts deleted file mode 100644 index a2edd614..00000000 --- a/src/vs/base/common/hierarchicalKind.ts +++ /dev/null @@ -1,31 +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 class HierarchicalKind { - public static readonly sep = '.'; - - public static readonly None = new HierarchicalKind('@@none@@'); // Special kind that matches nothing - public static readonly Empty = new HierarchicalKind(''); - - constructor( - public readonly value: string - ) { } - - public equals(other: HierarchicalKind): boolean { - return this.value === other.value; - } - - public contains(other: HierarchicalKind): boolean { - return this.equals(other) || this.value === '' || other.value.startsWith(this.value + HierarchicalKind.sep); - } - - public intersects(other: HierarchicalKind): boolean { - return this.contains(other) || other.contains(this); - } - - public append(...parts: string[]): HierarchicalKind { - return new HierarchicalKind((this.value ? [this.value, ...parts] : parts).join(HierarchicalKind.sep)); - } -} diff --git a/src/vs/base/common/history.ts b/src/vs/base/common/history.ts deleted file mode 100644 index 9d644a85..00000000 --- a/src/vs/base/common/history.ts +++ /dev/null @@ -1,277 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SetWithKey } from 'vs/base/common/collections'; -import { ArrayNavigator, INavigator } from 'vs/base/common/navigator'; - -export class HistoryNavigator implements INavigator { - - private _history!: Set; - private _limit: number; - private _navigator!: ArrayNavigator; - - constructor(history: readonly T[] = [], limit: number = 10) { - this._initialize(history); - this._limit = limit; - this._onChange(); - } - - public getHistory(): T[] { - return this._elements; - } - - public add(t: T) { - this._history.delete(t); - this._history.add(t); - this._onChange(); - } - - public next(): T | null { - // This will navigate past the end of the last element, and in that case the input should be cleared - return this._navigator.next(); - } - - public previous(): T | null { - if (this._currentPosition() !== 0) { - return this._navigator.previous(); - } - return null; - } - - public current(): T | null { - return this._navigator.current(); - } - - public first(): T | null { - return this._navigator.first(); - } - - public last(): T | null { - return this._navigator.last(); - } - - public isFirst(): boolean { - return this._currentPosition() === 0; - } - - public isLast(): boolean { - return this._currentPosition() >= this._elements.length - 1; - } - - public isNowhere(): boolean { - return this._navigator.current() === null; - } - - public has(t: T): boolean { - return this._history.has(t); - } - - public clear(): void { - this._initialize([]); - this._onChange(); - } - - private _onChange() { - this._reduceToLimit(); - const elements = this._elements; - this._navigator = new ArrayNavigator(elements, 0, elements.length, elements.length); - } - - private _reduceToLimit() { - const data = this._elements; - if (data.length > this._limit) { - this._initialize(data.slice(data.length - this._limit)); - } - } - - private _currentPosition(): number { - const currentElement = this._navigator.current(); - if (!currentElement) { - return -1; - } - - return this._elements.indexOf(currentElement); - } - - private _initialize(history: readonly T[]): void { - this._history = new Set(); - for (const entry of history) { - this._history.add(entry); - } - } - - private get _elements(): T[] { - const elements: T[] = []; - this._history.forEach(e => elements.push(e)); - return elements; - } -} - -interface HistoryNode { - value: T; - previous: HistoryNode | undefined; - next: HistoryNode | undefined; -} - -/** - * The right way to use HistoryNavigator2 is for the last item in the list to be the user's uncommitted current text. eg empty string, or whatever has been typed. Then - * the user can navigate away from the last item through the list, and back to it. When updating the last item, call replaceLast. - */ -export class HistoryNavigator2 { - - private valueSet: Set; - private head: HistoryNode; - private tail: HistoryNode; - private cursor: HistoryNode; - private _size: number; - get size(): number { return this._size; } - - constructor(history: readonly T[], private capacity: number = 10, private identityFn: (t: T) => any = t => t) { - if (history.length < 1) { - throw new Error('not supported'); - } - - this._size = 1; - this.head = this.tail = this.cursor = { - value: history[0], - previous: undefined, - next: undefined - }; - - this.valueSet = new SetWithKey([history[0]], identityFn); - for (let i = 1; i < history.length; i++) { - this.add(history[i]); - } - } - - add(value: T): void { - const node: HistoryNode = { - value, - previous: this.tail, - next: undefined - }; - - this.tail.next = node; - this.tail = node; - this.cursor = this.tail; - this._size++; - - if (this.valueSet.has(value)) { - this._deleteFromList(value); - } else { - this.valueSet.add(value); - } - - while (this._size > this.capacity) { - this.valueSet.delete(this.head.value); - - this.head = this.head.next!; - this.head.previous = undefined; - this._size--; - } - } - - /** - * @returns old last value - */ - replaceLast(value: T): T { - if (this.identityFn(this.tail.value) === this.identityFn(value)) { - return value; - } - - const oldValue = this.tail.value; - this.valueSet.delete(oldValue); - this.tail.value = value; - - if (this.valueSet.has(value)) { - this._deleteFromList(value); - } else { - this.valueSet.add(value); - } - - return oldValue; - } - - prepend(value: T): void { - if (this._size === this.capacity || this.valueSet.has(value)) { - return; - } - - const node: HistoryNode = { - value, - previous: undefined, - next: this.head - }; - - this.head.previous = node; - this.head = node; - this._size++; - - this.valueSet.add(value); - } - - isAtEnd(): boolean { - return this.cursor === this.tail; - } - - current(): T { - return this.cursor.value; - } - - previous(): T { - if (this.cursor.previous) { - this.cursor = this.cursor.previous; - } - - return this.cursor.value; - } - - next(): T { - if (this.cursor.next) { - this.cursor = this.cursor.next; - } - - return this.cursor.value; - } - - has(t: T): boolean { - return this.valueSet.has(t); - } - - resetCursor(): T { - this.cursor = this.tail; - return this.cursor.value; - } - - *[Symbol.iterator](): Iterator { - let node: HistoryNode | undefined = this.head; - - while (node) { - yield node.value; - node = node.next; - } - } - - private _deleteFromList(value: T): void { - let temp = this.head; - - const valueKey = this.identityFn(value); - while (temp !== this.tail) { - if (this.identityFn(temp.value) === valueKey) { - if (temp === this.head) { - this.head = this.head.next!; - this.head.previous = undefined; - } else { - temp.previous!.next = temp.next; - temp.next!.previous = temp.previous; - } - - this._size--; - } - - temp = temp.next!; - } - } -} diff --git a/src/vs/base/common/hotReload.ts b/src/vs/base/common/hotReload.ts deleted file mode 100644 index 609fd9d8..00000000 --- a/src/vs/base/common/hotReload.ts +++ /dev/null @@ -1,112 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IDisposable } from 'vs/base/common/lifecycle'; -import { env } from 'vs/base/common/process'; - -export function isHotReloadEnabled(): boolean { - return env && !!env['VSCODE_DEV']; -} -export function registerHotReloadHandler(handler: HotReloadHandler): IDisposable { - if (!isHotReloadEnabled()) { - return { dispose() { } }; - } else { - const handlers = registerGlobalHotReloadHandler(); - handlers.add(handler); - return { - dispose() { handlers.delete(handler); } - }; - } -} - -/** - * Takes the old exports of the module to reload and returns a function to apply the new exports. - * If `undefined` is returned, this handler is not able to handle the module. - * - * If no handler can apply the new exports, the module will not be reloaded. - */ -export type HotReloadHandler = (args: { oldExports: Record; newSrc: string; config: IHotReloadConfig }) => AcceptNewExportsHandler | undefined; -export type AcceptNewExportsHandler = (newExports: Record) => boolean; -export type IHotReloadConfig = HotReloadConfig; - -function registerGlobalHotReloadHandler() { - if (!hotReloadHandlers) { - hotReloadHandlers = new Set(); - } - - const g = globalThis as unknown as GlobalThisAddition; - if (!g.$hotReload_applyNewExports) { - g.$hotReload_applyNewExports = args => { - const args2 = { config: { mode: undefined }, ...args }; - - const results: AcceptNewExportsHandler[] = []; - for (const h of hotReloadHandlers!) { - const result = h(args2); - if (result) { - results.push(result); - } - } - if (results.length > 0) { - return newExports => { - let result = false; - for (const r of results) { - if (r(newExports)) { - result = true; - } - } - return result; - }; - } - return undefined; - }; - } - - return hotReloadHandlers; -} - -let hotReloadHandlers: Set<(args: { oldExports: Record; newSrc: string; config: HotReloadConfig }) => AcceptNewExportsFn | undefined> | undefined = undefined; - -interface HotReloadConfig { - mode?: 'patch-prototype' | undefined; -} - -interface GlobalThisAddition { - $hotReload_applyNewExports?(args: { oldExports: Record; newSrc: string; config?: HotReloadConfig }): AcceptNewExportsFn | undefined; -} - -type AcceptNewExportsFn = (newExports: Record) => boolean; - -if (isHotReloadEnabled()) { - // This code does not run in production. - registerHotReloadHandler(({ oldExports, newSrc, config }) => { - if (config.mode !== 'patch-prototype') { - return undefined; - } - - return newExports => { - for (const key in newExports) { - const exportedItem = newExports[key]; - console.log(`[hot-reload] Patching prototype methods of '${key}'`, { exportedItem }); - if (typeof exportedItem === 'function' && exportedItem.prototype) { - const oldExportedItem = oldExports[key]; - if (oldExportedItem) { - for (const prop of Object.getOwnPropertyNames(exportedItem.prototype)) { - const descriptor = Object.getOwnPropertyDescriptor(exportedItem.prototype, prop)!; - const oldDescriptor = Object.getOwnPropertyDescriptor((oldExportedItem as any).prototype, prop); - - if (descriptor?.value?.toString() !== oldDescriptor?.value?.toString()) { - console.log(`[hot-reload] Patching prototype method '${key}.${prop}'`); - } - - Object.defineProperty((oldExportedItem as any).prototype, prop, descriptor); - } - newExports[key] = oldExportedItem; - } - } - } - return true; - }; - }); -} diff --git a/src/vs/base/common/hotReloadHelpers.ts b/src/vs/base/common/hotReloadHelpers.ts deleted file mode 100644 index 174b1adc..00000000 --- a/src/vs/base/common/hotReloadHelpers.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { isHotReloadEnabled, registerHotReloadHandler } from 'vs/base/common/hotReload'; -import { IReader, observableSignalFromEvent } from 'vs/base/common/observable'; - -export function readHotReloadableExport(value: T, reader: IReader | undefined): T { - observeHotReloadableExports([value], reader); - return value; -} - -export function observeHotReloadableExports(values: any[], reader: IReader | undefined): void { - if (isHotReloadEnabled()) { - const o = observableSignalFromEvent( - 'reload', - event => registerHotReloadHandler(({ oldExports }) => { - if (![...Object.values(oldExports)].some(v => values.includes(v))) { - return undefined; - } - return (_newExports) => { - event(undefined); - return true; - }; - }) - ); - o.read(reader); - } -} diff --git a/src/vs/base/common/idGenerator.ts b/src/vs/base/common/idGenerator.ts deleted file mode 100644 index 0a66cfec..00000000 --- a/src/vs/base/common/idGenerator.ts +++ /dev/null @@ -1,21 +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 class IdGenerator { - - private _prefix: string; - private _lastId: number; - - constructor(prefix: string) { - this._prefix = prefix; - this._lastId = 0; - } - - public nextId(): string { - return this._prefix + (++this._lastId); - } -} - -export const defaultGenerator = new IdGenerator('id#'); diff --git a/src/vs/base/common/ime.ts b/src/vs/base/common/ime.ts deleted file mode 100644 index ce80099b..00000000 --- a/src/vs/base/common/ime.ts +++ /dev/null @@ -1,36 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Emitter } from 'vs/base/common/event'; - -export class IMEImpl { - - private readonly _onDidChange = new Emitter(); - public readonly onDidChange = this._onDidChange.event; - - private _enabled = true; - - public get enabled() { - return this._enabled; - } - - /** - * Enable IME - */ - public enable(): void { - this._enabled = true; - this._onDidChange.fire(); - } - - /** - * Disable IME - */ - public disable(): void { - this._enabled = false; - this._onDidChange.fire(); - } -} - -export const IME = new IMEImpl(); diff --git a/src/vs/base/common/json.ts b/src/vs/base/common/json.ts deleted file mode 100644 index e4adc590..00000000 --- a/src/vs/base/common/json.ts +++ /dev/null @@ -1,1326 +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 const enum ScanError { - None = 0, - UnexpectedEndOfComment = 1, - UnexpectedEndOfString = 2, - UnexpectedEndOfNumber = 3, - InvalidUnicode = 4, - InvalidEscapeCharacter = 5, - InvalidCharacter = 6 -} - -export const enum SyntaxKind { - OpenBraceToken = 1, - CloseBraceToken = 2, - OpenBracketToken = 3, - CloseBracketToken = 4, - CommaToken = 5, - ColonToken = 6, - NullKeyword = 7, - TrueKeyword = 8, - FalseKeyword = 9, - StringLiteral = 10, - NumericLiteral = 11, - LineCommentTrivia = 12, - BlockCommentTrivia = 13, - LineBreakTrivia = 14, - Trivia = 15, - Unknown = 16, - EOF = 17 -} - -/** - * The scanner object, representing a JSON scanner at a position in the input string. - */ -export interface JSONScanner { - /** - * Sets the scan position to a new offset. A call to 'scan' is needed to get the first token. - */ - setPosition(pos: number): void; - /** - * Read the next token. Returns the token code. - */ - scan(): SyntaxKind; - /** - * Returns the current scan position, which is after the last read token. - */ - getPosition(): number; - /** - * Returns the last read token. - */ - getToken(): SyntaxKind; - /** - * Returns the last read token value. The value for strings is the decoded string content. For numbers its of type number, for boolean it's true or false. - */ - getTokenValue(): string; - /** - * The start offset of the last read token. - */ - getTokenOffset(): number; - /** - * The length of the last read token. - */ - getTokenLength(): number; - /** - * An error code of the last scan. - */ - getTokenError(): ScanError; -} - - - -export interface ParseError { - error: ParseErrorCode; - offset: number; - length: number; -} - -export const enum ParseErrorCode { - InvalidSymbol = 1, - InvalidNumberFormat = 2, - PropertyNameExpected = 3, - ValueExpected = 4, - ColonExpected = 5, - CommaExpected = 6, - CloseBraceExpected = 7, - CloseBracketExpected = 8, - EndOfFileExpected = 9, - InvalidCommentToken = 10, - UnexpectedEndOfComment = 11, - UnexpectedEndOfString = 12, - UnexpectedEndOfNumber = 13, - InvalidUnicode = 14, - InvalidEscapeCharacter = 15, - InvalidCharacter = 16 -} - -export type NodeType = 'object' | 'array' | 'property' | 'string' | 'number' | 'boolean' | 'null'; - -export interface Node { - readonly type: NodeType; - readonly value?: any; - readonly offset: number; - readonly length: number; - readonly colonOffset?: number; - readonly parent?: Node; - readonly children?: Node[]; -} - -export type Segment = string | number; -export type JSONPath = Segment[]; - -export interface Location { - /** - * The previous property key or literal value (string, number, boolean or null) or undefined. - */ - previousNode?: Node; - /** - * The path describing the location in the JSON document. The path consists of a sequence strings - * representing an object property or numbers for array indices. - */ - path: JSONPath; - /** - * Matches the locations path against a pattern consisting of strings (for properties) and numbers (for array indices). - * '*' will match a single segment, of any property name or index. - * '**' will match a sequence of segments or no segment, of any property name or index. - */ - matches: (patterns: JSONPath) => boolean; - /** - * If set, the location's offset is at a property key. - */ - isAtPropertyKey: boolean; -} - -export interface ParseOptions { - disallowComments?: boolean; - allowTrailingComma?: boolean; - allowEmptyContent?: boolean; -} - -export namespace ParseOptions { - export const DEFAULT = { - allowTrailingComma: true - }; -} - -export interface JSONVisitor { - /** - * Invoked when an open brace is encountered and an object is started. The offset and length represent the location of the open brace. - */ - onObjectBegin?: (offset: number, length: number) => void; - - /** - * Invoked when a property is encountered. The offset and length represent the location of the property name. - */ - onObjectProperty?: (property: string, offset: number, length: number) => void; - - /** - * Invoked when a closing brace is encountered and an object is completed. The offset and length represent the location of the closing brace. - */ - onObjectEnd?: (offset: number, length: number) => void; - - /** - * Invoked when an open bracket is encountered. The offset and length represent the location of the open bracket. - */ - onArrayBegin?: (offset: number, length: number) => void; - - /** - * Invoked when a closing bracket is encountered. The offset and length represent the location of the closing bracket. - */ - onArrayEnd?: (offset: number, length: number) => void; - - /** - * Invoked when a literal value is encountered. The offset and length represent the location of the literal value. - */ - onLiteralValue?: (value: any, offset: number, length: number) => void; - - /** - * Invoked when a comma or colon separator is encountered. The offset and length represent the location of the separator. - */ - onSeparator?: (character: string, offset: number, length: number) => void; - - /** - * When comments are allowed, invoked when a line or block comment is encountered. The offset and length represent the location of the comment. - */ - onComment?: (offset: number, length: number) => void; - - /** - * Invoked on an error. - */ - onError?: (error: ParseErrorCode, offset: number, length: number) => void; -} - -/** - * Creates a JSON scanner on the given text. - * If ignoreTrivia is set, whitespaces or comments are ignored. - */ -export function createScanner(text: string, ignoreTrivia: boolean = false): JSONScanner { - - let pos = 0; - const len = text.length; - let value: string = ''; - let tokenOffset = 0; - let token: SyntaxKind = SyntaxKind.Unknown; - let scanError: ScanError = ScanError.None; - - function scanHexDigits(count: number): number { - let digits = 0; - let hexValue = 0; - while (digits < count) { - const ch = text.charCodeAt(pos); - if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) { - hexValue = hexValue * 16 + ch - CharacterCodes._0; - } - else if (ch >= CharacterCodes.A && ch <= CharacterCodes.F) { - hexValue = hexValue * 16 + ch - CharacterCodes.A + 10; - } - else if (ch >= CharacterCodes.a && ch <= CharacterCodes.f) { - hexValue = hexValue * 16 + ch - CharacterCodes.a + 10; - } - else { - break; - } - pos++; - digits++; - } - if (digits < count) { - hexValue = -1; - } - return hexValue; - } - - function setPosition(newPosition: number) { - pos = newPosition; - value = ''; - tokenOffset = 0; - token = SyntaxKind.Unknown; - scanError = ScanError.None; - } - - function scanNumber(): string { - const start = pos; - if (text.charCodeAt(pos) === CharacterCodes._0) { - pos++; - } else { - pos++; - while (pos < text.length && isDigit(text.charCodeAt(pos))) { - pos++; - } - } - if (pos < text.length && text.charCodeAt(pos) === CharacterCodes.dot) { - pos++; - if (pos < text.length && isDigit(text.charCodeAt(pos))) { - pos++; - while (pos < text.length && isDigit(text.charCodeAt(pos))) { - pos++; - } - } else { - scanError = ScanError.UnexpectedEndOfNumber; - return text.substring(start, pos); - } - } - let end = pos; - if (pos < text.length && (text.charCodeAt(pos) === CharacterCodes.E || text.charCodeAt(pos) === CharacterCodes.e)) { - pos++; - if (pos < text.length && text.charCodeAt(pos) === CharacterCodes.plus || text.charCodeAt(pos) === CharacterCodes.minus) { - pos++; - } - if (pos < text.length && isDigit(text.charCodeAt(pos))) { - pos++; - while (pos < text.length && isDigit(text.charCodeAt(pos))) { - pos++; - } - end = pos; - } else { - scanError = ScanError.UnexpectedEndOfNumber; - } - } - return text.substring(start, end); - } - - function scanString(): string { - - let result = '', - start = pos; - - while (true) { - if (pos >= len) { - result += text.substring(start, pos); - scanError = ScanError.UnexpectedEndOfString; - break; - } - const ch = text.charCodeAt(pos); - if (ch === CharacterCodes.doubleQuote) { - result += text.substring(start, pos); - pos++; - break; - } - if (ch === CharacterCodes.backslash) { - result += text.substring(start, pos); - pos++; - if (pos >= len) { - scanError = ScanError.UnexpectedEndOfString; - break; - } - const ch2 = text.charCodeAt(pos++); - switch (ch2) { - case CharacterCodes.doubleQuote: - result += '\"'; - break; - case CharacterCodes.backslash: - result += '\\'; - break; - case CharacterCodes.slash: - result += '/'; - break; - case CharacterCodes.b: - result += '\b'; - break; - case CharacterCodes.f: - result += '\f'; - break; - case CharacterCodes.n: - result += '\n'; - break; - case CharacterCodes.r: - result += '\r'; - break; - case CharacterCodes.t: - result += '\t'; - break; - case CharacterCodes.u: { - const ch3 = scanHexDigits(4); - if (ch3 >= 0) { - result += String.fromCharCode(ch3); - } else { - scanError = ScanError.InvalidUnicode; - } - break; - } - default: - scanError = ScanError.InvalidEscapeCharacter; - } - start = pos; - continue; - } - if (ch >= 0 && ch <= 0x1F) { - if (isLineBreak(ch)) { - result += text.substring(start, pos); - scanError = ScanError.UnexpectedEndOfString; - break; - } else { - scanError = ScanError.InvalidCharacter; - // mark as error but continue with string - } - } - pos++; - } - return result; - } - - function scanNext(): SyntaxKind { - - value = ''; - scanError = ScanError.None; - - tokenOffset = pos; - - if (pos >= len) { - // at the end - tokenOffset = len; - return token = SyntaxKind.EOF; - } - - let code = text.charCodeAt(pos); - // trivia: whitespace - if (isWhitespace(code)) { - do { - pos++; - value += String.fromCharCode(code); - code = text.charCodeAt(pos); - } while (isWhitespace(code)); - - return token = SyntaxKind.Trivia; - } - - // trivia: newlines - if (isLineBreak(code)) { - pos++; - value += String.fromCharCode(code); - if (code === CharacterCodes.carriageReturn && text.charCodeAt(pos) === CharacterCodes.lineFeed) { - pos++; - value += '\n'; - } - return token = SyntaxKind.LineBreakTrivia; - } - - switch (code) { - // tokens: []{}:, - case CharacterCodes.openBrace: - pos++; - return token = SyntaxKind.OpenBraceToken; - case CharacterCodes.closeBrace: - pos++; - return token = SyntaxKind.CloseBraceToken; - case CharacterCodes.openBracket: - pos++; - return token = SyntaxKind.OpenBracketToken; - case CharacterCodes.closeBracket: - pos++; - return token = SyntaxKind.CloseBracketToken; - case CharacterCodes.colon: - pos++; - return token = SyntaxKind.ColonToken; - case CharacterCodes.comma: - pos++; - return token = SyntaxKind.CommaToken; - - // strings - case CharacterCodes.doubleQuote: - pos++; - value = scanString(); - return token = SyntaxKind.StringLiteral; - - // comments - case CharacterCodes.slash: { - const start = pos - 1; - // Single-line comment - if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; - - while (pos < len) { - if (isLineBreak(text.charCodeAt(pos))) { - break; - } - pos++; - - } - value = text.substring(start, pos); - return token = SyntaxKind.LineCommentTrivia; - } - - // Multi-line comment - if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { - pos += 2; - - const safeLength = len - 1; // For lookahead. - let commentClosed = false; - while (pos < safeLength) { - const ch = text.charCodeAt(pos); - - if (ch === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; - commentClosed = true; - break; - } - pos++; - } - - if (!commentClosed) { - pos++; - scanError = ScanError.UnexpectedEndOfComment; - } - - value = text.substring(start, pos); - return token = SyntaxKind.BlockCommentTrivia; - } - // just a single slash - value += String.fromCharCode(code); - pos++; - return token = SyntaxKind.Unknown; - } - // numbers - case CharacterCodes.minus: - value += String.fromCharCode(code); - pos++; - if (pos === len || !isDigit(text.charCodeAt(pos))) { - return token = SyntaxKind.Unknown; - } - // found a minus, followed by a number so - // we fall through to proceed with scanning - // numbers - case CharacterCodes._0: - case CharacterCodes._1: - case CharacterCodes._2: - case CharacterCodes._3: - case CharacterCodes._4: - case CharacterCodes._5: - case CharacterCodes._6: - case CharacterCodes._7: - case CharacterCodes._8: - case CharacterCodes._9: - value += scanNumber(); - return token = SyntaxKind.NumericLiteral; - // literals and unknown symbols - default: - // is a literal? Read the full word. - while (pos < len && isUnknownContentCharacter(code)) { - pos++; - code = text.charCodeAt(pos); - } - if (tokenOffset !== pos) { - value = text.substring(tokenOffset, pos); - // keywords: true, false, null - switch (value) { - case 'true': return token = SyntaxKind.TrueKeyword; - case 'false': return token = SyntaxKind.FalseKeyword; - case 'null': return token = SyntaxKind.NullKeyword; - } - return token = SyntaxKind.Unknown; - } - // some - value += String.fromCharCode(code); - pos++; - return token = SyntaxKind.Unknown; - } - } - - function isUnknownContentCharacter(code: CharacterCodes) { - if (isWhitespace(code) || isLineBreak(code)) { - return false; - } - switch (code) { - case CharacterCodes.closeBrace: - case CharacterCodes.closeBracket: - case CharacterCodes.openBrace: - case CharacterCodes.openBracket: - case CharacterCodes.doubleQuote: - case CharacterCodes.colon: - case CharacterCodes.comma: - case CharacterCodes.slash: - return false; - } - return true; - } - - - function scanNextNonTrivia(): SyntaxKind { - let result: SyntaxKind; - do { - result = scanNext(); - } while (result >= SyntaxKind.LineCommentTrivia && result <= SyntaxKind.Trivia); - return result; - } - - return { - setPosition: setPosition, - getPosition: () => pos, - scan: ignoreTrivia ? scanNextNonTrivia : scanNext, - getToken: () => token, - getTokenValue: () => value, - getTokenOffset: () => tokenOffset, - getTokenLength: () => pos - tokenOffset, - getTokenError: () => scanError - }; -} - -function isWhitespace(ch: number): boolean { - return ch === CharacterCodes.space || ch === CharacterCodes.tab || ch === CharacterCodes.verticalTab || ch === CharacterCodes.formFeed || - ch === CharacterCodes.nonBreakingSpace || ch === CharacterCodes.ogham || ch >= CharacterCodes.enQuad && ch <= CharacterCodes.zeroWidthSpace || - ch === CharacterCodes.narrowNoBreakSpace || ch === CharacterCodes.mathematicalSpace || ch === CharacterCodes.ideographicSpace || ch === CharacterCodes.byteOrderMark; -} - -function isLineBreak(ch: number): boolean { - return ch === CharacterCodes.lineFeed || ch === CharacterCodes.carriageReturn || ch === CharacterCodes.lineSeparator || ch === CharacterCodes.paragraphSeparator; -} - -function isDigit(ch: number): boolean { - return ch >= CharacterCodes._0 && ch <= CharacterCodes._9; -} - -const enum CharacterCodes { - nullCharacter = 0, - maxAsciiCharacter = 0x7F, - - lineFeed = 0x0A, // \n - carriageReturn = 0x0D, // \r - lineSeparator = 0x2028, - paragraphSeparator = 0x2029, - - // REVIEW: do we need to support this? The scanner doesn't, but our IText does. This seems - // like an odd disparity? (Or maybe it's completely fine for them to be different). - nextLine = 0x0085, - - // Unicode 3.0 space characters - space = 0x0020, // " " - nonBreakingSpace = 0x00A0, // - enQuad = 0x2000, - emQuad = 0x2001, - enSpace = 0x2002, - emSpace = 0x2003, - threePerEmSpace = 0x2004, - fourPerEmSpace = 0x2005, - sixPerEmSpace = 0x2006, - figureSpace = 0x2007, - punctuationSpace = 0x2008, - thinSpace = 0x2009, - hairSpace = 0x200A, - zeroWidthSpace = 0x200B, - narrowNoBreakSpace = 0x202F, - ideographicSpace = 0x3000, - mathematicalSpace = 0x205F, - ogham = 0x1680, - - _ = 0x5F, - $ = 0x24, - - _0 = 0x30, - _1 = 0x31, - _2 = 0x32, - _3 = 0x33, - _4 = 0x34, - _5 = 0x35, - _6 = 0x36, - _7 = 0x37, - _8 = 0x38, - _9 = 0x39, - - a = 0x61, - b = 0x62, - c = 0x63, - d = 0x64, - e = 0x65, - f = 0x66, - g = 0x67, - h = 0x68, - i = 0x69, - j = 0x6A, - k = 0x6B, - l = 0x6C, - m = 0x6D, - n = 0x6E, - o = 0x6F, - p = 0x70, - q = 0x71, - r = 0x72, - s = 0x73, - t = 0x74, - u = 0x75, - v = 0x76, - w = 0x77, - x = 0x78, - y = 0x79, - z = 0x7A, - - A = 0x41, - B = 0x42, - C = 0x43, - D = 0x44, - E = 0x45, - F = 0x46, - G = 0x47, - H = 0x48, - I = 0x49, - J = 0x4A, - K = 0x4B, - L = 0x4C, - M = 0x4D, - N = 0x4E, - O = 0x4F, - P = 0x50, - Q = 0x51, - R = 0x52, - S = 0x53, - T = 0x54, - U = 0x55, - V = 0x56, - W = 0x57, - X = 0x58, - Y = 0x59, - Z = 0x5A, - - ampersand = 0x26, // & - asterisk = 0x2A, // * - at = 0x40, // @ - backslash = 0x5C, // \ - bar = 0x7C, // | - caret = 0x5E, // ^ - closeBrace = 0x7D, // } - closeBracket = 0x5D, // ] - closeParen = 0x29, // ) - colon = 0x3A, // : - comma = 0x2C, // , - dot = 0x2E, // . - doubleQuote = 0x22, // " - equals = 0x3D, // = - exclamation = 0x21, // ! - greaterThan = 0x3E, // > - lessThan = 0x3C, // < - minus = 0x2D, // - - openBrace = 0x7B, // { - openBracket = 0x5B, // [ - openParen = 0x28, // ( - percent = 0x25, // % - plus = 0x2B, // + - question = 0x3F, // ? - semicolon = 0x3B, // ; - singleQuote = 0x27, // ' - slash = 0x2F, // / - tilde = 0x7E, // ~ - - backspace = 0x08, // \b - formFeed = 0x0C, // \f - byteOrderMark = 0xFEFF, - tab = 0x09, // \t - verticalTab = 0x0B, // \v -} - -interface NodeImpl extends Node { - type: NodeType; - value?: any; - offset: number; - length: number; - colonOffset?: number; - parent?: NodeImpl; - children?: NodeImpl[]; -} - -/** - * For a given offset, evaluate the location in the JSON document. Each segment in the location path is either a property name or an array index. - */ -export function getLocation(text: string, position: number): Location { - const segments: Segment[] = []; // strings or numbers - const earlyReturnException = new Object(); - let previousNode: NodeImpl | undefined = undefined; - const previousNodeInst: NodeImpl = { - value: {}, - offset: 0, - length: 0, - type: 'object', - parent: undefined - }; - let isAtPropertyKey = false; - function setPreviousNode(value: string, offset: number, length: number, type: NodeType) { - previousNodeInst.value = value; - previousNodeInst.offset = offset; - previousNodeInst.length = length; - previousNodeInst.type = type; - previousNodeInst.colonOffset = undefined; - previousNode = previousNodeInst; - } - try { - - visit(text, { - onObjectBegin: (offset: number, length: number) => { - if (position <= offset) { - throw earlyReturnException; - } - previousNode = undefined; - isAtPropertyKey = position > offset; - segments.push(''); // push a placeholder (will be replaced) - }, - onObjectProperty: (name: string, offset: number, length: number) => { - if (position < offset) { - throw earlyReturnException; - } - setPreviousNode(name, offset, length, 'property'); - segments[segments.length - 1] = name; - if (position <= offset + length) { - throw earlyReturnException; - } - }, - onObjectEnd: (offset: number, length: number) => { - if (position <= offset) { - throw earlyReturnException; - } - previousNode = undefined; - segments.pop(); - }, - onArrayBegin: (offset: number, length: number) => { - if (position <= offset) { - throw earlyReturnException; - } - previousNode = undefined; - segments.push(0); - }, - onArrayEnd: (offset: number, length: number) => { - if (position <= offset) { - throw earlyReturnException; - } - previousNode = undefined; - segments.pop(); - }, - onLiteralValue: (value: any, offset: number, length: number) => { - if (position < offset) { - throw earlyReturnException; - } - setPreviousNode(value, offset, length, getNodeType(value)); - - if (position <= offset + length) { - throw earlyReturnException; - } - }, - onSeparator: (sep: string, offset: number, length: number) => { - if (position <= offset) { - throw earlyReturnException; - } - if (sep === ':' && previousNode && previousNode.type === 'property') { - previousNode.colonOffset = offset; - isAtPropertyKey = false; - previousNode = undefined; - } else if (sep === ',') { - const last = segments[segments.length - 1]; - if (typeof last === 'number') { - segments[segments.length - 1] = last + 1; - } else { - isAtPropertyKey = true; - segments[segments.length - 1] = ''; - } - previousNode = undefined; - } - } - }); - } catch (e) { - if (e !== earlyReturnException) { - throw e; - } - } - - return { - path: segments, - previousNode, - isAtPropertyKey, - matches: (pattern: Segment[]) => { - let k = 0; - for (let i = 0; k < pattern.length && i < segments.length; i++) { - if (pattern[k] === segments[i] || pattern[k] === '*') { - k++; - } else if (pattern[k] !== '**') { - return false; - } - } - return k === pattern.length; - } - }; -} - - -/** - * Parses the given text and returns the object the JSON content represents. On invalid input, the parser tries to be as fault tolerant as possible, but still return a result. - * Therefore always check the errors list to find out if the input was valid. - */ -export function parse(text: string, errors: ParseError[] = [], options: ParseOptions = ParseOptions.DEFAULT): any { - let currentProperty: string | null = null; - let currentParent: any = []; - const previousParents: any[] = []; - - function onValue(value: any) { - if (Array.isArray(currentParent)) { - (currentParent).push(value); - } else if (currentProperty !== null) { - currentParent[currentProperty] = value; - } - } - - const visitor: JSONVisitor = { - onObjectBegin: () => { - const object = {}; - onValue(object); - previousParents.push(currentParent); - currentParent = object; - currentProperty = null; - }, - onObjectProperty: (name: string) => { - currentProperty = name; - }, - onObjectEnd: () => { - currentParent = previousParents.pop(); - }, - onArrayBegin: () => { - const array: any[] = []; - onValue(array); - previousParents.push(currentParent); - currentParent = array; - currentProperty = null; - }, - onArrayEnd: () => { - currentParent = previousParents.pop(); - }, - onLiteralValue: onValue, - onError: (error: ParseErrorCode, offset: number, length: number) => { - errors.push({ error, offset, length }); - } - }; - visit(text, visitor, options); - return currentParent[0]; -} - - -/** - * Parses the given text and returns a tree representation the JSON content. On invalid input, the parser tries to be as fault tolerant as possible, but still return a result. - */ -export function parseTree(text: string, errors: ParseError[] = [], options: ParseOptions = ParseOptions.DEFAULT): Node { - let currentParent: NodeImpl = { type: 'array', offset: -1, length: -1, children: [], parent: undefined }; // artificial root - - function ensurePropertyComplete(endOffset: number) { - if (currentParent.type === 'property') { - currentParent.length = endOffset - currentParent.offset; - currentParent = currentParent.parent!; - } - } - - function onValue(valueNode: Node): Node { - currentParent.children!.push(valueNode); - return valueNode; - } - - const visitor: JSONVisitor = { - onObjectBegin: (offset: number) => { - currentParent = onValue({ type: 'object', offset, length: -1, parent: currentParent, children: [] }); - }, - onObjectProperty: (name: string, offset: number, length: number) => { - currentParent = onValue({ type: 'property', offset, length: -1, parent: currentParent, children: [] }); - currentParent.children!.push({ type: 'string', value: name, offset, length, parent: currentParent }); - }, - onObjectEnd: (offset: number, length: number) => { - currentParent.length = offset + length - currentParent.offset; - currentParent = currentParent.parent!; - ensurePropertyComplete(offset + length); - }, - onArrayBegin: (offset: number, length: number) => { - currentParent = onValue({ type: 'array', offset, length: -1, parent: currentParent, children: [] }); - }, - onArrayEnd: (offset: number, length: number) => { - currentParent.length = offset + length - currentParent.offset; - currentParent = currentParent.parent!; - ensurePropertyComplete(offset + length); - }, - onLiteralValue: (value: any, offset: number, length: number) => { - onValue({ type: getNodeType(value), offset, length, parent: currentParent, value }); - ensurePropertyComplete(offset + length); - }, - onSeparator: (sep: string, offset: number, length: number) => { - if (currentParent.type === 'property') { - if (sep === ':') { - currentParent.colonOffset = offset; - } else if (sep === ',') { - ensurePropertyComplete(offset); - } - } - }, - onError: (error: ParseErrorCode, offset: number, length: number) => { - errors.push({ error, offset, length }); - } - }; - visit(text, visitor, options); - - const result = currentParent.children![0]; - if (result) { - delete result.parent; - } - return result; -} - -/** - * Finds the node at the given path in a JSON DOM. - */ -export function findNodeAtLocation(root: Node, path: JSONPath): Node | undefined { - if (!root) { - return undefined; - } - let node = root; - for (const segment of path) { - if (typeof segment === 'string') { - if (node.type !== 'object' || !Array.isArray(node.children)) { - return undefined; - } - let found = false; - for (const propertyNode of node.children) { - if (Array.isArray(propertyNode.children) && propertyNode.children[0].value === segment) { - node = propertyNode.children[1]; - found = true; - break; - } - } - if (!found) { - return undefined; - } - } else { - const index = segment; - if (node.type !== 'array' || index < 0 || !Array.isArray(node.children) || index >= node.children.length) { - return undefined; - } - node = node.children[index]; - } - } - return node; -} - -/** - * Gets the JSON path of the given JSON DOM node - */ -export function getNodePath(node: Node): JSONPath { - if (!node.parent || !node.parent.children) { - return []; - } - const path = getNodePath(node.parent); - if (node.parent.type === 'property') { - const key = node.parent.children[0].value; - path.push(key); - } else if (node.parent.type === 'array') { - const index = node.parent.children.indexOf(node); - if (index !== -1) { - path.push(index); - } - } - return path; -} - -/** - * Evaluates the JavaScript object of the given JSON DOM node - */ -export function getNodeValue(node: Node): any { - switch (node.type) { - case 'array': - return node.children!.map(getNodeValue); - case 'object': { - const obj = Object.create(null); - for (const prop of node.children!) { - const valueNode = prop.children![1]; - if (valueNode) { - obj[prop.children![0].value] = getNodeValue(valueNode); - } - } - return obj; - } - case 'null': - case 'string': - case 'number': - case 'boolean': - return node.value; - default: - return undefined; - } - -} - -export function contains(node: Node, offset: number, includeRightBound = false): boolean { - return (offset >= node.offset && offset < (node.offset + node.length)) || includeRightBound && (offset === (node.offset + node.length)); -} - -/** - * Finds the most inner node at the given offset. If includeRightBound is set, also finds nodes that end at the given offset. - */ -export function findNodeAtOffset(node: Node, offset: number, includeRightBound = false): Node | undefined { - if (contains(node, offset, includeRightBound)) { - const children = node.children; - if (Array.isArray(children)) { - for (let i = 0; i < children.length && children[i].offset <= offset; i++) { - const item = findNodeAtOffset(children[i], offset, includeRightBound); - if (item) { - return item; - } - } - - } - return node; - } - return undefined; -} - - -/** - * Parses the given text and invokes the visitor functions for each object, array and literal reached. - */ -export function visit(text: string, visitor: JSONVisitor, options: ParseOptions = ParseOptions.DEFAULT): any { - - const _scanner = createScanner(text, false); - - function toNoArgVisit(visitFunction?: (offset: number, length: number) => void): () => void { - return visitFunction ? () => visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength()) : () => true; - } - function toOneArgVisit(visitFunction?: (arg: T, offset: number, length: number) => void): (arg: T) => void { - return visitFunction ? (arg: T) => visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength()) : () => true; - } - - const onObjectBegin = toNoArgVisit(visitor.onObjectBegin), - onObjectProperty = toOneArgVisit(visitor.onObjectProperty), - onObjectEnd = toNoArgVisit(visitor.onObjectEnd), - onArrayBegin = toNoArgVisit(visitor.onArrayBegin), - onArrayEnd = toNoArgVisit(visitor.onArrayEnd), - onLiteralValue = toOneArgVisit(visitor.onLiteralValue), - onSeparator = toOneArgVisit(visitor.onSeparator), - onComment = toNoArgVisit(visitor.onComment), - onError = toOneArgVisit(visitor.onError); - - const disallowComments = options && options.disallowComments; - const allowTrailingComma = options && options.allowTrailingComma; - function scanNext(): SyntaxKind { - while (true) { - const token = _scanner.scan(); - switch (_scanner.getTokenError()) { - case ScanError.InvalidUnicode: - handleError(ParseErrorCode.InvalidUnicode); - break; - case ScanError.InvalidEscapeCharacter: - handleError(ParseErrorCode.InvalidEscapeCharacter); - break; - case ScanError.UnexpectedEndOfNumber: - handleError(ParseErrorCode.UnexpectedEndOfNumber); - break; - case ScanError.UnexpectedEndOfComment: - if (!disallowComments) { - handleError(ParseErrorCode.UnexpectedEndOfComment); - } - break; - case ScanError.UnexpectedEndOfString: - handleError(ParseErrorCode.UnexpectedEndOfString); - break; - case ScanError.InvalidCharacter: - handleError(ParseErrorCode.InvalidCharacter); - break; - } - switch (token) { - case SyntaxKind.LineCommentTrivia: - case SyntaxKind.BlockCommentTrivia: - if (disallowComments) { - handleError(ParseErrorCode.InvalidCommentToken); - } else { - onComment(); - } - break; - case SyntaxKind.Unknown: - handleError(ParseErrorCode.InvalidSymbol); - break; - case SyntaxKind.Trivia: - case SyntaxKind.LineBreakTrivia: - break; - default: - return token; - } - } - } - - function handleError(error: ParseErrorCode, skipUntilAfter: SyntaxKind[] = [], skipUntil: SyntaxKind[] = []): void { - onError(error); - if (skipUntilAfter.length + skipUntil.length > 0) { - let token = _scanner.getToken(); - while (token !== SyntaxKind.EOF) { - if (skipUntilAfter.indexOf(token) !== -1) { - scanNext(); - break; - } else if (skipUntil.indexOf(token) !== -1) { - break; - } - token = scanNext(); - } - } - } - - function parseString(isValue: boolean): boolean { - const value = _scanner.getTokenValue(); - if (isValue) { - onLiteralValue(value); - } else { - onObjectProperty(value); - } - scanNext(); - return true; - } - - function parseLiteral(): boolean { - switch (_scanner.getToken()) { - case SyntaxKind.NumericLiteral: { - let value = 0; - try { - value = JSON.parse(_scanner.getTokenValue()); - if (typeof value !== 'number') { - handleError(ParseErrorCode.InvalidNumberFormat); - value = 0; - } - } catch (e) { - handleError(ParseErrorCode.InvalidNumberFormat); - } - onLiteralValue(value); - break; - } - case SyntaxKind.NullKeyword: - onLiteralValue(null); - break; - case SyntaxKind.TrueKeyword: - onLiteralValue(true); - break; - case SyntaxKind.FalseKeyword: - onLiteralValue(false); - break; - default: - return false; - } - scanNext(); - return true; - } - - function parseProperty(): boolean { - if (_scanner.getToken() !== SyntaxKind.StringLiteral) { - handleError(ParseErrorCode.PropertyNameExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken]); - return false; - } - parseString(false); - if (_scanner.getToken() === SyntaxKind.ColonToken) { - onSeparator(':'); - scanNext(); // consume colon - - if (!parseValue()) { - handleError(ParseErrorCode.ValueExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken]); - } - } else { - handleError(ParseErrorCode.ColonExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken]); - } - return true; - } - - function parseObject(): boolean { - onObjectBegin(); - scanNext(); // consume open brace - - let needsComma = false; - while (_scanner.getToken() !== SyntaxKind.CloseBraceToken && _scanner.getToken() !== SyntaxKind.EOF) { - if (_scanner.getToken() === SyntaxKind.CommaToken) { - if (!needsComma) { - handleError(ParseErrorCode.ValueExpected, [], []); - } - onSeparator(','); - scanNext(); // consume comma - if (_scanner.getToken() === SyntaxKind.CloseBraceToken && allowTrailingComma) { - break; - } - } else if (needsComma) { - handleError(ParseErrorCode.CommaExpected, [], []); - } - if (!parseProperty()) { - handleError(ParseErrorCode.ValueExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken]); - } - needsComma = true; - } - onObjectEnd(); - if (_scanner.getToken() !== SyntaxKind.CloseBraceToken) { - handleError(ParseErrorCode.CloseBraceExpected, [SyntaxKind.CloseBraceToken], []); - } else { - scanNext(); // consume close brace - } - return true; - } - - function parseArray(): boolean { - onArrayBegin(); - scanNext(); // consume open bracket - - let needsComma = false; - while (_scanner.getToken() !== SyntaxKind.CloseBracketToken && _scanner.getToken() !== SyntaxKind.EOF) { - if (_scanner.getToken() === SyntaxKind.CommaToken) { - if (!needsComma) { - handleError(ParseErrorCode.ValueExpected, [], []); - } - onSeparator(','); - scanNext(); // consume comma - if (_scanner.getToken() === SyntaxKind.CloseBracketToken && allowTrailingComma) { - break; - } - } else if (needsComma) { - handleError(ParseErrorCode.CommaExpected, [], []); - } - if (!parseValue()) { - handleError(ParseErrorCode.ValueExpected, [], [SyntaxKind.CloseBracketToken, SyntaxKind.CommaToken]); - } - needsComma = true; - } - onArrayEnd(); - if (_scanner.getToken() !== SyntaxKind.CloseBracketToken) { - handleError(ParseErrorCode.CloseBracketExpected, [SyntaxKind.CloseBracketToken], []); - } else { - scanNext(); // consume close bracket - } - return true; - } - - function parseValue(): boolean { - switch (_scanner.getToken()) { - case SyntaxKind.OpenBracketToken: - return parseArray(); - case SyntaxKind.OpenBraceToken: - return parseObject(); - case SyntaxKind.StringLiteral: - return parseString(true); - default: - return parseLiteral(); - } - } - - scanNext(); - if (_scanner.getToken() === SyntaxKind.EOF) { - if (options.allowEmptyContent) { - return true; - } - handleError(ParseErrorCode.ValueExpected, [], []); - return false; - } - if (!parseValue()) { - handleError(ParseErrorCode.ValueExpected, [], []); - return false; - } - if (_scanner.getToken() !== SyntaxKind.EOF) { - handleError(ParseErrorCode.EndOfFileExpected, [], []); - } - return true; -} - -export function getNodeType(value: any): NodeType { - switch (typeof value) { - case 'boolean': return 'boolean'; - case 'number': return 'number'; - case 'string': return 'string'; - case 'object': { - if (!value) { - return 'null'; - } else if (Array.isArray(value)) { - return 'array'; - } - return 'object'; - } - default: return 'null'; - } -} diff --git a/src/vs/base/common/jsonEdit.ts b/src/vs/base/common/jsonEdit.ts deleted file mode 100644 index 9d62ed9e..00000000 --- a/src/vs/base/common/jsonEdit.ts +++ /dev/null @@ -1,176 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { findNodeAtLocation, JSONPath, Node, ParseError, parseTree, Segment } from './json'; -import { Edit, format, FormattingOptions, isEOL } from './jsonFormatter'; - - -export function removeProperty(text: string, path: JSONPath, formattingOptions: FormattingOptions): Edit[] { - return setProperty(text, path, undefined, formattingOptions); -} - -export function setProperty(text: string, originalPath: JSONPath, value: any, formattingOptions: FormattingOptions, getInsertionIndex?: (properties: string[]) => number): Edit[] { - const path = originalPath.slice(); - const errors: ParseError[] = []; - const root = parseTree(text, errors); - let parent: Node | undefined = undefined; - - let lastSegment: Segment | undefined = undefined; - while (path.length > 0) { - lastSegment = path.pop(); - parent = findNodeAtLocation(root, path); - if (parent === undefined && value !== undefined) { - if (typeof lastSegment === 'string') { - value = { [lastSegment]: value }; - } else { - value = [value]; - } - } else { - break; - } - } - - if (!parent) { - // empty document - if (value === undefined) { // delete - return []; // property does not exist, nothing to do - } - return withFormatting(text, { offset: root ? root.offset : 0, length: root ? root.length : 0, content: JSON.stringify(value) }, formattingOptions); - } else if (parent.type === 'object' && typeof lastSegment === 'string' && Array.isArray(parent.children)) { - const existing = findNodeAtLocation(parent, [lastSegment]); - if (existing !== undefined) { - if (value === undefined) { // delete - if (!existing.parent) { - throw new Error('Malformed AST'); - } - const propertyIndex = parent.children.indexOf(existing.parent); - let removeBegin: number; - let removeEnd = existing.parent.offset + existing.parent.length; - if (propertyIndex > 0) { - // remove the comma of the previous node - const previous = parent.children[propertyIndex - 1]; - removeBegin = previous.offset + previous.length; - } else { - removeBegin = parent.offset + 1; - if (parent.children.length > 1) { - // remove the comma of the next node - const next = parent.children[1]; - removeEnd = next.offset; - } - } - return withFormatting(text, { offset: removeBegin, length: removeEnd - removeBegin, content: '' }, formattingOptions); - } else { - // set value of existing property - return withFormatting(text, { offset: existing.offset, length: existing.length, content: JSON.stringify(value) }, formattingOptions); - } - } else { - if (value === undefined) { // delete - return []; // property does not exist, nothing to do - } - const newProperty = `${JSON.stringify(lastSegment)}: ${JSON.stringify(value)}`; - const index = getInsertionIndex ? getInsertionIndex(parent.children.map(p => p.children![0].value)) : parent.children.length; - let edit: Edit; - if (index > 0) { - const previous = parent.children[index - 1]; - edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty }; - } else if (parent.children.length === 0) { - edit = { offset: parent.offset + 1, length: 0, content: newProperty }; - } else { - edit = { offset: parent.offset + 1, length: 0, content: newProperty + ',' }; - } - return withFormatting(text, edit, formattingOptions); - } - } else if (parent.type === 'array' && typeof lastSegment === 'number' && Array.isArray(parent.children)) { - if (value !== undefined) { - // Insert - const newProperty = `${JSON.stringify(value)}`; - let edit: Edit; - if (parent.children.length === 0 || lastSegment === 0) { - edit = { offset: parent.offset + 1, length: 0, content: parent.children.length === 0 ? newProperty : newProperty + ',' }; - } else { - const index = lastSegment === -1 || lastSegment > parent.children.length ? parent.children.length : lastSegment; - const previous = parent.children[index - 1]; - edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty }; - } - return withFormatting(text, edit, formattingOptions); - } else { - //Removal - const removalIndex = lastSegment; - const toRemove = parent.children[removalIndex]; - let edit: Edit; - if (parent.children.length === 1) { - // only item - edit = { offset: parent.offset + 1, length: parent.length - 2, content: '' }; - } else if (parent.children.length - 1 === removalIndex) { - // last item - const previous = parent.children[removalIndex - 1]; - const offset = previous.offset + previous.length; - const parentEndOffset = parent.offset + parent.length; - edit = { offset, length: parentEndOffset - 2 - offset, content: '' }; - } else { - edit = { offset: toRemove.offset, length: parent.children[removalIndex + 1].offset - toRemove.offset, content: '' }; - } - return withFormatting(text, edit, formattingOptions); - } - } else { - throw new Error(`Can not add ${typeof lastSegment !== 'number' ? 'index' : 'property'} to parent of type ${parent.type}`); - } -} - -export function withFormatting(text: string, edit: Edit, formattingOptions: FormattingOptions): Edit[] { - // apply the edit - let newText = applyEdit(text, edit); - - // format the new text - let begin = edit.offset; - let end = edit.offset + edit.content.length; - if (edit.length === 0 || edit.content.length === 0) { // insert or remove - while (begin > 0 && !isEOL(newText, begin - 1)) { - begin--; - } - while (end < newText.length && !isEOL(newText, end)) { - end++; - } - } - - const edits = format(newText, { offset: begin, length: end - begin }, formattingOptions); - - // apply the formatting edits and track the begin and end offsets of the changes - for (let i = edits.length - 1; i >= 0; i--) { - const curr = edits[i]; - newText = applyEdit(newText, curr); - begin = Math.min(begin, curr.offset); - end = Math.max(end, curr.offset + curr.length); - end += curr.content.length - curr.length; - } - // create a single edit with all changes - const editLength = text.length - (newText.length - end) - begin; - return [{ offset: begin, length: editLength, content: newText.substring(begin, end) }]; -} - -export function applyEdit(text: string, edit: Edit): string { - return text.substring(0, edit.offset) + edit.content + text.substring(edit.offset + edit.length); -} - -export function applyEdits(text: string, edits: Edit[]): string { - const sortedEdits = edits.slice(0).sort((a, b) => { - const diff = a.offset - b.offset; - if (diff === 0) { - return a.length - b.length; - } - return diff; - }); - let lastModifiedOffset = text.length; - for (let i = sortedEdits.length - 1; i >= 0; i--) { - const e = sortedEdits[i]; - if (e.offset + e.length <= lastModifiedOffset) { - text = applyEdit(text, e); - } else { - throw new Error('Overlapping edit'); - } - lastModifiedOffset = e.offset; - } - return text; -} diff --git a/src/vs/base/common/jsonErrorMessages.ts b/src/vs/base/common/jsonErrorMessages.ts deleted file mode 100644 index 49b5b988..00000000 --- a/src/vs/base/common/jsonErrorMessages.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * Extracted from json.ts to keep json nls free. - */ -import { localize } from 'vs/nls'; -import { ParseErrorCode } from './json'; - -export function getParseErrorMessage(errorCode: ParseErrorCode): string { - switch (errorCode) { - case ParseErrorCode.InvalidSymbol: return localize('error.invalidSymbol', 'Invalid symbol'); - case ParseErrorCode.InvalidNumberFormat: return localize('error.invalidNumberFormat', 'Invalid number format'); - case ParseErrorCode.PropertyNameExpected: return localize('error.propertyNameExpected', 'Property name expected'); - case ParseErrorCode.ValueExpected: return localize('error.valueExpected', 'Value expected'); - case ParseErrorCode.ColonExpected: return localize('error.colonExpected', 'Colon expected'); - case ParseErrorCode.CommaExpected: return localize('error.commaExpected', 'Comma expected'); - case ParseErrorCode.CloseBraceExpected: return localize('error.closeBraceExpected', 'Closing brace expected'); - case ParseErrorCode.CloseBracketExpected: return localize('error.closeBracketExpected', 'Closing bracket expected'); - case ParseErrorCode.EndOfFileExpected: return localize('error.endOfFileExpected', 'End of file expected'); - default: - return ''; - } -} diff --git a/src/vs/base/common/jsonFormatter.ts b/src/vs/base/common/jsonFormatter.ts deleted file mode 100644 index 18fc0d53..00000000 --- a/src/vs/base/common/jsonFormatter.ts +++ /dev/null @@ -1,261 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { createScanner, ScanError, SyntaxKind } from './json'; - -export interface FormattingOptions { - /** - * If indentation is based on spaces (`insertSpaces` = true), then what is the number of spaces that make an indent? - */ - tabSize?: number; - /** - * Is indentation based on spaces? - */ - insertSpaces?: boolean; - /** - * The default 'end of line' character. If not set, '\n' is used as default. - */ - eol?: string; -} - -/** - * Represents a text modification - */ -export interface Edit { - /** - * The start offset of the modification. - */ - offset: number; - /** - * The length of the modification. Must not be negative. Empty length represents an *insert*. - */ - length: number; - /** - * The new content. Empty content represents a *remove*. - */ - content: string; -} - -/** - * A text range in the document -*/ -export interface Range { - /** - * The start offset of the range. - */ - offset: number; - /** - * The length of the range. Must not be negative. - */ - length: number; -} - - -export function format(documentText: string, range: Range | undefined, options: FormattingOptions): Edit[] { - let initialIndentLevel: number; - let formatText: string; - let formatTextStart: number; - let rangeStart: number; - let rangeEnd: number; - if (range) { - rangeStart = range.offset; - rangeEnd = rangeStart + range.length; - - formatTextStart = rangeStart; - while (formatTextStart > 0 && !isEOL(documentText, formatTextStart - 1)) { - formatTextStart--; - } - let endOffset = rangeEnd; - while (endOffset < documentText.length && !isEOL(documentText, endOffset)) { - endOffset++; - } - formatText = documentText.substring(formatTextStart, endOffset); - initialIndentLevel = computeIndentLevel(formatText, options); - } else { - formatText = documentText; - initialIndentLevel = 0; - formatTextStart = 0; - rangeStart = 0; - rangeEnd = documentText.length; - } - const eol = getEOL(options, documentText); - - let lineBreak = false; - let indentLevel = 0; - let indentValue: string; - if (options.insertSpaces) { - indentValue = repeat(' ', options.tabSize || 4); - } else { - indentValue = '\t'; - } - - const scanner = createScanner(formatText, false); - let hasError = false; - - function newLineAndIndent(): string { - return eol + repeat(indentValue, initialIndentLevel + indentLevel); - } - function scanNext(): SyntaxKind { - let token = scanner.scan(); - lineBreak = false; - while (token === SyntaxKind.Trivia || token === SyntaxKind.LineBreakTrivia) { - lineBreak = lineBreak || (token === SyntaxKind.LineBreakTrivia); - token = scanner.scan(); - } - hasError = token === SyntaxKind.Unknown || scanner.getTokenError() !== ScanError.None; - return token; - } - const editOperations: Edit[] = []; - function addEdit(text: string, startOffset: number, endOffset: number) { - if (!hasError && startOffset < rangeEnd && endOffset > rangeStart && documentText.substring(startOffset, endOffset) !== text) { - editOperations.push({ offset: startOffset, length: endOffset - startOffset, content: text }); - } - } - - let firstToken = scanNext(); - - if (firstToken !== SyntaxKind.EOF) { - const firstTokenStart = scanner.getTokenOffset() + formatTextStart; - const initialIndent = repeat(indentValue, initialIndentLevel); - addEdit(initialIndent, formatTextStart, firstTokenStart); - } - - while (firstToken !== SyntaxKind.EOF) { - let firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + formatTextStart; - let secondToken = scanNext(); - - let replaceContent = ''; - while (!lineBreak && (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia)) { - // comments on the same line: keep them on the same line, but ignore them otherwise - const commentTokenStart = scanner.getTokenOffset() + formatTextStart; - addEdit(' ', firstTokenEnd, commentTokenStart); - firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + formatTextStart; - replaceContent = secondToken === SyntaxKind.LineCommentTrivia ? newLineAndIndent() : ''; - secondToken = scanNext(); - } - - if (secondToken === SyntaxKind.CloseBraceToken) { - if (firstToken !== SyntaxKind.OpenBraceToken) { - indentLevel--; - replaceContent = newLineAndIndent(); - } - } else if (secondToken === SyntaxKind.CloseBracketToken) { - if (firstToken !== SyntaxKind.OpenBracketToken) { - indentLevel--; - replaceContent = newLineAndIndent(); - } - } else { - switch (firstToken) { - case SyntaxKind.OpenBracketToken: - case SyntaxKind.OpenBraceToken: - indentLevel++; - replaceContent = newLineAndIndent(); - break; - case SyntaxKind.CommaToken: - case SyntaxKind.LineCommentTrivia: - replaceContent = newLineAndIndent(); - break; - case SyntaxKind.BlockCommentTrivia: - if (lineBreak) { - replaceContent = newLineAndIndent(); - } else { - // symbol following comment on the same line: keep on same line, separate with ' ' - replaceContent = ' '; - } - break; - case SyntaxKind.ColonToken: - replaceContent = ' '; - break; - case SyntaxKind.StringLiteral: - if (secondToken === SyntaxKind.ColonToken) { - replaceContent = ''; - break; - } - // fall through - case SyntaxKind.NullKeyword: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NumericLiteral: - case SyntaxKind.CloseBraceToken: - case SyntaxKind.CloseBracketToken: - if (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia) { - replaceContent = ' '; - } else if (secondToken !== SyntaxKind.CommaToken && secondToken !== SyntaxKind.EOF) { - hasError = true; - } - break; - case SyntaxKind.Unknown: - hasError = true; - break; - } - if (lineBreak && (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia)) { - replaceContent = newLineAndIndent(); - } - - } - const secondTokenStart = scanner.getTokenOffset() + formatTextStart; - addEdit(replaceContent, firstTokenEnd, secondTokenStart); - firstToken = secondToken; - } - return editOperations; -} - -/** - * Creates a formatted string out of the object passed as argument, using the given formatting options - * @param any The object to stringify and format - * @param options The formatting options to use - */ -export function toFormattedString(obj: any, options: FormattingOptions) { - const content = JSON.stringify(obj, undefined, options.insertSpaces ? options.tabSize || 4 : '\t'); - if (options.eol !== undefined) { - return content.replace(/\r\n|\r|\n/g, options.eol); - } - return content; -} - -function repeat(s: string, count: number): string { - let result = ''; - for (let i = 0; i < count; i++) { - result += s; - } - return result; -} - -function computeIndentLevel(content: string, options: FormattingOptions): number { - let i = 0; - let nChars = 0; - const tabSize = options.tabSize || 4; - while (i < content.length) { - const ch = content.charAt(i); - if (ch === ' ') { - nChars++; - } else if (ch === '\t') { - nChars += tabSize; - } else { - break; - } - i++; - } - return Math.floor(nChars / tabSize); -} - -export function getEOL(options: FormattingOptions, text: string): string { - for (let i = 0; i < text.length; i++) { - const ch = text.charAt(i); - if (ch === '\r') { - if (i + 1 < text.length && text.charAt(i + 1) === '\n') { - return '\r\n'; - } - return '\r'; - } else if (ch === '\n') { - return '\n'; - } - } - return (options && options.eol) || '\n'; -} - -export function isEOL(text: string, offset: number) { - return '\r\n'.indexOf(text.charAt(offset)) !== -1; -} diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts deleted file mode 100644 index fbf6d981..00000000 --- a/src/vs/base/common/jsonSchema.ts +++ /dev/null @@ -1,267 +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 type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'null' | 'array' | 'object'; - -export interface IJSONSchema { - id?: string; - $id?: string; - $schema?: string; - type?: JSONSchemaType | JSONSchemaType[]; - title?: string; - default?: any; - definitions?: IJSONSchemaMap; - description?: string; - properties?: IJSONSchemaMap; - patternProperties?: IJSONSchemaMap; - additionalProperties?: boolean | IJSONSchema; - minProperties?: number; - maxProperties?: number; - dependencies?: IJSONSchemaMap | { [prop: string]: string[] }; - items?: IJSONSchema | IJSONSchema[]; - minItems?: number; - maxItems?: number; - uniqueItems?: boolean; - additionalItems?: boolean | IJSONSchema; - pattern?: string; - minLength?: number; - maxLength?: number; - minimum?: number; - maximum?: number; - exclusiveMinimum?: boolean | number; - exclusiveMaximum?: boolean | number; - multipleOf?: number; - required?: string[]; - $ref?: string; - anyOf?: IJSONSchema[]; - allOf?: IJSONSchema[]; - oneOf?: IJSONSchema[]; - not?: IJSONSchema; - enum?: any[]; - format?: string; - - // schema draft 06 - const?: any; - contains?: IJSONSchema; - propertyNames?: IJSONSchema; - examples?: any[]; - - // schema draft 07 - $comment?: string; - if?: IJSONSchema; - then?: IJSONSchema; - else?: IJSONSchema; - - // schema 2019-09 - unevaluatedProperties?: boolean | IJSONSchema; - unevaluatedItems?: boolean | IJSONSchema; - minContains?: number; - maxContains?: number; - deprecated?: boolean; - dependentRequired?: { [prop: string]: string[] }; - dependentSchemas?: IJSONSchemaMap; - $defs?: { [name: string]: IJSONSchema }; - $anchor?: string; - $recursiveRef?: string; - $recursiveAnchor?: string; - $vocabulary?: any; - - // schema 2020-12 - prefixItems?: IJSONSchema[]; - $dynamicRef?: string; - $dynamicAnchor?: string; - - // VSCode extensions - - defaultSnippets?: IJSONSchemaSnippet[]; - errorMessage?: string; - patternErrorMessage?: string; - deprecationMessage?: string; - markdownDeprecationMessage?: string; - enumDescriptions?: string[]; - markdownEnumDescriptions?: string[]; - markdownDescription?: string; - doNotSuggest?: boolean; - suggestSortText?: string; - allowComments?: boolean; - allowTrailingCommas?: boolean; -} - -export interface IJSONSchemaMap { - [name: string]: IJSONSchema; -} - -export interface IJSONSchemaSnippet { - label?: string; - description?: string; - body?: any; // a object that will be JSON stringified - bodyText?: string; // an already stringified JSON object that can contain new lines (\n) and tabs (\t) -} - -/** - * Converts a basic JSON schema to a TypeScript type. - * - * TODO: only supports basic schemas. Doesn't support all JSON schema features. - */ -export type SchemaToType = T extends { type: 'string' } - ? string - : T extends { type: 'number' } - ? number - : T extends { type: 'boolean' } - ? boolean - : T extends { type: 'null' } - ? null - : T extends { type: 'object'; properties: infer P } - ? { [K in keyof P]: SchemaToType } - : T extends { type: 'array'; items: infer I } - ? Array> - : never; - -interface Equals { schemas: IJSONSchema[]; id?: string } - -export function getCompressedContent(schema: IJSONSchema): string { - let hasDups = false; - - - // visit all schema nodes and collect the ones that are equal - const equalsByString = new Map(); - const nodeToEquals = new Map(); - const visitSchemas = (next: IJSONSchema) => { - if (schema === next) { - return true; - } - const val = JSON.stringify(next); - if (val.length < 30) { - // the $ref takes around 25 chars, so we don't save anything - return true; - } - const eq = equalsByString.get(val); - if (!eq) { - const newEq = { schemas: [next] }; - equalsByString.set(val, newEq); - nodeToEquals.set(next, newEq); - return true; - } - eq.schemas.push(next); - nodeToEquals.set(next, eq); - hasDups = true; - return false; - }; - traverseNodes(schema, visitSchemas); - equalsByString.clear(); - - if (!hasDups) { - return JSON.stringify(schema); - } - - let defNodeName = '$defs'; - while (schema.hasOwnProperty(defNodeName)) { - defNodeName += '_'; - } - - // used to collect all schemas that are later put in `$defs`. The index in the array is the id of the schema. - const definitions: IJSONSchema[] = []; - - function stringify(root: IJSONSchema): string { - return JSON.stringify(root, (_key: string, value: any) => { - if (value !== root) { - const eq = nodeToEquals.get(value); - if (eq && eq.schemas.length > 1) { - if (!eq.id) { - eq.id = `_${definitions.length}`; - definitions.push(eq.schemas[0]); - } - return { $ref: `#/${defNodeName}/${eq.id}` }; - } - } - return value; - }); - } - - // stringify the schema and replace duplicate subtrees with $ref - // this will add new items to the definitions array - const str = stringify(schema); - - // now stringify the definitions. Each invication of stringify cann add new items to the definitions array, so the length can grow while we iterate - const defStrings: string[] = []; - for (let i = 0; i < definitions.length; i++) { - defStrings.push(`"_${i}":${stringify(definitions[i])}`); - } - if (defStrings.length) { - return `${str.substring(0, str.length - 1)},"${defNodeName}":{${defStrings.join(',')}}}`; - } - return str; -} - -type IJSONSchemaRef = IJSONSchema | boolean; - -function isObject(thing: any): thing is object { - return typeof thing === 'object' && thing !== null; -} - -/* - * Traverse a JSON schema and visit each schema node -*/ -function traverseNodes(root: IJSONSchema, visit: (schema: IJSONSchema) => boolean) { - if (!root || typeof root !== 'object') { - return; - } - const collectEntries = (...entries: (IJSONSchemaRef | undefined)[]) => { - for (const entry of entries) { - if (isObject(entry)) { - toWalk.push(entry); - } - } - }; - const collectMapEntries = (...maps: (IJSONSchemaMap | undefined)[]) => { - for (const map of maps) { - if (isObject(map)) { - for (const key in map) { - const entry = map[key]; - if (isObject(entry)) { - toWalk.push(entry); - } - } - } - } - }; - const collectArrayEntries = (...arrays: (IJSONSchemaRef[] | undefined)[]) => { - for (const array of arrays) { - if (Array.isArray(array)) { - for (const entry of array) { - if (isObject(entry)) { - toWalk.push(entry); - } - } - } - } - }; - const collectEntryOrArrayEntries = (items: (IJSONSchemaRef[] | IJSONSchemaRef | undefined)) => { - if (Array.isArray(items)) { - for (const entry of items) { - if (isObject(entry)) { - toWalk.push(entry); - } - } - } else if (isObject(items)) { - toWalk.push(items); - } - }; - - const toWalk: IJSONSchema[] = [root]; - - let next = toWalk.pop(); - while (next) { - const visitChildern = visit(next); - if (visitChildern) { - collectEntries(next.additionalItems, next.additionalProperties, next.not, next.contains, next.propertyNames, next.if, next.then, next.else, next.unevaluatedItems, next.unevaluatedProperties); - collectMapEntries(next.definitions, next.$defs, next.properties, next.patternProperties, next.dependencies, next.dependentSchemas); - collectArrayEntries(next.anyOf, next.allOf, next.oneOf, next.prefixItems); - collectEntryOrArrayEntries(next.items); - } - next = toWalk.pop(); - } -} - diff --git a/src/vs/base/common/jsonc.d.ts b/src/vs/base/common/jsonc.d.ts deleted file mode 100644 index 504e6c60..00000000 --- a/src/vs/base/common/jsonc.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * A drop-in replacement for JSON.parse that can parse - * JSON with comments and trailing commas. - * - * @param content the content to strip comments from - * @returns the parsed content as JSON -*/ -export function parse(content: string): any; - -/** - * Strips single and multi line JavaScript comments from JSON - * content. Ignores characters in strings BUT doesn't support - * string continuation across multiple lines since it is not - * supported in JSON. - * - * @param content the content to strip comments from - * @returns the content without comments -*/ -export function stripComments(content: string): string; diff --git a/src/vs/base/common/jsonc.js b/src/vs/base/common/jsonc.js deleted file mode 100644 index 21e3b7ea..00000000 --- a/src/vs/base/common/jsonc.js +++ /dev/null @@ -1,89 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/// - -//@ts-check -'use strict'; - -// ESM-uncomment-begin -// const module = { exports: {} }; -// ESM-uncomment-end - -(function () { - - function factory() { - // First group matches a double quoted string - // Second group matches a single quoted string - // Third group matches a multi line comment - // Forth group matches a single line comment - // Fifth group matches a trailing comma - const regexp = /("[^"\\]*(?:\\.[^"\\]*)*")|('[^'\\]*(?:\\.[^'\\]*)*')|(\/\*[^\/\*]*(?:(?:\*|\/)[^\/\*]*)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))|(,\s*[}\]])/g; - - /** - * @param {string} content - * @returns {string} - */ - function stripComments(content) { - return content.replace(regexp, function (match, _m1, _m2, m3, m4, m5) { - // Only one of m1, m2, m3, m4, m5 matches - if (m3) { - // A block comment. Replace with nothing - return ''; - } else if (m4) { - // Since m4 is a single line comment is is at least of length 2 (e.g. //) - // If it ends in \r?\n then keep it. - const length = m4.length; - if (m4[length - 1] === '\n') { - return m4[length - 2] === '\r' ? '\r\n' : '\n'; - } - else { - return ''; - } - } else if (m5) { - // Remove the trailing comma - return match.substring(1); - } else { - // We match a string - return match; - } - }); - } - - /** - * @param {string} content - * @returns {any} - */ - function parse(content) { - const commentsStripped = stripComments(content); - - try { - return JSON.parse(commentsStripped); - } catch (error) { - const trailingCommasStriped = commentsStripped.replace(/,\s*([}\]])/g, '$1'); - return JSON.parse(trailingCommasStriped); - } - } - return { - stripComments, - parse - }; - } - - if (typeof define === 'function') { - // amd - define([], function () { return factory(); }); - } else if (typeof module === 'object' && typeof module.exports === 'object') { - // commonjs - module.exports = factory(); - } else { - console.trace('jsonc defined in UNKNOWN context (neither requirejs or commonjs)'); - } -})(); - -// ESM-uncomment-begin -// export const stripComments = module.exports.stripComments; -// export const parse = module.exports.parse; -// ESM-uncomment-end diff --git a/src/vs/base/common/keybindingLabels.ts b/src/vs/base/common/keybindingLabels.ts deleted file mode 100644 index 9eb93273..00000000 --- a/src/vs/base/common/keybindingLabels.ts +++ /dev/null @@ -1,184 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Modifiers } from 'vs/base/common/keybindings'; -import { OperatingSystem } from 'vs/base/common/platform'; -import * as nls from 'vs/nls'; - -export interface ModifierLabels { - readonly ctrlKey: string; - readonly shiftKey: string; - readonly altKey: string; - readonly metaKey: string; - readonly separator: string; -} - -export interface KeyLabelProvider { - (keybinding: T): string | null; -} - -export class ModifierLabelProvider { - - public readonly modifierLabels: ModifierLabels[]; - - constructor(mac: ModifierLabels, windows: ModifierLabels, linux: ModifierLabels = windows) { - this.modifierLabels = [null!]; // index 0 will never me accessed. - this.modifierLabels[OperatingSystem.Macintosh] = mac; - this.modifierLabels[OperatingSystem.Windows] = windows; - this.modifierLabels[OperatingSystem.Linux] = linux; - } - - public toLabel(OS: OperatingSystem, chords: readonly T[], keyLabelProvider: KeyLabelProvider): string | null { - if (chords.length === 0) { - return null; - } - - const result: string[] = []; - for (let i = 0, len = chords.length; i < len; i++) { - const chord = chords[i]; - const keyLabel = keyLabelProvider(chord); - if (keyLabel === null) { - // this keybinding cannot be expressed... - return null; - } - result[i] = _simpleAsString(chord, keyLabel, this.modifierLabels[OS]); - } - return result.join(' '); - } -} - -/** - * A label provider that prints modifiers in a suitable format for displaying in the UI. - */ -export const UILabelProvider = new ModifierLabelProvider( - { - ctrlKey: '\u2303', - shiftKey: '⇧', - altKey: '⌥', - metaKey: '⌘', - separator: '', - }, - { - ctrlKey: nls.localize({ key: 'ctrlKey', comment: ['This is the short form for the Control key on the keyboard'] }, "Ctrl"), - shiftKey: nls.localize({ key: 'shiftKey', comment: ['This is the short form for the Shift key on the keyboard'] }, "Shift"), - altKey: nls.localize({ key: 'altKey', comment: ['This is the short form for the Alt key on the keyboard'] }, "Alt"), - metaKey: nls.localize({ key: 'windowsKey', comment: ['This is the short form for the Windows key on the keyboard'] }, "Windows"), - separator: '+', - }, - { - ctrlKey: nls.localize({ key: 'ctrlKey', comment: ['This is the short form for the Control key on the keyboard'] }, "Ctrl"), - shiftKey: nls.localize({ key: 'shiftKey', comment: ['This is the short form for the Shift key on the keyboard'] }, "Shift"), - altKey: nls.localize({ key: 'altKey', comment: ['This is the short form for the Alt key on the keyboard'] }, "Alt"), - metaKey: nls.localize({ key: 'superKey', comment: ['This is the short form for the Super key on the keyboard'] }, "Super"), - separator: '+', - } -); - -/** - * A label provider that prints modifiers in a suitable format for ARIA. - */ -export const AriaLabelProvider = new ModifierLabelProvider( - { - ctrlKey: nls.localize({ key: 'ctrlKey.long', comment: ['This is the long form for the Control key on the keyboard'] }, "Control"), - shiftKey: nls.localize({ key: 'shiftKey.long', comment: ['This is the long form for the Shift key on the keyboard'] }, "Shift"), - altKey: nls.localize({ key: 'optKey.long', comment: ['This is the long form for the Alt/Option key on the keyboard'] }, "Option"), - metaKey: nls.localize({ key: 'cmdKey.long', comment: ['This is the long form for the Command key on the keyboard'] }, "Command"), - separator: '+', - }, - { - ctrlKey: nls.localize({ key: 'ctrlKey.long', comment: ['This is the long form for the Control key on the keyboard'] }, "Control"), - shiftKey: nls.localize({ key: 'shiftKey.long', comment: ['This is the long form for the Shift key on the keyboard'] }, "Shift"), - altKey: nls.localize({ key: 'altKey.long', comment: ['This is the long form for the Alt key on the keyboard'] }, "Alt"), - metaKey: nls.localize({ key: 'windowsKey.long', comment: ['This is the long form for the Windows key on the keyboard'] }, "Windows"), - separator: '+', - }, - { - ctrlKey: nls.localize({ key: 'ctrlKey.long', comment: ['This is the long form for the Control key on the keyboard'] }, "Control"), - shiftKey: nls.localize({ key: 'shiftKey.long', comment: ['This is the long form for the Shift key on the keyboard'] }, "Shift"), - altKey: nls.localize({ key: 'altKey.long', comment: ['This is the long form for the Alt key on the keyboard'] }, "Alt"), - metaKey: nls.localize({ key: 'superKey.long', comment: ['This is the long form for the Super key on the keyboard'] }, "Super"), - separator: '+', - } -); - -/** - * A label provider that prints modifiers in a suitable format for Electron Accelerators. - * See https://github.com/electron/electron/blob/master/docs/api/accelerator.md - */ -export const ElectronAcceleratorLabelProvider = new ModifierLabelProvider( - { - ctrlKey: 'Ctrl', - shiftKey: 'Shift', - altKey: 'Alt', - metaKey: 'Cmd', - separator: '+', - }, - { - ctrlKey: 'Ctrl', - shiftKey: 'Shift', - altKey: 'Alt', - metaKey: 'Super', - separator: '+', - } -); - -/** - * A label provider that prints modifiers in a suitable format for user settings. - */ -export const UserSettingsLabelProvider = new ModifierLabelProvider( - { - ctrlKey: 'ctrl', - shiftKey: 'shift', - altKey: 'alt', - metaKey: 'cmd', - separator: '+', - }, - { - ctrlKey: 'ctrl', - shiftKey: 'shift', - altKey: 'alt', - metaKey: 'win', - separator: '+', - }, - { - ctrlKey: 'ctrl', - shiftKey: 'shift', - altKey: 'alt', - metaKey: 'meta', - separator: '+', - } -); - -function _simpleAsString(modifiers: Modifiers, key: string, labels: ModifierLabels): string { - if (key === null) { - return ''; - } - - const result: string[] = []; - - // translate modifier keys: Ctrl-Shift-Alt-Meta - if (modifiers.ctrlKey) { - result.push(labels.ctrlKey); - } - - if (modifiers.shiftKey) { - result.push(labels.shiftKey); - } - - if (modifiers.altKey) { - result.push(labels.altKey); - } - - if (modifiers.metaKey) { - result.push(labels.metaKey); - } - - // the actual key - if (key !== '') { - result.push(key); - } - - return result.join(labels.separator); -} diff --git a/src/vs/base/common/keybindingParser.ts b/src/vs/base/common/keybindingParser.ts deleted file mode 100644 index 3c36b810..00000000 --- a/src/vs/base/common/keybindingParser.ts +++ /dev/null @@ -1,102 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { KeyCodeUtils, ScanCodeUtils } from 'vs/base/common/keyCodes'; -import { KeyCodeChord, ScanCodeChord, Keybinding, Chord } from 'vs/base/common/keybindings'; - -export class KeybindingParser { - - private static _readModifiers(input: string) { - input = input.toLowerCase().trim(); - - let ctrl = false; - let shift = false; - let alt = false; - let meta = false; - - let matchedModifier: boolean; - - do { - matchedModifier = false; - if (/^ctrl(\+|\-)/.test(input)) { - ctrl = true; - input = input.substr('ctrl-'.length); - matchedModifier = true; - } - if (/^shift(\+|\-)/.test(input)) { - shift = true; - input = input.substr('shift-'.length); - matchedModifier = true; - } - if (/^alt(\+|\-)/.test(input)) { - alt = true; - input = input.substr('alt-'.length); - matchedModifier = true; - } - if (/^meta(\+|\-)/.test(input)) { - meta = true; - input = input.substr('meta-'.length); - matchedModifier = true; - } - if (/^win(\+|\-)/.test(input)) { - meta = true; - input = input.substr('win-'.length); - matchedModifier = true; - } - if (/^cmd(\+|\-)/.test(input)) { - meta = true; - input = input.substr('cmd-'.length); - matchedModifier = true; - } - } while (matchedModifier); - - let key: string; - - const firstSpaceIdx = input.indexOf(' '); - if (firstSpaceIdx > 0) { - key = input.substring(0, firstSpaceIdx); - input = input.substring(firstSpaceIdx); - } else { - key = input; - input = ''; - } - - return { - remains: input, - ctrl, - shift, - alt, - meta, - key - }; - } - - private static parseChord(input: string): [Chord, string] { - const mods = this._readModifiers(input); - const scanCodeMatch = mods.key.match(/^\[([^\]]+)\]$/); - if (scanCodeMatch) { - const strScanCode = scanCodeMatch[1]; - const scanCode = ScanCodeUtils.lowerCaseToEnum(strScanCode); - return [new ScanCodeChord(mods.ctrl, mods.shift, mods.alt, mods.meta, scanCode), mods.remains]; - } - const keyCode = KeyCodeUtils.fromUserSettings(mods.key); - return [new KeyCodeChord(mods.ctrl, mods.shift, mods.alt, mods.meta, keyCode), mods.remains]; - } - - static parseKeybinding(input: string): Keybinding | null { - if (!input) { - return null; - } - - const chords: Chord[] = []; - let chord: Chord; - - while (input.length > 0) { - [chord, input] = this.parseChord(input); - chords.push(chord); - } - return (chords.length > 0 ? new Keybinding(chords) : null); - } -} diff --git a/src/vs/base/common/linkedText.ts b/src/vs/base/common/linkedText.ts deleted file mode 100644 index 89f6da52..00000000 --- a/src/vs/base/common/linkedText.ts +++ /dev/null @@ -1,55 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { memoize } from 'vs/base/common/decorators'; - -export interface ILink { - readonly label: string; - readonly href: string; - readonly title?: string; -} - -export type LinkedTextNode = string | ILink; - -export class LinkedText { - - constructor(readonly nodes: LinkedTextNode[]) { } - - @memoize - toString(): string { - return this.nodes.map(node => typeof node === 'string' ? node : node.label).join(''); - } -} - -const LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:|file:)[^\)\s]+)(?: (["'])(.+?)(\3))?\)/gi; - -export function parseLinkedText(text: string): LinkedText { - const result: LinkedTextNode[] = []; - - let index = 0; - let match: RegExpExecArray | null; - - while (match = LINK_REGEX.exec(text)) { - if (match.index - index > 0) { - result.push(text.substring(index, match.index)); - } - - const [, label, href, , title] = match; - - if (title) { - result.push({ label, href, title }); - } else { - result.push({ label, href }); - } - - index = match.index + match[0].length; - } - - if (index < text.length) { - result.push(text.substring(index)); - } - - return new LinkedText(result); -} diff --git a/src/vs/base/common/mime.ts b/src/vs/base/common/mime.ts deleted file mode 100644 index 288a8daf..00000000 --- a/src/vs/base/common/mime.ts +++ /dev/null @@ -1,126 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { extname } from 'vs/base/common/path'; - -export const Mimes = Object.freeze({ - text: 'text/plain', - binary: 'application/octet-stream', - unknown: 'application/unknown', - markdown: 'text/markdown', - latex: 'text/latex', - uriList: 'text/uri-list', -}); - -interface MapExtToMediaMimes { - [index: string]: string; -} - -const mapExtToTextMimes: MapExtToMediaMimes = { - '.css': 'text/css', - '.csv': 'text/csv', - '.htm': 'text/html', - '.html': 'text/html', - '.ics': 'text/calendar', - '.js': 'text/javascript', - '.mjs': 'text/javascript', - '.txt': 'text/plain', - '.xml': 'text/xml' -}; - -// Known media mimes that we can handle -const mapExtToMediaMimes: MapExtToMediaMimes = { - '.aac': 'audio/x-aac', - '.avi': 'video/x-msvideo', - '.bmp': 'image/bmp', - '.flv': 'video/x-flv', - '.gif': 'image/gif', - '.ico': 'image/x-icon', - '.jpe': 'image/jpg', - '.jpeg': 'image/jpg', - '.jpg': 'image/jpg', - '.m1v': 'video/mpeg', - '.m2a': 'audio/mpeg', - '.m2v': 'video/mpeg', - '.m3a': 'audio/mpeg', - '.mid': 'audio/midi', - '.midi': 'audio/midi', - '.mk3d': 'video/x-matroska', - '.mks': 'video/x-matroska', - '.mkv': 'video/x-matroska', - '.mov': 'video/quicktime', - '.movie': 'video/x-sgi-movie', - '.mp2': 'audio/mpeg', - '.mp2a': 'audio/mpeg', - '.mp3': 'audio/mpeg', - '.mp4': 'video/mp4', - '.mp4a': 'audio/mp4', - '.mp4v': 'video/mp4', - '.mpe': 'video/mpeg', - '.mpeg': 'video/mpeg', - '.mpg': 'video/mpeg', - '.mpg4': 'video/mp4', - '.mpga': 'audio/mpeg', - '.oga': 'audio/ogg', - '.ogg': 'audio/ogg', - '.opus': 'audio/opus', - '.ogv': 'video/ogg', - '.png': 'image/png', - '.psd': 'image/vnd.adobe.photoshop', - '.qt': 'video/quicktime', - '.spx': 'audio/ogg', - '.svg': 'image/svg+xml', - '.tga': 'image/x-tga', - '.tif': 'image/tiff', - '.tiff': 'image/tiff', - '.wav': 'audio/x-wav', - '.webm': 'video/webm', - '.webp': 'image/webp', - '.wma': 'audio/x-ms-wma', - '.wmv': 'video/x-ms-wmv', - '.woff': 'application/font-woff', -}; - -export function getMediaOrTextMime(path: string): string | undefined { - const ext = extname(path); - const textMime = mapExtToTextMimes[ext.toLowerCase()]; - if (textMime !== undefined) { - return textMime; - } else { - return getMediaMime(path); - } -} - -export function getMediaMime(path: string): string | undefined { - const ext = extname(path); - return mapExtToMediaMimes[ext.toLowerCase()]; -} - -export function getExtensionForMimeType(mimeType: string): string | undefined { - for (const extension in mapExtToMediaMimes) { - if (mapExtToMediaMimes[extension] === mimeType) { - return extension; - } - } - - return undefined; -} - -const _simplePattern = /^(.+)\/(.+?)(;.+)?$/; - -export function normalizeMimeType(mimeType: string): string; -export function normalizeMimeType(mimeType: string, strict: true): string | undefined; -export function normalizeMimeType(mimeType: string, strict?: true): string | undefined { - - const match = _simplePattern.exec(mimeType); - if (!match) { - return strict - ? undefined - : mimeType; - } - // https://datatracker.ietf.org/doc/html/rfc2045#section-5.1 - // media and subtype must ALWAYS be lowercase, parameter not - return `${match[1].toLowerCase()}/${match[2].toLowerCase()}${match[3] ?? ''}`; -} diff --git a/src/vs/base/common/navigator.ts b/src/vs/base/common/navigator.ts deleted file mode 100644 index ba7feffe..00000000 --- a/src/vs/base/common/navigator.ts +++ /dev/null @@ -1,50 +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 INavigator { - current(): T | null; - previous(): T | null; - first(): T | null; - last(): T | null; - next(): T | null; -} - -export class ArrayNavigator implements INavigator { - - constructor( - private readonly items: readonly T[], - protected start: number = 0, - protected end: number = items.length, - protected index = start - 1 - ) { } - - current(): T | null { - if (this.index === this.start - 1 || this.index === this.end) { - return null; - } - - return this.items[this.index]; - } - - next(): T | null { - this.index = Math.min(this.index + 1, this.end); - return this.current(); - } - - previous(): T | null { - this.index = Math.max(this.index - 1, this.start - 1); - return this.current(); - } - - first(): T | null { - this.index = this.start; - return this.current(); - } - - last(): T | null { - this.index = this.end - 1; - return this.current(); - } -} diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts deleted file mode 100644 index 5662c57a..00000000 --- a/src/vs/base/common/network.ts +++ /dev/null @@ -1,128 +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 Schemas { - - /** - * A schema that is used for models that exist in memory - * only and that have no correspondence on a server or such. - */ - export const inMemory = 'inmemory'; - - /** - * A schema that is used for setting files - */ - export const vscode = 'vscode'; - - /** - * A schema that is used for internal private files - */ - export const internal = 'private'; - - /** - * A walk-through document. - */ - export const walkThrough = 'walkThrough'; - - /** - * An embedded code snippet. - */ - export const walkThroughSnippet = 'walkThroughSnippet'; - - export const http = 'http'; - - export const https = 'https'; - - export const file = 'file'; - - export const mailto = 'mailto'; - - export const untitled = 'untitled'; - - export const data = 'data'; - - export const command = 'command'; - - export const vscodeRemote = 'vscode-remote'; - - export const vscodeRemoteResource = 'vscode-remote-resource'; - - export const vscodeManagedRemoteResource = 'vscode-managed-remote-resource'; - - export const vscodeUserData = 'vscode-userdata'; - - export const vscodeCustomEditor = 'vscode-custom-editor'; - - export const vscodeNotebookCell = 'vscode-notebook-cell'; - export const vscodeNotebookCellMetadata = 'vscode-notebook-cell-metadata'; - export const vscodeNotebookCellOutput = 'vscode-notebook-cell-output'; - export const vscodeInteractiveInput = 'vscode-interactive-input'; - - export const vscodeSettings = 'vscode-settings'; - - export const vscodeWorkspaceTrust = 'vscode-workspace-trust'; - - export const vscodeTerminal = 'vscode-terminal'; - - /** Scheme used for code blocks in chat. */ - export const vscodeChatCodeBlock = 'vscode-chat-code-block'; - - /** - * Scheme used for backing documents created by copilot for chat. - */ - export const vscodeCopilotBackingChatCodeBlock = 'vscode-copilot-chat-code-block'; - - /** Scheme used for LHS of code compare (aka diff) blocks in chat. */ - export const vscodeChatCodeCompareBlock = 'vscode-chat-code-compare-block'; - - /** Scheme used for the chat input editor. */ - export const vscodeChatSesssion = 'vscode-chat-editor'; - - /** - * Scheme used internally for webviews that aren't linked to a resource (i.e. not custom editors) - */ - export const webviewPanel = 'webview-panel'; - - /** - * Scheme used for loading the wrapper html and script in webviews. - */ - export const vscodeWebview = 'vscode-webview'; - - /** - * Scheme used for extension pages - */ - export const extension = 'extension'; - - /** - * Scheme used as a replacement of `file` scheme to load - * files with our custom protocol handler (desktop only). - */ - export const vscodeFileResource = 'vscode-file'; - - /** - * Scheme used for temporary resources - */ - export const tmp = 'tmp'; - - /** - * Scheme used vs live share - */ - export const vsls = 'vsls'; - - /** - * Scheme used for the Source Control commit input's text document - */ - export const vscodeSourceControl = 'vscode-scm'; - - /** - * Scheme used for input box for creating comments. - */ - export const commentsInput = 'comment'; - - /** - * Scheme used for special rendering of settings in the release notes - */ - export const codeSetting = 'code-setting'; -} diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts deleted file mode 100644 index 14ec0e71..00000000 --- a/src/vs/base/common/objects.ts +++ /dev/null @@ -1,274 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { isTypedArray, isObject, isUndefinedOrNull } from 'vs/base/common/types'; - -export function deepClone(obj: T): T { - if (!obj || typeof obj !== 'object') { - return obj; - } - if (obj instanceof RegExp) { - return obj; - } - const result: any = Array.isArray(obj) ? [] : {}; - Object.entries(obj).forEach(([key, value]) => { - result[key] = value && typeof value === 'object' ? deepClone(value) : value; - }); - return result; -} - -export function deepFreeze(obj: T): T { - if (!obj || typeof obj !== 'object') { - return obj; - } - const stack: any[] = [obj]; - while (stack.length > 0) { - const obj = stack.shift(); - Object.freeze(obj); - for (const key in obj) { - if (_hasOwnProperty.call(obj, key)) { - const prop = obj[key]; - if (typeof prop === 'object' && !Object.isFrozen(prop) && !isTypedArray(prop)) { - stack.push(prop); - } - } - } - } - return obj; -} - -const _hasOwnProperty = Object.prototype.hasOwnProperty; - - -export function cloneAndChange(obj: any, changer: (orig: any) => any): any { - return _cloneAndChange(obj, changer, new Set()); -} - -function _cloneAndChange(obj: any, changer: (orig: any) => any, seen: Set): any { - if (isUndefinedOrNull(obj)) { - return obj; - } - - const changed = changer(obj); - if (typeof changed !== 'undefined') { - return changed; - } - - if (Array.isArray(obj)) { - const r1: any[] = []; - for (const e of obj) { - r1.push(_cloneAndChange(e, changer, seen)); - } - return r1; - } - - if (isObject(obj)) { - if (seen.has(obj)) { - throw new Error('Cannot clone recursive data-structure'); - } - seen.add(obj); - const r2 = {}; - for (const i2 in obj) { - if (_hasOwnProperty.call(obj, i2)) { - (r2 as any)[i2] = _cloneAndChange(obj[i2], changer, seen); - } - } - seen.delete(obj); - return r2; - } - - return obj; -} - -/** - * Copies all properties of source into destination. The optional parameter "overwrite" allows to control - * if existing properties on the destination should be overwritten or not. Defaults to true (overwrite). - */ -export function mixin(destination: any, source: any, overwrite: boolean = true): any { - if (!isObject(destination)) { - return source; - } - - if (isObject(source)) { - Object.keys(source).forEach(key => { - if (key in destination) { - if (overwrite) { - if (isObject(destination[key]) && isObject(source[key])) { - mixin(destination[key], source[key], overwrite); - } else { - destination[key] = source[key]; - } - } - } else { - destination[key] = source[key]; - } - }); - } - return destination; -} - -export function equals(one: any, other: any): boolean { - if (one === other) { - return true; - } - if (one === null || one === undefined || other === null || other === undefined) { - return false; - } - if (typeof one !== typeof other) { - return false; - } - if (typeof one !== 'object') { - return false; - } - if ((Array.isArray(one)) !== (Array.isArray(other))) { - return false; - } - - let i: number; - let key: string; - - if (Array.isArray(one)) { - if (one.length !== other.length) { - return false; - } - for (i = 0; i < one.length; i++) { - if (!equals(one[i], other[i])) { - return false; - } - } - } else { - const oneKeys: string[] = []; - - for (key in one) { - oneKeys.push(key); - } - oneKeys.sort(); - const otherKeys: string[] = []; - for (key in other) { - otherKeys.push(key); - } - otherKeys.sort(); - if (!equals(oneKeys, otherKeys)) { - return false; - } - for (i = 0; i < oneKeys.length; i++) { - if (!equals(one[oneKeys[i]], other[oneKeys[i]])) { - return false; - } - } - } - return true; -} - -/** - * Calls `JSON.Stringify` with a replacer to break apart any circular references. - * This prevents `JSON`.stringify` from throwing the exception - * "Uncaught TypeError: Converting circular structure to JSON" - */ -export function safeStringify(obj: any): string { - const seen = new Set(); - return JSON.stringify(obj, (key, value) => { - if (isObject(value) || Array.isArray(value)) { - if (seen.has(value)) { - return '[Circular]'; - } else { - seen.add(value); - } - } - if (typeof value === 'bigint') { - return `[BigInt ${value.toString()}]`; - } - return value; - }); -} - -type obj = { [key: string]: any }; -/** - * Returns an object that has keys for each value that is different in the base object. Keys - * that do not exist in the target but in the base object are not considered. - * - * Note: This is not a deep-diffing method, so the values are strictly taken into the resulting - * object if they differ. - * - * @param base the object to diff against - * @param obj the object to use for diffing - */ -export function distinct(base: obj, target: obj): obj { - const result = Object.create(null); - - if (!base || !target) { - return result; - } - - const targetKeys = Object.keys(target); - targetKeys.forEach(k => { - const baseValue = base[k]; - const targetValue = target[k]; - - if (!equals(baseValue, targetValue)) { - result[k] = targetValue; - } - }); - - return result; -} - -export function getCaseInsensitive(target: obj, key: string): any { - const lowercaseKey = key.toLowerCase(); - const equivalentKey = Object.keys(target).find(k => k.toLowerCase() === lowercaseKey); - return equivalentKey ? target[equivalentKey] : target[key]; -} - -export function filter(obj: obj, predicate: (key: string, value: any) => boolean): obj { - const result = Object.create(null); - for (const [key, value] of Object.entries(obj)) { - if (predicate(key, value)) { - result[key] = value; - } - } - return result; -} - -export function getAllPropertyNames(obj: object): string[] { - let res: string[] = []; - while (Object.prototype !== obj) { - res = res.concat(Object.getOwnPropertyNames(obj)); - obj = Object.getPrototypeOf(obj); - } - return res; -} - -export function getAllMethodNames(obj: object): string[] { - const methods: string[] = []; - for (const prop of getAllPropertyNames(obj)) { - if (typeof (obj as any)[prop] === 'function') { - methods.push(prop); - } - } - return methods; -} - -export function createProxyObject(methodNames: string[], invoke: (method: string, args: unknown[]) => unknown): T { - const createProxyMethod = (method: string): () => unknown => { - return function () { - const args = Array.prototype.slice.call(arguments, 0); - return invoke(method, args); - }; - }; - - const result = {} as T; - for (const methodName of methodNames) { - (result)[methodName] = createProxyMethod(methodName); - } - return result; -} - -export function mapValues(obj: T, fn: (value: T[keyof T], key: string) => R): { [K in keyof T]: R } { - const result: { [key: string]: R } = {}; - for (const [key, value] of Object.entries(obj)) { - result[key] = fn(value, key); - } - return result as { [K in keyof T]: R }; -} diff --git a/src/vs/base/common/paging.ts b/src/vs/base/common/paging.ts deleted file mode 100644 index c13957cc..00000000 --- a/src/vs/base/common/paging.ts +++ /dev/null @@ -1,189 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { range } from 'vs/base/common/arrays'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { CancellationError } from 'vs/base/common/errors'; - -/** - * A Pager is a stateless abstraction over a paged collection. - */ -export interface IPager { - firstPage: T[]; - total: number; - pageSize: number; - getPage(pageIndex: number, cancellationToken: CancellationToken): Promise; -} - -interface IPage { - isResolved: boolean; - promise: Promise | null; - cts: CancellationTokenSource | null; - promiseIndexes: Set; - elements: T[]; -} - -function createPage(elements?: T[]): IPage { - return { - isResolved: !!elements, - promise: null, - cts: null, - promiseIndexes: new Set(), - elements: elements || [] - }; -} - -/** - * A PagedModel is a stateful model over an abstracted paged collection. - */ -export interface IPagedModel { - length: number; - isResolved(index: number): boolean; - get(index: number): T; - resolve(index: number, cancellationToken: CancellationToken): Promise; -} - -export function singlePagePager(elements: T[]): IPager { - return { - firstPage: elements, - total: elements.length, - pageSize: elements.length, - getPage: (pageIndex: number, cancellationToken: CancellationToken): Promise => { - return Promise.resolve(elements); - } - }; -} - -export class PagedModel implements IPagedModel { - - private pager: IPager; - private pages: IPage[] = []; - - get length(): number { return this.pager.total; } - - constructor(arg: IPager | T[]) { - this.pager = Array.isArray(arg) ? singlePagePager(arg) : arg; - - const totalPages = Math.ceil(this.pager.total / this.pager.pageSize); - - this.pages = [ - createPage(this.pager.firstPage.slice()), - ...range(totalPages - 1).map(() => createPage()) - ]; - } - - isResolved(index: number): boolean { - const pageIndex = Math.floor(index / this.pager.pageSize); - const page = this.pages[pageIndex]; - - return !!page.isResolved; - } - - get(index: number): T { - const pageIndex = Math.floor(index / this.pager.pageSize); - const indexInPage = index % this.pager.pageSize; - const page = this.pages[pageIndex]; - - return page.elements[indexInPage]; - } - - resolve(index: number, cancellationToken: CancellationToken): Promise { - if (cancellationToken.isCancellationRequested) { - return Promise.reject(new CancellationError()); - } - - const pageIndex = Math.floor(index / this.pager.pageSize); - const indexInPage = index % this.pager.pageSize; - const page = this.pages[pageIndex]; - - if (page.isResolved) { - return Promise.resolve(page.elements[indexInPage]); - } - - if (!page.promise) { - page.cts = new CancellationTokenSource(); - page.promise = this.pager.getPage(pageIndex, page.cts.token) - .then(elements => { - page.elements = elements; - page.isResolved = true; - page.promise = null; - page.cts = null; - }, err => { - page.isResolved = false; - page.promise = null; - page.cts = null; - return Promise.reject(err); - }); - } - - const listener = cancellationToken.onCancellationRequested(() => { - if (!page.cts) { - return; - } - - page.promiseIndexes.delete(index); - - if (page.promiseIndexes.size === 0) { - page.cts.cancel(); - } - }); - - page.promiseIndexes.add(index); - - return page.promise.then(() => page.elements[indexInPage]) - .finally(() => listener.dispose()); - } -} - -export class DelayedPagedModel implements IPagedModel { - - get length(): number { return this.model.length; } - - constructor(private model: IPagedModel, private timeout: number = 500) { } - - isResolved(index: number): boolean { - return this.model.isResolved(index); - } - - get(index: number): T { - return this.model.get(index); - } - - resolve(index: number, cancellationToken: CancellationToken): Promise { - return new Promise((c, e) => { - if (cancellationToken.isCancellationRequested) { - return e(new CancellationError()); - } - - const timer = setTimeout(() => { - if (cancellationToken.isCancellationRequested) { - return e(new CancellationError()); - } - - timeoutCancellation.dispose(); - this.model.resolve(index, cancellationToken).then(c, e); - }, this.timeout); - - const timeoutCancellation = cancellationToken.onCancellationRequested(() => { - clearTimeout(timer); - timeoutCancellation.dispose(); - e(new CancellationError()); - }); - }); - } -} - -/** - * Similar to array.map, `mapPager` lets you map the elements of an - * abstract paged collection to another type. - */ -export function mapPager(pager: IPager, fn: (t: T) => R): IPager { - return { - firstPage: pager.firstPage.map(fn), - total: pager.total, - pageSize: pager.pageSize, - getPage: (pageIndex, token) => pager.getPage(pageIndex, token).then(r => r.map(fn)) - }; -} diff --git a/src/vs/base/common/parsers.ts b/src/vs/base/common/parsers.ts deleted file mode 100644 index 07010309..00000000 --- a/src/vs/base/common/parsers.ts +++ /dev/null @@ -1,79 +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 const enum ValidationState { - OK = 0, - Info = 1, - Warning = 2, - Error = 3, - Fatal = 4 -} - -export class ValidationStatus { - private _state: ValidationState; - - constructor() { - this._state = ValidationState.OK; - } - - public get state(): ValidationState { - return this._state; - } - - public set state(value: ValidationState) { - if (value > this._state) { - this._state = value; - } - } - - public isOK(): boolean { - return this._state === ValidationState.OK; - } - - public isFatal(): boolean { - return this._state === ValidationState.Fatal; - } -} - -export interface IProblemReporter { - info(message: string): void; - warn(message: string): void; - error(message: string): void; - fatal(message: string): void; - status: ValidationStatus; -} - -export abstract class Parser { - - private _problemReporter: IProblemReporter; - - constructor(problemReporter: IProblemReporter) { - this._problemReporter = problemReporter; - } - - public reset(): void { - this._problemReporter.status.state = ValidationState.OK; - } - - public get problemReporter(): IProblemReporter { - return this._problemReporter; - } - - public info(message: string): void { - this._problemReporter.info(message); - } - - public warn(message: string): void { - this._problemReporter.warn(message); - } - - public error(message: string): void { - this._problemReporter.error(message); - } - - public fatal(message: string): void { - this._problemReporter.fatal(message); - } -} diff --git a/src/vs/base/common/path.ts b/src/vs/base/common/path.ts deleted file mode 100644 index 6f40f7d0..00000000 --- a/src/vs/base/common/path.ts +++ /dev/null @@ -1,1529 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// NOTE: VSCode's copy of nodejs path library to be usable in common (non-node) namespace -// Copied from: https://github.com/nodejs/node/commits/v20.9.0/lib/path.js -// Excluding: the change that adds primordials -// (https://github.com/nodejs/node/commit/187a862d221dec42fa9a5c4214e7034d9092792f and others) - -/** - * Copyright Joyent, Inc. and other Node contributors. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to permit - * persons to whom the Software is furnished to do so, subject to the - * following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN - * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE - * USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -import * as process from 'vs/base/common/process'; - -const CHAR_UPPERCASE_A = 65;/* A */ -const CHAR_LOWERCASE_A = 97; /* a */ -const CHAR_UPPERCASE_Z = 90; /* Z */ -const CHAR_LOWERCASE_Z = 122; /* z */ -const CHAR_DOT = 46; /* . */ -const CHAR_FORWARD_SLASH = 47; /* / */ -const CHAR_BACKWARD_SLASH = 92; /* \ */ -const CHAR_COLON = 58; /* : */ -const CHAR_QUESTION_MARK = 63; /* ? */ - -class ErrorInvalidArgType extends Error { - code: 'ERR_INVALID_ARG_TYPE'; - constructor(name: string, expected: string, actual: unknown) { - // determiner: 'must be' or 'must not be' - let determiner; - if (typeof expected === 'string' && expected.indexOf('not ') === 0) { - determiner = 'must not be'; - expected = expected.replace(/^not /, ''); - } else { - determiner = 'must be'; - } - - const type = name.indexOf('.') !== -1 ? 'property' : 'argument'; - let msg = `The "${name}" ${type} ${determiner} of type ${expected}`; - - msg += `. Received type ${typeof actual}`; - super(msg); - - this.code = 'ERR_INVALID_ARG_TYPE'; - } -} - -function validateObject(pathObject: object, name: string) { - if (pathObject === null || typeof pathObject !== 'object') { - throw new ErrorInvalidArgType(name, 'Object', pathObject); - } -} - -function validateString(value: string, name: string) { - if (typeof value !== 'string') { - throw new ErrorInvalidArgType(name, 'string', value); - } -} - -const platformIsWin32 = (process.platform === 'win32'); - -function isPathSeparator(code: number | undefined) { - return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; -} - -function isPosixPathSeparator(code: number | undefined) { - return code === CHAR_FORWARD_SLASH; -} - -function isWindowsDeviceRoot(code: number) { - return (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) || - (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z); -} - -// Resolves . and .. elements in a path with directory names -function normalizeString(path: string, allowAboveRoot: boolean, separator: string, isPathSeparator: (code?: number) => boolean) { - let res = ''; - let lastSegmentLength = 0; - let lastSlash = -1; - let dots = 0; - let code = 0; - for (let i = 0; i <= path.length; ++i) { - if (i < path.length) { - code = path.charCodeAt(i); - } - else if (isPathSeparator(code)) { - break; - } - else { - code = CHAR_FORWARD_SLASH; - } - - if (isPathSeparator(code)) { - if (lastSlash === i - 1 || dots === 1) { - // NOOP - } else if (dots === 2) { - if (res.length < 2 || lastSegmentLength !== 2 || - res.charCodeAt(res.length - 1) !== CHAR_DOT || - res.charCodeAt(res.length - 2) !== CHAR_DOT) { - if (res.length > 2) { - const lastSlashIndex = res.lastIndexOf(separator); - if (lastSlashIndex === -1) { - res = ''; - lastSegmentLength = 0; - } else { - res = res.slice(0, lastSlashIndex); - lastSegmentLength = res.length - 1 - res.lastIndexOf(separator); - } - lastSlash = i; - dots = 0; - continue; - } else if (res.length !== 0) { - res = ''; - lastSegmentLength = 0; - lastSlash = i; - dots = 0; - continue; - } - } - if (allowAboveRoot) { - res += res.length > 0 ? `${separator}..` : '..'; - lastSegmentLength = 2; - } - } else { - if (res.length > 0) { - res += `${separator}${path.slice(lastSlash + 1, i)}`; - } - else { - res = path.slice(lastSlash + 1, i); - } - lastSegmentLength = i - lastSlash - 1; - } - lastSlash = i; - dots = 0; - } else if (code === CHAR_DOT && dots !== -1) { - ++dots; - } else { - dots = -1; - } - } - return res; -} - -function formatExt(ext: string): string { - return ext ? `${ext[0] === '.' ? '' : '.'}${ext}` : ''; -} - -function _format(sep: string, pathObject: ParsedPath) { - validateObject(pathObject, 'pathObject'); - const dir = pathObject.dir || pathObject.root; - const base = pathObject.base || - `${pathObject.name || ''}${formatExt(pathObject.ext)}`; - if (!dir) { - return base; - } - return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`; -} - -export interface ParsedPath { - root: string; - dir: string; - base: string; - ext: string; - name: string; -} - -export interface IPath { - normalize(path: string): string; - isAbsolute(path: string): boolean; - join(...paths: string[]): string; - resolve(...pathSegments: string[]): string; - relative(from: string, to: string): string; - dirname(path: string): string; - basename(path: string, suffix?: string): string; - extname(path: string): string; - format(pathObject: ParsedPath): string; - parse(path: string): ParsedPath; - toNamespacedPath(path: string): string; - sep: '\\' | '/'; - delimiter: string; - win32: IPath | null; - posix: IPath | null; -} - -export const win32: IPath = { - // path.resolve([from ...], to) - resolve(...pathSegments: string[]): string { - let resolvedDevice = ''; - let resolvedTail = ''; - let resolvedAbsolute = false; - - for (let i = pathSegments.length - 1; i >= -1; i--) { - let path; - if (i >= 0) { - path = pathSegments[i]; - validateString(path, `paths[${i}]`); - - // Skip empty entries - if (path.length === 0) { - continue; - } - } else if (resolvedDevice.length === 0) { - path = process.cwd(); - } else { - // Windows has the concept of drive-specific current working - // directories. If we've resolved a drive letter but not yet an - // absolute path, get cwd for that drive, or the process cwd if - // the drive cwd is not available. We're sure the device is not - // a UNC path at this points, because UNC paths are always absolute. - path = process.env[`=${resolvedDevice}`] || process.cwd(); - - // Verify that a cwd was found and that it actually points - // to our drive. If not, default to the drive's root. - if (path === undefined || - (path.slice(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() && - path.charCodeAt(2) === CHAR_BACKWARD_SLASH)) { - path = `${resolvedDevice}\\`; - } - } - - const len = path.length; - let rootEnd = 0; - let device = ''; - let isAbsolute = false; - const code = path.charCodeAt(0); - - // Try to match a root - if (len === 1) { - if (isPathSeparator(code)) { - // `path` contains just a path separator - rootEnd = 1; - isAbsolute = true; - } - } else if (isPathSeparator(code)) { - // Possible UNC root - - // If we started with a separator, we know we at least have an - // absolute path of some kind (UNC or otherwise) - isAbsolute = true; - - if (isPathSeparator(path.charCodeAt(1))) { - // Matched double path separator at beginning - let j = 2; - let last = j; - // Match 1 or more non-path separators - while (j < len && !isPathSeparator(path.charCodeAt(j))) { - j++; - } - if (j < len && j !== last) { - const firstPart = path.slice(last, j); - // Matched! - last = j; - // Match 1 or more path separators - while (j < len && isPathSeparator(path.charCodeAt(j))) { - j++; - } - if (j < len && j !== last) { - // Matched! - last = j; - // Match 1 or more non-path separators - while (j < len && !isPathSeparator(path.charCodeAt(j))) { - j++; - } - if (j === len || j !== last) { - // We matched a UNC root - device = `\\\\${firstPart}\\${path.slice(last, j)}`; - rootEnd = j; - } - } - } - } else { - rootEnd = 1; - } - } else if (isWindowsDeviceRoot(code) && - path.charCodeAt(1) === CHAR_COLON) { - // Possible device root - device = path.slice(0, 2); - rootEnd = 2; - if (len > 2 && isPathSeparator(path.charCodeAt(2))) { - // Treat separator following drive name as an absolute path - // indicator - isAbsolute = true; - rootEnd = 3; - } - } - - if (device.length > 0) { - if (resolvedDevice.length > 0) { - if (device.toLowerCase() !== resolvedDevice.toLowerCase()) { - // This path points to another device so it is not applicable - continue; - } - } else { - resolvedDevice = device; - } - } - - if (resolvedAbsolute) { - if (resolvedDevice.length > 0) { - break; - } - } else { - resolvedTail = `${path.slice(rootEnd)}\\${resolvedTail}`; - resolvedAbsolute = isAbsolute; - if (isAbsolute && resolvedDevice.length > 0) { - break; - } - } - } - - // At this point the path should be resolved to a full absolute path, - // but handle relative paths to be safe (might happen when process.cwd() - // fails) - - // Normalize the tail path - resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', - isPathSeparator); - - return resolvedAbsolute ? - `${resolvedDevice}\\${resolvedTail}` : - `${resolvedDevice}${resolvedTail}` || '.'; - }, - - normalize(path: string): string { - validateString(path, 'path'); - const len = path.length; - if (len === 0) { - return '.'; - } - let rootEnd = 0; - let device; - let isAbsolute = false; - const code = path.charCodeAt(0); - - // Try to match a root - if (len === 1) { - // `path` contains just a single char, exit early to avoid - // unnecessary work - return isPosixPathSeparator(code) ? '\\' : path; - } - if (isPathSeparator(code)) { - // Possible UNC root - - // If we started with a separator, we know we at least have an absolute - // path of some kind (UNC or otherwise) - isAbsolute = true; - - if (isPathSeparator(path.charCodeAt(1))) { - // Matched double path separator at beginning - let j = 2; - let last = j; - // Match 1 or more non-path separators - while (j < len && !isPathSeparator(path.charCodeAt(j))) { - j++; - } - if (j < len && j !== last) { - const firstPart = path.slice(last, j); - // Matched! - last = j; - // Match 1 or more path separators - while (j < len && isPathSeparator(path.charCodeAt(j))) { - j++; - } - if (j < len && j !== last) { - // Matched! - last = j; - // Match 1 or more non-path separators - while (j < len && !isPathSeparator(path.charCodeAt(j))) { - j++; - } - if (j === len) { - // We matched a UNC root only - // Return the normalized version of the UNC root since there - // is nothing left to process - return `\\\\${firstPart}\\${path.slice(last)}\\`; - } - if (j !== last) { - // We matched a UNC root with leftovers - device = `\\\\${firstPart}\\${path.slice(last, j)}`; - rootEnd = j; - } - } - } - } else { - rootEnd = 1; - } - } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { - // Possible device root - device = path.slice(0, 2); - rootEnd = 2; - if (len > 2 && isPathSeparator(path.charCodeAt(2))) { - // Treat separator following drive name as an absolute path - // indicator - isAbsolute = true; - rootEnd = 3; - } - } - - let tail = rootEnd < len ? - normalizeString(path.slice(rootEnd), !isAbsolute, '\\', isPathSeparator) : - ''; - if (tail.length === 0 && !isAbsolute) { - tail = '.'; - } - if (tail.length > 0 && isPathSeparator(path.charCodeAt(len - 1))) { - tail += '\\'; - } - if (device === undefined) { - return isAbsolute ? `\\${tail}` : tail; - } - return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`; - }, - - isAbsolute(path: string): boolean { - validateString(path, 'path'); - const len = path.length; - if (len === 0) { - return false; - } - - const code = path.charCodeAt(0); - return isPathSeparator(code) || - // Possible device root - (len > 2 && - isWindowsDeviceRoot(code) && - path.charCodeAt(1) === CHAR_COLON && - isPathSeparator(path.charCodeAt(2))); - }, - - join(...paths: string[]): string { - if (paths.length === 0) { - return '.'; - } - - let joined; - let firstPart: string | undefined; - for (let i = 0; i < paths.length; ++i) { - const arg = paths[i]; - validateString(arg, 'path'); - if (arg.length > 0) { - if (joined === undefined) { - joined = firstPart = arg; - } - else { - joined += `\\${arg}`; - } - } - } - - if (joined === undefined) { - return '.'; - } - - // Make sure that the joined path doesn't start with two slashes, because - // normalize() will mistake it for a UNC path then. - // - // This step is skipped when it is very clear that the user actually - // intended to point at a UNC path. This is assumed when the first - // non-empty string arguments starts with exactly two slashes followed by - // at least one more non-slash character. - // - // Note that for normalize() to treat a path as a UNC path it needs to - // have at least 2 components, so we don't filter for that here. - // This means that the user can use join to construct UNC paths from - // a server name and a share name; for example: - // path.join('//server', 'share') -> '\\\\server\\share\\') - let needsReplace = true; - let slashCount = 0; - if (typeof firstPart === 'string' && isPathSeparator(firstPart.charCodeAt(0))) { - ++slashCount; - const firstLen = firstPart.length; - if (firstLen > 1 && isPathSeparator(firstPart.charCodeAt(1))) { - ++slashCount; - if (firstLen > 2) { - if (isPathSeparator(firstPart.charCodeAt(2))) { - ++slashCount; - } else { - // We matched a UNC path in the first part - needsReplace = false; - } - } - } - } - if (needsReplace) { - // Find any more consecutive slashes we need to replace - while (slashCount < joined.length && - isPathSeparator(joined.charCodeAt(slashCount))) { - slashCount++; - } - - // Replace the slashes if needed - if (slashCount >= 2) { - joined = `\\${joined.slice(slashCount)}`; - } - } - - return win32.normalize(joined); - }, - - - // It will solve the relative path from `from` to `to`, for instance: - // from = 'C:\\orandea\\test\\aaa' - // to = 'C:\\orandea\\impl\\bbb' - // The output of the function should be: '..\\..\\impl\\bbb' - relative(from: string, to: string): string { - validateString(from, 'from'); - validateString(to, 'to'); - - if (from === to) { - return ''; - } - - const fromOrig = win32.resolve(from); - const toOrig = win32.resolve(to); - - if (fromOrig === toOrig) { - return ''; - } - - from = fromOrig.toLowerCase(); - to = toOrig.toLowerCase(); - - if (from === to) { - return ''; - } - - // Trim any leading backslashes - let fromStart = 0; - while (fromStart < from.length && - from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) { - fromStart++; - } - // Trim trailing backslashes (applicable to UNC paths only) - let fromEnd = from.length; - while (fromEnd - 1 > fromStart && - from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) { - fromEnd--; - } - const fromLen = fromEnd - fromStart; - - // Trim any leading backslashes - let toStart = 0; - while (toStart < to.length && - to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) { - toStart++; - } - // Trim trailing backslashes (applicable to UNC paths only) - let toEnd = to.length; - while (toEnd - 1 > toStart && - to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) { - toEnd--; - } - const toLen = toEnd - toStart; - - // Compare paths to find the longest common path from root - const length = fromLen < toLen ? fromLen : toLen; - let lastCommonSep = -1; - let i = 0; - for (; i < length; i++) { - const fromCode = from.charCodeAt(fromStart + i); - if (fromCode !== to.charCodeAt(toStart + i)) { - break; - } else if (fromCode === CHAR_BACKWARD_SLASH) { - lastCommonSep = i; - } - } - - // We found a mismatch before the first common path separator was seen, so - // return the original `to`. - if (i !== length) { - if (lastCommonSep === -1) { - return toOrig; - } - } else { - if (toLen > length) { - if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) { - // We get here if `from` is the exact base path for `to`. - // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz' - return toOrig.slice(toStart + i + 1); - } - if (i === 2) { - // We get here if `from` is the device root. - // For example: from='C:\\'; to='C:\\foo' - return toOrig.slice(toStart + i); - } - } - if (fromLen > length) { - if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) { - // We get here if `to` is the exact base path for `from`. - // For example: from='C:\\foo\\bar'; to='C:\\foo' - lastCommonSep = i; - } else if (i === 2) { - // We get here if `to` is the device root. - // For example: from='C:\\foo\\bar'; to='C:\\' - lastCommonSep = 3; - } - } - if (lastCommonSep === -1) { - lastCommonSep = 0; - } - } - - let out = ''; - // Generate the relative path based on the path difference between `to` and - // `from` - for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { - if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) { - out += out.length === 0 ? '..' : '\\..'; - } - } - - toStart += lastCommonSep; - - // Lastly, append the rest of the destination (`to`) path that comes after - // the common path parts - if (out.length > 0) { - return `${out}${toOrig.slice(toStart, toEnd)}`; - } - - if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) { - ++toStart; - } - - return toOrig.slice(toStart, toEnd); - }, - - toNamespacedPath(path: string): string { - // Note: this will *probably* throw somewhere. - if (typeof path !== 'string' || path.length === 0) { - return path; - } - - const resolvedPath = win32.resolve(path); - - if (resolvedPath.length <= 2) { - return path; - } - - if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) { - // Possible UNC root - if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) { - const code = resolvedPath.charCodeAt(2); - if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) { - // Matched non-long UNC root, convert the path to a long UNC path - return `\\\\?\\UNC\\${resolvedPath.slice(2)}`; - } - } - } else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) && - resolvedPath.charCodeAt(1) === CHAR_COLON && - resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH) { - // Matched device root, convert the path to a long UNC path - return `\\\\?\\${resolvedPath}`; - } - - return path; - }, - - dirname(path: string): string { - validateString(path, 'path'); - const len = path.length; - if (len === 0) { - return '.'; - } - let rootEnd = -1; - let offset = 0; - const code = path.charCodeAt(0); - - if (len === 1) { - // `path` contains just a path separator, exit early to avoid - // unnecessary work or a dot. - return isPathSeparator(code) ? path : '.'; - } - - // Try to match a root - if (isPathSeparator(code)) { - // Possible UNC root - - rootEnd = offset = 1; - - if (isPathSeparator(path.charCodeAt(1))) { - // Matched double path separator at beginning - let j = 2; - let last = j; - // Match 1 or more non-path separators - while (j < len && !isPathSeparator(path.charCodeAt(j))) { - j++; - } - if (j < len && j !== last) { - // Matched! - last = j; - // Match 1 or more path separators - while (j < len && isPathSeparator(path.charCodeAt(j))) { - j++; - } - if (j < len && j !== last) { - // Matched! - last = j; - // Match 1 or more non-path separators - while (j < len && !isPathSeparator(path.charCodeAt(j))) { - j++; - } - if (j === len) { - // We matched a UNC root only - return path; - } - if (j !== last) { - // We matched a UNC root with leftovers - - // Offset by 1 to include the separator after the UNC root to - // treat it as a "normal root" on top of a (UNC) root - rootEnd = offset = j + 1; - } - } - } - } - // Possible device root - } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { - rootEnd = len > 2 && isPathSeparator(path.charCodeAt(2)) ? 3 : 2; - offset = rootEnd; - } - - let end = -1; - let matchedSlash = true; - for (let i = len - 1; i >= offset; --i) { - if (isPathSeparator(path.charCodeAt(i))) { - if (!matchedSlash) { - end = i; - break; - } - } else { - // We saw the first non-path separator - matchedSlash = false; - } - } - - if (end === -1) { - if (rootEnd === -1) { - return '.'; - } - - end = rootEnd; - } - return path.slice(0, end); - }, - - basename(path: string, suffix?: string): string { - if (suffix !== undefined) { - validateString(suffix, 'suffix'); - } - validateString(path, 'path'); - let start = 0; - let end = -1; - let matchedSlash = true; - let i; - - // Check for a drive letter prefix so as not to mistake the following - // path separator as an extra separator at the end of the path that can be - // disregarded - if (path.length >= 2 && - isWindowsDeviceRoot(path.charCodeAt(0)) && - path.charCodeAt(1) === CHAR_COLON) { - start = 2; - } - - if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) { - if (suffix === path) { - return ''; - } - let extIdx = suffix.length - 1; - let firstNonSlashEnd = -1; - for (i = path.length - 1; i >= start; --i) { - const code = path.charCodeAt(i); - if (isPathSeparator(code)) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - start = i + 1; - break; - } - } else { - if (firstNonSlashEnd === -1) { - // We saw the first non-path separator, remember this index in case - // we need it if the extension ends up not matching - matchedSlash = false; - firstNonSlashEnd = i + 1; - } - if (extIdx >= 0) { - // Try to match the explicit extension - if (code === suffix.charCodeAt(extIdx)) { - if (--extIdx === -1) { - // We matched the extension, so mark this as the end of our path - // component - end = i; - } - } else { - // Extension does not match, so our result is the entire path - // component - extIdx = -1; - end = firstNonSlashEnd; - } - } - } - } - - if (start === end) { - end = firstNonSlashEnd; - } else if (end === -1) { - end = path.length; - } - return path.slice(start, end); - } - for (i = path.length - 1; i >= start; --i) { - if (isPathSeparator(path.charCodeAt(i))) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - start = i + 1; - break; - } - } else if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // path component - matchedSlash = false; - end = i + 1; - } - } - - if (end === -1) { - return ''; - } - return path.slice(start, end); - }, - - extname(path: string): string { - validateString(path, 'path'); - let start = 0; - let startDot = -1; - let startPart = 0; - let end = -1; - let matchedSlash = true; - // Track the state of characters (if any) we see before our first dot and - // after any path separator we find - let preDotState = 0; - - // Check for a drive letter prefix so as not to mistake the following - // path separator as an extra separator at the end of the path that can be - // disregarded - - if (path.length >= 2 && - path.charCodeAt(1) === CHAR_COLON && - isWindowsDeviceRoot(path.charCodeAt(0))) { - start = startPart = 2; - } - - for (let i = path.length - 1; i >= start; --i) { - const code = path.charCodeAt(i); - if (isPathSeparator(code)) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - startPart = i + 1; - break; - } - continue; - } - if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // extension - matchedSlash = false; - end = i + 1; - } - if (code === CHAR_DOT) { - // If this is our first dot, mark it as the start of our extension - if (startDot === -1) { - startDot = i; - } - else if (preDotState !== 1) { - preDotState = 1; - } - } else if (startDot !== -1) { - // We saw a non-dot and non-path separator before our dot, so we should - // have a good chance at having a non-empty extension - preDotState = -1; - } - } - - if (startDot === -1 || - end === -1 || - // We saw a non-dot character immediately before the dot - preDotState === 0 || - // The (right-most) trimmed path component is exactly '..' - (preDotState === 1 && - startDot === end - 1 && - startDot === startPart + 1)) { - return ''; - } - return path.slice(startDot, end); - }, - - format: _format.bind(null, '\\'), - - parse(path) { - validateString(path, 'path'); - - const ret = { root: '', dir: '', base: '', ext: '', name: '' }; - if (path.length === 0) { - return ret; - } - - const len = path.length; - let rootEnd = 0; - let code = path.charCodeAt(0); - - if (len === 1) { - if (isPathSeparator(code)) { - // `path` contains just a path separator, exit early to avoid - // unnecessary work - ret.root = ret.dir = path; - return ret; - } - ret.base = ret.name = path; - return ret; - } - // Try to match a root - if (isPathSeparator(code)) { - // Possible UNC root - - rootEnd = 1; - if (isPathSeparator(path.charCodeAt(1))) { - // Matched double path separator at beginning - let j = 2; - let last = j; - // Match 1 or more non-path separators - while (j < len && !isPathSeparator(path.charCodeAt(j))) { - j++; - } - if (j < len && j !== last) { - // Matched! - last = j; - // Match 1 or more path separators - while (j < len && isPathSeparator(path.charCodeAt(j))) { - j++; - } - if (j < len && j !== last) { - // Matched! - last = j; - // Match 1 or more non-path separators - while (j < len && !isPathSeparator(path.charCodeAt(j))) { - j++; - } - if (j === len) { - // We matched a UNC root only - rootEnd = j; - } else if (j !== last) { - // We matched a UNC root with leftovers - rootEnd = j + 1; - } - } - } - } - } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { - // Possible device root - if (len <= 2) { - // `path` contains just a drive root, exit early to avoid - // unnecessary work - ret.root = ret.dir = path; - return ret; - } - rootEnd = 2; - if (isPathSeparator(path.charCodeAt(2))) { - if (len === 3) { - // `path` contains just a drive root, exit early to avoid - // unnecessary work - ret.root = ret.dir = path; - return ret; - } - rootEnd = 3; - } - } - if (rootEnd > 0) { - ret.root = path.slice(0, rootEnd); - } - - let startDot = -1; - let startPart = rootEnd; - let end = -1; - let matchedSlash = true; - let i = path.length - 1; - - // Track the state of characters (if any) we see before our first dot and - // after any path separator we find - let preDotState = 0; - - // Get non-dir info - for (; i >= rootEnd; --i) { - code = path.charCodeAt(i); - if (isPathSeparator(code)) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - startPart = i + 1; - break; - } - continue; - } - if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // extension - matchedSlash = false; - end = i + 1; - } - if (code === CHAR_DOT) { - // If this is our first dot, mark it as the start of our extension - if (startDot === -1) { - startDot = i; - } else if (preDotState !== 1) { - preDotState = 1; - } - } else if (startDot !== -1) { - // We saw a non-dot and non-path separator before our dot, so we should - // have a good chance at having a non-empty extension - preDotState = -1; - } - } - - if (end !== -1) { - if (startDot === -1 || - // We saw a non-dot character immediately before the dot - preDotState === 0 || - // The (right-most) trimmed path component is exactly '..' - (preDotState === 1 && - startDot === end - 1 && - startDot === startPart + 1)) { - ret.base = ret.name = path.slice(startPart, end); - } else { - ret.name = path.slice(startPart, startDot); - ret.base = path.slice(startPart, end); - ret.ext = path.slice(startDot, end); - } - } - - // If the directory is the root, use the entire root as the `dir` including - // the trailing slash if any (`C:\abc` -> `C:\`). Otherwise, strip out the - // trailing slash (`C:\abc\def` -> `C:\abc`). - if (startPart > 0 && startPart !== rootEnd) { - ret.dir = path.slice(0, startPart - 1); - } else { - ret.dir = ret.root; - } - - return ret; - }, - - sep: '\\', - delimiter: ';', - win32: null, - posix: null -}; - -const posixCwd = (() => { - if (platformIsWin32) { - // Converts Windows' backslash path separators to POSIX forward slashes - // and truncates any drive indicator - const regexp = /\\/g; - return () => { - const cwd = process.cwd().replace(regexp, '/'); - return cwd.slice(cwd.indexOf('/')); - }; - } - - // We're already on POSIX, no need for any transformations - return () => process.cwd(); -})(); - -export const posix: IPath = { - // path.resolve([from ...], to) - resolve(...pathSegments: string[]): string { - let resolvedPath = ''; - let resolvedAbsolute = false; - - for (let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--) { - const path = i >= 0 ? pathSegments[i] : posixCwd(); - - validateString(path, `paths[${i}]`); - - // Skip empty entries - if (path.length === 0) { - continue; - } - - resolvedPath = `${path}/${resolvedPath}`; - resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; - } - - // At this point the path should be resolved to a full absolute path, but - // handle relative paths to be safe (might happen when process.cwd() fails) - - // Normalize the path - resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', - isPosixPathSeparator); - - if (resolvedAbsolute) { - return `/${resolvedPath}`; - } - return resolvedPath.length > 0 ? resolvedPath : '.'; - }, - - normalize(path: string): string { - validateString(path, 'path'); - - if (path.length === 0) { - return '.'; - } - - const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; - const trailingSeparator = - path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH; - - // Normalize the path - path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator); - - if (path.length === 0) { - if (isAbsolute) { - return '/'; - } - return trailingSeparator ? './' : '.'; - } - if (trailingSeparator) { - path += '/'; - } - - return isAbsolute ? `/${path}` : path; - }, - - isAbsolute(path: string): boolean { - validateString(path, 'path'); - return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH; - }, - - join(...paths: string[]): string { - if (paths.length === 0) { - return '.'; - } - let joined; - for (let i = 0; i < paths.length; ++i) { - const arg = paths[i]; - validateString(arg, 'path'); - if (arg.length > 0) { - if (joined === undefined) { - joined = arg; - } else { - joined += `/${arg}`; - } - } - } - if (joined === undefined) { - return '.'; - } - return posix.normalize(joined); - }, - - relative(from: string, to: string): string { - validateString(from, 'from'); - validateString(to, 'to'); - - if (from === to) { - return ''; - } - - // Trim leading forward slashes. - from = posix.resolve(from); - to = posix.resolve(to); - - if (from === to) { - return ''; - } - - const fromStart = 1; - const fromEnd = from.length; - const fromLen = fromEnd - fromStart; - const toStart = 1; - const toLen = to.length - toStart; - - // Compare paths to find the longest common path from root - const length = (fromLen < toLen ? fromLen : toLen); - let lastCommonSep = -1; - let i = 0; - for (; i < length; i++) { - const fromCode = from.charCodeAt(fromStart + i); - if (fromCode !== to.charCodeAt(toStart + i)) { - break; - } else if (fromCode === CHAR_FORWARD_SLASH) { - lastCommonSep = i; - } - } - if (i === length) { - if (toLen > length) { - if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) { - // We get here if `from` is the exact base path for `to`. - // For example: from='/foo/bar'; to='/foo/bar/baz' - return to.slice(toStart + i + 1); - } - if (i === 0) { - // We get here if `from` is the root - // For example: from='/'; to='/foo' - return to.slice(toStart + i); - } - } else if (fromLen > length) { - if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) { - // We get here if `to` is the exact base path for `from`. - // For example: from='/foo/bar/baz'; to='/foo/bar' - lastCommonSep = i; - } else if (i === 0) { - // We get here if `to` is the root. - // For example: from='/foo/bar'; to='/' - lastCommonSep = 0; - } - } - } - - let out = ''; - // Generate the relative path based on the path difference between `to` - // and `from`. - for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { - if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) { - out += out.length === 0 ? '..' : '/..'; - } - } - - // Lastly, append the rest of the destination (`to`) path that comes after - // the common path parts. - return `${out}${to.slice(toStart + lastCommonSep)}`; - }, - - toNamespacedPath(path: string): string { - // Non-op on posix systems - return path; - }, - - dirname(path: string): string { - validateString(path, 'path'); - if (path.length === 0) { - return '.'; - } - const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH; - let end = -1; - let matchedSlash = true; - for (let i = path.length - 1; i >= 1; --i) { - if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { - if (!matchedSlash) { - end = i; - break; - } - } else { - // We saw the first non-path separator - matchedSlash = false; - } - } - - if (end === -1) { - return hasRoot ? '/' : '.'; - } - if (hasRoot && end === 1) { - return '//'; - } - return path.slice(0, end); - }, - - basename(path: string, suffix?: string): string { - if (suffix !== undefined) { - validateString(suffix, 'ext'); - } - validateString(path, 'path'); - - let start = 0; - let end = -1; - let matchedSlash = true; - let i; - - if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) { - if (suffix === path) { - return ''; - } - let extIdx = suffix.length - 1; - let firstNonSlashEnd = -1; - for (i = path.length - 1; i >= 0; --i) { - const code = path.charCodeAt(i); - if (code === CHAR_FORWARD_SLASH) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - start = i + 1; - break; - } - } else { - if (firstNonSlashEnd === -1) { - // We saw the first non-path separator, remember this index in case - // we need it if the extension ends up not matching - matchedSlash = false; - firstNonSlashEnd = i + 1; - } - if (extIdx >= 0) { - // Try to match the explicit extension - if (code === suffix.charCodeAt(extIdx)) { - if (--extIdx === -1) { - // We matched the extension, so mark this as the end of our path - // component - end = i; - } - } else { - // Extension does not match, so our result is the entire path - // component - extIdx = -1; - end = firstNonSlashEnd; - } - } - } - } - - if (start === end) { - end = firstNonSlashEnd; - } else if (end === -1) { - end = path.length; - } - return path.slice(start, end); - } - for (i = path.length - 1; i >= 0; --i) { - if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - start = i + 1; - break; - } - } else if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // path component - matchedSlash = false; - end = i + 1; - } - } - - if (end === -1) { - return ''; - } - return path.slice(start, end); - }, - - extname(path: string): string { - validateString(path, 'path'); - let startDot = -1; - let startPart = 0; - let end = -1; - let matchedSlash = true; - // Track the state of characters (if any) we see before our first dot and - // after any path separator we find - let preDotState = 0; - for (let i = path.length - 1; i >= 0; --i) { - const code = path.charCodeAt(i); - if (code === CHAR_FORWARD_SLASH) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - startPart = i + 1; - break; - } - continue; - } - if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // extension - matchedSlash = false; - end = i + 1; - } - if (code === CHAR_DOT) { - // If this is our first dot, mark it as the start of our extension - if (startDot === -1) { - startDot = i; - } - else if (preDotState !== 1) { - preDotState = 1; - } - } else if (startDot !== -1) { - // We saw a non-dot and non-path separator before our dot, so we should - // have a good chance at having a non-empty extension - preDotState = -1; - } - } - - if (startDot === -1 || - end === -1 || - // We saw a non-dot character immediately before the dot - preDotState === 0 || - // The (right-most) trimmed path component is exactly '..' - (preDotState === 1 && - startDot === end - 1 && - startDot === startPart + 1)) { - return ''; - } - return path.slice(startDot, end); - }, - - format: _format.bind(null, '/'), - - parse(path: string): ParsedPath { - validateString(path, 'path'); - - const ret = { root: '', dir: '', base: '', ext: '', name: '' }; - if (path.length === 0) { - return ret; - } - const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; - let start; - if (isAbsolute) { - ret.root = '/'; - start = 1; - } else { - start = 0; - } - let startDot = -1; - let startPart = 0; - let end = -1; - let matchedSlash = true; - let i = path.length - 1; - - // Track the state of characters (if any) we see before our first dot and - // after any path separator we find - let preDotState = 0; - - // Get non-dir info - for (; i >= start; --i) { - const code = path.charCodeAt(i); - if (code === CHAR_FORWARD_SLASH) { - // If we reached a path separator that was not part of a set of path - // separators at the end of the string, stop now - if (!matchedSlash) { - startPart = i + 1; - break; - } - continue; - } - if (end === -1) { - // We saw the first non-path separator, mark this as the end of our - // extension - matchedSlash = false; - end = i + 1; - } - if (code === CHAR_DOT) { - // If this is our first dot, mark it as the start of our extension - if (startDot === -1) { - startDot = i; - } else if (preDotState !== 1) { - preDotState = 1; - } - } else if (startDot !== -1) { - // We saw a non-dot and non-path separator before our dot, so we should - // have a good chance at having a non-empty extension - preDotState = -1; - } - } - - if (end !== -1) { - const start = startPart === 0 && isAbsolute ? 1 : startPart; - if (startDot === -1 || - // We saw a non-dot character immediately before the dot - preDotState === 0 || - // The (right-most) trimmed path component is exactly '..' - (preDotState === 1 && - startDot === end - 1 && - startDot === startPart + 1)) { - ret.base = ret.name = path.slice(start, end); - } else { - ret.name = path.slice(start, startDot); - ret.base = path.slice(start, end); - ret.ext = path.slice(startDot, end); - } - } - - if (startPart > 0) { - ret.dir = path.slice(0, startPart - 1); - } else if (isAbsolute) { - ret.dir = '/'; - } - - return ret; - }, - - sep: '/', - delimiter: ':', - win32: null, - posix: null -}; - -posix.win32 = win32.win32 = win32; -posix.posix = win32.posix = posix; - -export const normalize = (platformIsWin32 ? win32.normalize : posix.normalize); -export const isAbsolute = (platformIsWin32 ? win32.isAbsolute : posix.isAbsolute); -export const join = (platformIsWin32 ? win32.join : posix.join); -export const resolve = (platformIsWin32 ? win32.resolve : posix.resolve); -export const relative = (platformIsWin32 ? win32.relative : posix.relative); -export const dirname = (platformIsWin32 ? win32.dirname : posix.dirname); -export const basename = (platformIsWin32 ? win32.basename : posix.basename); -export const extname = (platformIsWin32 ? win32.extname : posix.extname); -export const format = (platformIsWin32 ? win32.format : posix.format); -export const parse = (platformIsWin32 ? win32.parse : posix.parse); -export const toNamespacedPath = (platformIsWin32 ? win32.toNamespacedPath : posix.toNamespacedPath); -export const sep = (platformIsWin32 ? win32.sep : posix.sep); -export const delimiter = (platformIsWin32 ? win32.delimiter : posix.delimiter); diff --git a/src/vs/base/common/performance.d.ts b/src/vs/base/common/performance.d.ts deleted file mode 100644 index fc233e6f..00000000 --- a/src/vs/base/common/performance.d.ts +++ /dev/null @@ -1,16 +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 PerformanceMark { - readonly name: string; - readonly startTime: number; -} - -export function mark(name: string): void; - -/** - * Returns all marks, sorted by `startTime`. - */ -export function getMarks(): PerformanceMark[]; diff --git a/src/vs/base/common/performance.js b/src/vs/base/common/performance.js deleted file mode 100644 index cdea4917..00000000 --- a/src/vs/base/common/performance.js +++ /dev/null @@ -1,135 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check -'use strict'; - -// ESM-uncomment-begin -// const module = { exports: {} }; -// ESM-uncomment-end - -(function () { - - /** - * @returns {{mark(name:string):void, getMarks():{name:string, startTime:number}[]}} - */ - function _definePolyfillMarks(timeOrigin) { - - const _data = []; - if (typeof timeOrigin === 'number') { - _data.push('code/timeOrigin', timeOrigin); - } - - function mark(name) { - _data.push(name, Date.now()); - } - function getMarks() { - const result = []; - for (let i = 0; i < _data.length; i += 2) { - result.push({ - name: _data[i], - startTime: _data[i + 1], - }); - } - return result; - } - return { mark, getMarks }; - } - - /** - * @returns {{mark(name:string):void, getMarks():{name:string, startTime:number}[]}} - */ - function _define() { - - // Identify browser environment when following property is not present - // https://nodejs.org/dist/latest-v16.x/docs/api/perf_hooks.html#performancenodetiming - // @ts-ignore - if (typeof performance === 'object' && typeof performance.mark === 'function' && !performance.nodeTiming) { - // in a browser context, reuse performance-util - - if (typeof performance.timeOrigin !== 'number' && !performance.timing) { - // safari & webworker: because there is no timeOrigin and no workaround - // we use the `Date.now`-based polyfill. - return _definePolyfillMarks(); - - } else { - // use "native" performance for mark and getMarks - return { - mark(name) { - performance.mark(name); - }, - getMarks() { - let timeOrigin = performance.timeOrigin; - if (typeof timeOrigin !== 'number') { - // safari: there is no timerOrigin but in renderers there is the timing-property - // see https://bugs.webkit.org/show_bug.cgi?id=174862 - timeOrigin = performance.timing.navigationStart || performance.timing.redirectStart || performance.timing.fetchStart; - } - const result = [{ name: 'code/timeOrigin', startTime: Math.round(timeOrigin) }]; - for (const entry of performance.getEntriesByType('mark')) { - result.push({ - name: entry.name, - startTime: Math.round(timeOrigin + entry.startTime) - }); - } - return result; - } - }; - } - - } else if (typeof process === 'object') { - // node.js: use the normal polyfill but add the timeOrigin - // from the node perf_hooks API as very first mark - const timeOrigin = performance?.timeOrigin ?? Math.round((require.__$__nodeRequire || require)('perf_hooks').performance.timeOrigin); - return _definePolyfillMarks(timeOrigin); - - } else { - // unknown environment - console.trace('perf-util loaded in UNKNOWN environment'); - return _definePolyfillMarks(); - } - } - - function _factory(sharedObj) { - if (!sharedObj.MonacoPerformanceMarks) { - sharedObj.MonacoPerformanceMarks = _define(); - } - return sharedObj.MonacoPerformanceMarks; - } - - // This module can be loaded in an amd and commonjs-context. - // Because we want both instances to use the same perf-data - // we store them globally - - // eslint-disable-next-line no-var - var sharedObj; - if (typeof global === 'object') { - // nodejs - sharedObj = global; - } else if (typeof self === 'object') { - // browser - sharedObj = self; - } else { - sharedObj = {}; - } - - if (typeof define === 'function') { - // amd - define([], function () { return _factory(sharedObj); }); - } else if (typeof module === 'object' && typeof module.exports === 'object') { - // commonjs - module.exports = _factory(sharedObj); - } else { - console.trace('perf-util defined in UNKNOWN context (neither requirejs or commonjs)'); - // @ts-ignore - sharedObj.perf = _factory(sharedObj); - } - -})(); - -// ESM-uncomment-begin -// export const mark = module.exports.mark; -// export const getMarks = module.exports.getMarks; -// ESM-uncomment-end diff --git a/src/vs/base/common/ports.ts b/src/vs/base/common/ports.ts deleted file mode 100644 index 5ec75530..00000000 --- a/src/vs/base/common/ports.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * @returns Returns a random port between 1025 and 65535. - */ -export function randomPort(): number { - const min = 1025; - const max = 65535; - return min + Math.floor((max - min) * Math.random()); -} diff --git a/src/vs/base/common/prefixTree.ts b/src/vs/base/common/prefixTree.ts deleted file mode 100644 index 53f02964..00000000 --- a/src/vs/base/common/prefixTree.ts +++ /dev/null @@ -1,252 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Iterable } from 'vs/base/common/iterator'; - -const unset = Symbol('unset'); - -export interface IPrefixTreeNode { - /** Possible children of the node. */ - children?: ReadonlyMap>; - - /** The value if data exists for this node in the tree. Mutable. */ - value: T | undefined; -} - -/** - * A simple prefix tree implementation where a value is stored based on - * well-defined prefix segments. - */ -export class WellDefinedPrefixTree { - private readonly root = new Node(); - private _size = 0; - - public get size() { - return this._size; - } - - /** Gets the top-level nodes of the tree */ - public get nodes(): Iterable> { - return this.root.children?.values() || Iterable.empty(); - } - - /** Gets the top-level nodes of the tree */ - public get entries(): Iterable<[string, IPrefixTreeNode]> { - return this.root.children?.entries() || Iterable.empty(); - } - - /** - * Inserts a new value in the prefix tree. - * @param onNode - called for each node as we descend to the insertion point, - * including the insertion point itself. - */ - insert(key: Iterable, value: V, onNode?: (n: IPrefixTreeNode) => void): void { - this.opNode(key, n => n._value = value, onNode); - } - - /** Mutates a value in the prefix tree. */ - mutate(key: Iterable, mutate: (value?: V) => V): void { - this.opNode(key, n => n._value = mutate(n._value === unset ? undefined : n._value)); - } - - /** Mutates nodes along the path in the prefix tree. */ - mutatePath(key: Iterable, mutate: (node: IPrefixTreeNode) => void): void { - this.opNode(key, () => { }, n => mutate(n)); - } - - /** Deletes a node from the prefix tree, returning the value it contained. */ - delete(key: Iterable): V | undefined { - const path = this.getPathToKey(key); - if (!path) { - return; - } - - let i = path.length - 1; - const value = path[i].node._value; - if (value === unset) { - return; // not actually a real node - } - - this._size--; - path[i].node._value = unset; - - for (; i > 0; i--) { - const { node, part } = path[i]; - if (node.children?.size || node._value !== unset) { - break; - } - - path[i - 1].node.children!.delete(part); - } - - return value; - } - - /** Deletes a subtree from the prefix tree, returning the values they contained. */ - *deleteRecursive(key: Iterable): Iterable { - const path = this.getPathToKey(key); - if (!path) { - return; - } - - const subtree = path[path.length - 1].node; - - // important: run the deletion before we start to yield results, so that - // it still runs even if the caller doesn't consumer the iterator - for (let i = path.length - 1; i > 0; i--) { - const parent = path[i - 1]; - parent.node.children!.delete(path[i].part); - if (parent.node.children!.size > 0 || parent.node._value !== unset) { - break; - } - } - - for (const node of bfsIterate(subtree)) { - if (node._value !== unset) { - this._size--; - yield node._value; - } - } - } - - /** Gets a value from the tree. */ - find(key: Iterable): V | undefined { - let node = this.root; - for (const segment of key) { - const next = node.children?.get(segment); - if (!next) { - return undefined; - } - - node = next; - } - - return node._value === unset ? undefined : node._value; - } - - /** Gets whether the tree has the key, or a parent of the key, already inserted. */ - hasKeyOrParent(key: Iterable): boolean { - let node = this.root; - for (const segment of key) { - const next = node.children?.get(segment); - if (!next) { - return false; - } - if (next._value !== unset) { - return true; - } - - node = next; - } - - return false; - } - - /** Gets whether the tree has the given key or any children. */ - hasKeyOrChildren(key: Iterable): boolean { - let node = this.root; - for (const segment of key) { - const next = node.children?.get(segment); - if (!next) { - return false; - } - - node = next; - } - - return true; - } - - /** Gets whether the tree has the given key. */ - hasKey(key: Iterable): boolean { - let node = this.root; - for (const segment of key) { - const next = node.children?.get(segment); - if (!next) { - return false; - } - - node = next; - } - - return node._value !== unset; - } - - private getPathToKey(key: Iterable) { - const path = [{ part: '', node: this.root }]; - let i = 0; - for (const part of key) { - const node = path[i].node.children?.get(part); - if (!node) { - return; // node not in tree - } - - path.push({ part, node }); - i++; - } - - return path; - } - - private opNode(key: Iterable, fn: (node: Node) => void, onDescend?: (node: Node) => void): void { - let node = this.root; - for (const part of key) { - if (!node.children) { - const next = new Node(); - node.children = new Map([[part, next]]); - node = next; - } else if (!node.children.has(part)) { - const next = new Node(); - node.children.set(part, next); - node = next; - } else { - node = node.children.get(part)!; - } - onDescend?.(node); - } - - const sizeBefore = node._value === unset ? 0 : 1; - fn(node); - const sizeAfter = node._value === unset ? 0 : 1; - this._size += sizeAfter - sizeBefore; - } - - /** Returns an iterable of the tree values in no defined order. */ - *values() { - for (const { _value } of bfsIterate(this.root)) { - if (_value !== unset) { - yield _value; - } - } - } -} - -function* bfsIterate(root: Node): Iterable> { - const stack = [root]; - while (stack.length > 0) { - const node = stack.pop()!; - yield node; - - if (node.children) { - for (const child of node.children.values()) { - stack.push(child); - } - } - } -} - -class Node implements IPrefixTreeNode { - public children?: Map>; - - public get value() { - return this._value === unset ? undefined : this._value; - } - - public set value(value: T | undefined) { - this._value = value === undefined ? unset : value; - } - - public _value: T | typeof unset = unset; -} diff --git a/src/vs/base/common/process.ts b/src/vs/base/common/process.ts deleted file mode 100644 index 48fcd8ac..00000000 --- a/src/vs/base/common/process.ts +++ /dev/null @@ -1,76 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { INodeProcess, isMacintosh, isWindows } from 'vs/base/common/platform'; - -let safeProcess: Omit & { arch: string | undefined }; -declare const process: INodeProcess; - -// Native sandbox environment -const vscodeGlobal = (globalThis as any).vscode; -if (typeof vscodeGlobal !== 'undefined' && typeof vscodeGlobal.process !== 'undefined') { - const sandboxProcess: INodeProcess = vscodeGlobal.process; - safeProcess = { - get platform() { return sandboxProcess.platform; }, - get arch() { return sandboxProcess.arch; }, - get env() { return sandboxProcess.env; }, - cwd() { return sandboxProcess.cwd(); } - }; -} - -// Native node.js environment -else if (typeof process !== 'undefined') { - safeProcess = { - get platform() { return process.platform; }, - get arch() { return process.arch; }, - get env() { return process.env; }, - cwd() { return process.env['VSCODE_CWD'] || process.cwd(); } - }; -} - -// Web environment -else { - safeProcess = { - - // Supported - get platform() { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; }, - get arch() { return undefined; /* arch is undefined in web */ }, - - // Unsupported - get env() { return {}; }, - cwd() { return '/'; } - }; -} - -/** - * Provides safe access to the `cwd` property in node.js, sandboxed or web - * environments. - * - * Note: in web, this property is hardcoded to be `/`. - * - * @skipMangle - */ -export const cwd = safeProcess.cwd; - -/** - * Provides safe access to the `env` property in node.js, sandboxed or web - * environments. - * - * Note: in web, this property is hardcoded to be `{}`. - */ -export const env = safeProcess.env; - -/** - * Provides safe access to the `platform` property in node.js, sandboxed or web - * environments. - */ -export const platform = safeProcess.platform; - -/** - * Provides safe access to the `arch` method in node.js, sandboxed or web - * environments. - * Note: `arch` is `undefined` in web - */ -export const arch = safeProcess.arch; diff --git a/src/vs/base/common/processes.ts b/src/vs/base/common/processes.ts deleted file mode 100644 index ef29387b..00000000 --- a/src/vs/base/common/processes.ts +++ /dev/null @@ -1,148 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IProcessEnvironment, isLinux } from 'vs/base/common/platform'; - -/** - * Options to be passed to the external program or shell. - */ -export interface CommandOptions { - /** - * The current working directory of the executed program or shell. - * If omitted VSCode's current workspace root is used. - */ - cwd?: string; - - /** - * The environment of the executed program or shell. If omitted - * the parent process' environment is used. - */ - env?: { [key: string]: string }; -} - -export interface Executable { - /** - * The command to be executed. Can be an external program or a shell - * command. - */ - command: string; - - /** - * Specifies whether the command is a shell command and therefore must - * be executed in a shell interpreter (e.g. cmd.exe, bash, ...). - */ - isShellCommand: boolean; - - /** - * The arguments passed to the command. - */ - args: string[]; - - /** - * The command options used when the command is executed. Can be omitted. - */ - options?: CommandOptions; -} - -export interface ForkOptions extends CommandOptions { - execArgv?: string[]; -} - -export const enum Source { - stdout, - stderr -} - -/** - * The data send via a success callback - */ -export interface SuccessData { - error?: Error; - cmdCode?: number; - terminated?: boolean; -} - -/** - * The data send via a error callback - */ -export interface ErrorData { - error?: Error; - terminated?: boolean; - stdout?: string; - stderr?: string; -} - -export interface TerminateResponse { - success: boolean; - code?: TerminateResponseCode; - error?: any; -} - -export const enum TerminateResponseCode { - Success = 0, - Unknown = 1, - AccessDenied = 2, - ProcessNotFound = 3, -} - -export interface ProcessItem { - name: string; - cmd: string; - pid: number; - ppid: number; - load: number; - mem: number; - - children?: ProcessItem[]; -} - -/** - * Sanitizes a VS Code process environment by removing all Electron/VS Code-related values. - */ -export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve: string[]): void { - const set = preserve.reduce>((set, key) => { - set[key] = true; - return set; - }, {}); - const keysToRemove = [ - /^ELECTRON_.+$/, - /^VSCODE_(?!(PORTABLE|SHELL_LOGIN|ENV_REPLACE|ENV_APPEND|ENV_PREPEND)).+$/, - /^SNAP(|_.*)$/, - /^GDK_PIXBUF_.+$/, - ]; - const envKeys = Object.keys(env); - envKeys - .filter(key => !set[key]) - .forEach(envKey => { - for (let i = 0; i < keysToRemove.length; i++) { - if (envKey.search(keysToRemove[i]) !== -1) { - delete env[envKey]; - break; - } - } - }); -} - -/** - * Remove dangerous environment variables that have caused crashes - * in forked processes (i.e. in ELECTRON_RUN_AS_NODE processes) - * - * @param env The env object to change - */ -export function removeDangerousEnvVariables(env: IProcessEnvironment | undefined): void { - if (!env) { - return; - } - - // Unset `DEBUG`, as an invalid value might lead to process crashes - // See https://github.com/microsoft/vscode/issues/130072 - delete env['DEBUG']; - - if (isLinux) { - // Unset `LD_PRELOAD`, as it might lead to process crashes - // See https://github.com/microsoft/vscode/issues/134177 - delete env['LD_PRELOAD']; - } -} diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts deleted file mode 100644 index 754eba49..00000000 --- a/src/vs/base/common/product.ts +++ /dev/null @@ -1,314 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IStringDictionary } from 'vs/base/common/collections'; -import { PlatformName } from 'vs/base/common/platform'; - -export interface IBuiltInExtension { - readonly name: string; - readonly version: string; - readonly repo: string; - readonly metadata: any; -} - -export interface IProductWalkthrough { - id: string; - steps: IProductWalkthroughStep[]; -} - -export interface IProductWalkthroughStep { - id: string; - title: string; - when: string; - description: string; - media: - | { type: 'image'; path: string | { hc: string; hcLight?: string; light: string; dark: string }; altText: string } - | { type: 'svg'; path: string; altText: string } - | { type: 'markdown'; path: string }; -} - -export interface IFeaturedExtension { - readonly id: string; - readonly title: string; - readonly description: string; - readonly imagePath: string; -} - -export type ConfigurationSyncStore = { - url: string; - insidersUrl: string; - stableUrl: string; - canSwitch?: boolean; - authenticationProviders: IStringDictionary<{ scopes: string[] }>; -}; - -export type ExtensionUntrustedWorkspaceSupport = { - readonly default?: boolean | 'limited'; - readonly override?: boolean | 'limited'; -}; - -export type ExtensionVirtualWorkspaceSupport = { - readonly default?: boolean; - readonly override?: boolean; -}; - -export interface IProductConfiguration { - readonly version: string; - readonly date?: string; - readonly quality?: string; - readonly commit?: string; - - readonly nameShort: string; - readonly nameLong: string; - - readonly win32AppUserModelId?: string; - readonly win32MutexName?: string; - readonly win32RegValueName?: string; - readonly applicationName: string; - readonly embedderIdentifier?: string; - - readonly urlProtocol: string; - readonly dataFolderName: string; // location for extensions (e.g. ~/.vscode-insiders) - - readonly builtInExtensions?: IBuiltInExtension[]; - readonly walkthroughMetadata?: IProductWalkthrough[]; - readonly featuredExtensions?: IFeaturedExtension[]; - - readonly downloadUrl?: string; - readonly updateUrl?: string; - readonly webUrl?: string; - readonly webEndpointUrlTemplate?: string; - readonly webviewContentExternalBaseUrlTemplate?: string; - readonly target?: string; - readonly nlsCoreBaseUrl?: string; - - readonly settingsSearchBuildId?: number; - readonly settingsSearchUrl?: string; - - readonly tasConfig?: { - endpoint: string; - telemetryEventName: string; - assignmentContextTelemetryPropertyName: string; - }; - - readonly extensionsGallery?: { - readonly serviceUrl: string; - readonly servicePPEUrl?: string; - readonly searchUrl?: string; - readonly itemUrl: string; - readonly publisherUrl: string; - readonly resourceUrlTemplate: string; - readonly controlUrl: string; - readonly nlsBaseUrl: string; - }; - - readonly extensionRecommendations?: IStringDictionary; - readonly configBasedExtensionTips?: IStringDictionary; - readonly exeBasedExtensionTips?: IStringDictionary; - readonly remoteExtensionTips?: IStringDictionary; - readonly virtualWorkspaceExtensionTips?: IStringDictionary; - readonly extensionKeywords?: IStringDictionary; - readonly keymapExtensionTips?: readonly string[]; - readonly webExtensionTips?: readonly string[]; - readonly languageExtensionTips?: readonly string[]; - readonly trustedExtensionUrlPublicKeys?: IStringDictionary; - readonly trustedExtensionAuthAccess?: string[] | IStringDictionary; - readonly trustedExtensionProtocolHandlers?: readonly string[]; - - readonly commandPaletteSuggestedCommandIds?: string[]; - - readonly crashReporter?: { - readonly companyName: string; - readonly productName: string; - }; - - readonly removeTelemetryMachineId?: boolean; - readonly enabledTelemetryLevels?: { error: boolean; usage: boolean }; - readonly enableTelemetry?: boolean; - readonly openToWelcomeMainPage?: boolean; - readonly aiConfig?: { - readonly ariaKey: string; - }; - - readonly documentationUrl?: string; - readonly serverDocumentationUrl?: string; - readonly releaseNotesUrl?: string; - readonly keyboardShortcutsUrlMac?: string; - readonly keyboardShortcutsUrlLinux?: string; - readonly keyboardShortcutsUrlWin?: string; - readonly introductoryVideosUrl?: string; - readonly tipsAndTricksUrl?: string; - readonly newsletterSignupUrl?: string; - readonly youTubeUrl?: string; - readonly requestFeatureUrl?: string; - readonly reportIssueUrl?: string; - readonly reportMarketplaceIssueUrl?: string; - readonly licenseUrl?: string; - readonly serverLicenseUrl?: string; - readonly privacyStatementUrl?: string; - readonly showTelemetryOptOut?: boolean; - - readonly serverGreeting?: string[]; - readonly serverLicense?: string[]; - readonly serverLicensePrompt?: string; - readonly serverApplicationName: string; - readonly serverDataFolderName?: string; - - readonly tunnelApplicationName?: string; - readonly tunnelApplicationConfig?: ITunnelApplicationConfig; - - readonly npsSurveyUrl?: string; - readonly cesSurveyUrl?: string; - readonly surveys?: readonly ISurveyData[]; - - readonly checksums?: { [path: string]: string }; - readonly checksumFailMoreInfoUrl?: string; - - readonly appCenter?: IAppCenterConfiguration; - - readonly portable?: string; - - readonly extensionKind?: { readonly [extensionId: string]: ('ui' | 'workspace' | 'web')[] }; - readonly extensionPointExtensionKind?: { readonly [extensionPointId: string]: ('ui' | 'workspace' | 'web')[] }; - readonly extensionSyncedKeys?: { readonly [extensionId: string]: string[] }; - - readonly extensionsEnabledWithApiProposalVersion?: string[]; - readonly extensionEnabledApiProposals?: { readonly [extensionId: string]: string[] }; - readonly extensionUntrustedWorkspaceSupport?: { readonly [extensionId: string]: ExtensionUntrustedWorkspaceSupport }; - readonly extensionVirtualWorkspacesSupport?: { readonly [extensionId: string]: ExtensionVirtualWorkspaceSupport }; - - readonly msftInternalDomains?: string[]; - readonly linkProtectionTrustedDomains?: readonly string[]; - - readonly 'configurationSync.store'?: ConfigurationSyncStore; - - readonly 'editSessions.store'?: Omit; - readonly darwinUniversalAssetId?: string; - readonly profileTemplatesUrl?: string; - - readonly commonlyUsedSettings?: string[]; - readonly aiGeneratedWorkspaceTrust?: IAiGeneratedWorkspaceTrust; - readonly gitHubEntitlement?: IGitHubEntitlement; - readonly chatWelcomeView?: IChatWelcomeView; - readonly chatParticipantRegistry?: string; -} - -export interface ITunnelApplicationConfig { - authenticationProviders: IStringDictionary<{ scopes: string[] }>; - editorWebUrl: string; - extension: IRemoteExtensionTip; -} - -export interface IExtensionRecommendations { - readonly onFileOpen: IFileOpenCondition[]; - readonly onSettingsEditorOpen?: ISettingsEditorOpenCondition; -} - -export interface ISettingsEditorOpenCondition { - readonly prerelease?: boolean | string; -} - -export interface IExtensionRecommendationCondition { - readonly important?: boolean; - readonly whenInstalled?: string[]; - readonly whenNotInstalled?: string[]; -} - -export type IFileOpenCondition = IFileLanguageCondition | IFilePathCondition | IFileContentCondition; - -export interface IFileLanguageCondition extends IExtensionRecommendationCondition { - readonly languages: string[]; -} - -export interface IFilePathCondition extends IExtensionRecommendationCondition { - readonly pathGlob: string; -} - -export type IFileContentCondition = (IFileLanguageCondition | IFilePathCondition) & { readonly contentPattern: string }; - -export interface IAppCenterConfiguration { - readonly 'win32-x64': string; - readonly 'win32-arm64': string; - readonly 'linux-x64': string; - readonly 'darwin': string; - readonly 'darwin-universal': string; - readonly 'darwin-arm64': string; -} - -export interface IConfigBasedExtensionTip { - configPath: string; - configName: string; - configScheme?: string; - recommendations: IStringDictionary<{ - name: string; - contentPattern?: string; - important?: boolean; - isExtensionPack?: boolean; - whenNotInstalled?: string[]; - }>; -} - -export interface IExeBasedExtensionTip { - friendlyName: string; - windowsPath?: string; - important?: boolean; - recommendations: IStringDictionary<{ name: string; important?: boolean; isExtensionPack?: boolean; whenNotInstalled?: string[] }>; -} - -export interface IRemoteExtensionTip { - friendlyName: string; - extensionId: string; - supportedPlatforms?: PlatformName[]; - startEntry?: { - helpLink: string; - startConnectLabel: string; - startCommand: string; - priority: number; - }; -} - -export interface IVirtualWorkspaceExtensionTip { - friendlyName: string; - extensionId: string; - supportedPlatforms?: PlatformName[]; - startEntry: { - helpLink: string; - startConnectLabel: string; - startCommand: string; - priority: number; - }; -} - -export interface ISurveyData { - surveyId: string; - surveyUrl: string; - languageId: string; - editCount: number; - userProbability: number; -} - -export interface IAiGeneratedWorkspaceTrust { - readonly title: string; - readonly checkboxText: string; - readonly trustOption: string; - readonly dontTrustOption: string; - readonly startupTrustRequestLearnMore: string; -} - -export interface IGitHubEntitlement { - providerId: string; - command: { title: string; titleWithoutPlaceHolder: string; action: string; when: string }; - entitlementUrl: string; - extensionId: string; - enablementKey: string; - confirmationMessage: string; - confirmationAction: string; -} - -export interface IChatWelcomeView { - welcomeViewId: string; - welcomeViewTitle: string; - welcomeViewContent: string; -} diff --git a/src/vs/base/common/range.ts b/src/vs/base/common/range.ts deleted file mode 100644 index 93a1a269..00000000 --- a/src/vs/base/common/range.ts +++ /dev/null @@ -1,60 +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 IRange { - start: number; - end: number; -} - -export interface IRangedGroup { - range: IRange; - size: number; -} - -export namespace Range { - - /** - * Returns the intersection between two ranges as a range itself. - * Returns `{ start: 0, end: 0 }` if the intersection is empty. - */ - export function intersect(one: IRange, other: IRange): IRange { - if (one.start >= other.end || other.start >= one.end) { - return { start: 0, end: 0 }; - } - - const start = Math.max(one.start, other.start); - const end = Math.min(one.end, other.end); - - if (end - start <= 0) { - return { start: 0, end: 0 }; - } - - return { start, end }; - } - - export function isEmpty(range: IRange): boolean { - return range.end - range.start <= 0; - } - - export function intersects(one: IRange, other: IRange): boolean { - return !isEmpty(intersect(one, other)); - } - - export function relativeComplement(one: IRange, other: IRange): IRange[] { - const result: IRange[] = []; - const first = { start: one.start, end: Math.min(other.start, one.end) }; - const second = { start: Math.max(other.end, one.start), end: one.end }; - - if (!isEmpty(first)) { - result.push(first); - } - - if (!isEmpty(second)) { - result.push(second); - } - - return result; - } -} diff --git a/src/vs/base/common/search.ts b/src/vs/base/common/search.ts deleted file mode 100644 index 24c5bf79..00000000 --- a/src/vs/base/common/search.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as strings from './strings'; - -export function buildReplaceStringWithCasePreserved(matches: string[] | null, pattern: string): string { - if (matches && (matches[0] !== '')) { - const containsHyphens = validateSpecificSpecialCharacter(matches, pattern, '-'); - const containsUnderscores = validateSpecificSpecialCharacter(matches, pattern, '_'); - if (containsHyphens && !containsUnderscores) { - return buildReplaceStringForSpecificSpecialCharacter(matches, pattern, '-'); - } else if (!containsHyphens && containsUnderscores) { - return buildReplaceStringForSpecificSpecialCharacter(matches, pattern, '_'); - } - if (matches[0].toUpperCase() === matches[0]) { - return pattern.toUpperCase(); - } else if (matches[0].toLowerCase() === matches[0]) { - return pattern.toLowerCase(); - } else if (strings.containsUppercaseCharacter(matches[0][0]) && pattern.length > 0) { - return pattern[0].toUpperCase() + pattern.substr(1); - } else if (matches[0][0].toUpperCase() !== matches[0][0] && pattern.length > 0) { - return pattern[0].toLowerCase() + pattern.substr(1); - } else { - // we don't understand its pattern yet. - return pattern; - } - } else { - return pattern; - } -} - -function validateSpecificSpecialCharacter(matches: string[], pattern: string, specialCharacter: string): boolean { - const doesContainSpecialCharacter = matches[0].indexOf(specialCharacter) !== -1 && pattern.indexOf(specialCharacter) !== -1; - return doesContainSpecialCharacter && matches[0].split(specialCharacter).length === pattern.split(specialCharacter).length; -} - -function buildReplaceStringForSpecificSpecialCharacter(matches: string[], pattern: string, specialCharacter: string): string { - const splitPatternAtSpecialCharacter = pattern.split(specialCharacter); - const splitMatchAtSpecialCharacter = matches[0].split(specialCharacter); - let replaceString: string = ''; - splitPatternAtSpecialCharacter.forEach((splitValue, index) => { - replaceString += buildReplaceStringWithCasePreserved([splitMatchAtSpecialCharacter[index]], splitValue) + specialCharacter; - }); - - return replaceString.slice(0, -1); -} diff --git a/src/vs/base/common/severity.ts b/src/vs/base/common/severity.ts deleted file mode 100644 index 83e753d8..00000000 --- a/src/vs/base/common/severity.ts +++ /dev/null @@ -1,56 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as strings from 'vs/base/common/strings'; - -enum Severity { - Ignore = 0, - Info = 1, - Warning = 2, - Error = 3 -} - -namespace Severity { - - const _error = 'error'; - const _warning = 'warning'; - const _warn = 'warn'; - const _info = 'info'; - const _ignore = 'ignore'; - - /** - * Parses 'error', 'warning', 'warn', 'info' in call casings - * and falls back to ignore. - */ - export function fromValue(value: string): Severity { - if (!value) { - return Severity.Ignore; - } - - if (strings.equalsIgnoreCase(_error, value)) { - return Severity.Error; - } - - if (strings.equalsIgnoreCase(_warning, value) || strings.equalsIgnoreCase(_warn, value)) { - return Severity.Warning; - } - - if (strings.equalsIgnoreCase(_info, value)) { - return Severity.Info; - } - return Severity.Ignore; - } - - export function toString(severity: Severity): string { - switch (severity) { - case Severity.Error: return _error; - case Severity.Warning: return _warning; - case Severity.Info: return _info; - default: return _ignore; - } - } -} - -export default Severity; diff --git a/src/vs/base/common/skipList.ts b/src/vs/base/common/skipList.ts deleted file mode 100644 index ed3fd7e5..00000000 --- a/src/vs/base/common/skipList.ts +++ /dev/null @@ -1,204 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - - -class Node { - readonly forward: Node[]; - constructor(readonly level: number, readonly key: K, public value: V) { - this.forward = []; - } -} - -const NIL: undefined = undefined; - -interface Comparator { - (a: K, b: K): number; -} - -export class SkipList implements Map { - - readonly [Symbol.toStringTag] = 'SkipList'; - - private _maxLevel: number; - private _level: number = 0; - private _header: Node; - private _size: number = 0; - - /** - * - * @param capacity Capacity at which the list performs best - */ - constructor( - readonly comparator: (a: K, b: K) => number, - capacity: number = 2 ** 16 - ) { - this._maxLevel = Math.max(1, Math.log2(capacity) | 0); - this._header = new Node(this._maxLevel, NIL, NIL); - } - - get size(): number { - return this._size; - } - - clear(): void { - this._header = new Node(this._maxLevel, NIL, NIL); - this._size = 0; - } - - has(key: K): boolean { - return Boolean(SkipList._search(this, key, this.comparator)); - } - - get(key: K): V | undefined { - return SkipList._search(this, key, this.comparator)?.value; - } - - set(key: K, value: V): this { - if (SkipList._insert(this, key, value, this.comparator)) { - this._size += 1; - } - return this; - } - - delete(key: K): boolean { - const didDelete = SkipList._delete(this, key, this.comparator); - if (didDelete) { - this._size -= 1; - } - return didDelete; - } - - // --- iteration - - forEach(callbackfn: (value: V, key: K, map: Map) => void, thisArg?: any): void { - let node = this._header.forward[0]; - while (node) { - callbackfn.call(thisArg, node.value, node.key, this); - node = node.forward[0]; - } - } - - [Symbol.iterator](): IterableIterator<[K, V]> { - return this.entries(); - } - - *entries(): IterableIterator<[K, V]> { - let node = this._header.forward[0]; - while (node) { - yield [node.key, node.value]; - node = node.forward[0]; - } - } - - *keys(): IterableIterator { - let node = this._header.forward[0]; - while (node) { - yield node.key; - node = node.forward[0]; - } - } - - *values(): IterableIterator { - let node = this._header.forward[0]; - while (node) { - yield node.value; - node = node.forward[0]; - } - } - - toString(): string { - // debug string... - let result = '[SkipList]:'; - let node = this._header.forward[0]; - while (node) { - result += `node(${node.key}, ${node.value}, lvl:${node.level})`; - node = node.forward[0]; - } - return result; - } - - // from https://www.epaperpress.com/sortsearch/download/skiplist.pdf - - private static _search(list: SkipList, searchKey: K, comparator: Comparator) { - let x = list._header; - for (let i = list._level - 1; i >= 0; i--) { - while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) { - x = x.forward[i]; - } - } - x = x.forward[0]; - if (x && comparator(x.key, searchKey) === 0) { - return x; - } - return undefined; - } - - private static _insert(list: SkipList, searchKey: K, value: V, comparator: Comparator) { - const update: Node[] = []; - let x = list._header; - for (let i = list._level - 1; i >= 0; i--) { - while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) { - x = x.forward[i]; - } - update[i] = x; - } - x = x.forward[0]; - if (x && comparator(x.key, searchKey) === 0) { - // update - x.value = value; - return false; - } else { - // insert - const lvl = SkipList._randomLevel(list); - if (lvl > list._level) { - for (let i = list._level; i < lvl; i++) { - update[i] = list._header; - } - list._level = lvl; - } - x = new Node(lvl, searchKey, value); - for (let i = 0; i < lvl; i++) { - x.forward[i] = update[i].forward[i]; - update[i].forward[i] = x; - } - return true; - } - } - - private static _randomLevel(list: SkipList, p: number = 0.5): number { - let lvl = 1; - while (Math.random() < p && lvl < list._maxLevel) { - lvl += 1; - } - return lvl; - } - - private static _delete(list: SkipList, searchKey: K, comparator: Comparator) { - const update: Node[] = []; - let x = list._header; - for (let i = list._level - 1; i >= 0; i--) { - while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) { - x = x.forward[i]; - } - update[i] = x; - } - x = x.forward[0]; - if (!x || comparator(x.key, searchKey) !== 0) { - // not found - return false; - } - for (let i = 0; i < list._level; i++) { - if (update[i].forward[i] !== x) { - break; - } - update[i].forward[i] = x.forward[i]; - } - while (list._level > 0 && list._header.forward[list._level - 1] === NIL) { - list._level -= 1; - } - return true; - } - -} diff --git a/src/vs/base/common/stream.ts b/src/vs/base/common/stream.ts deleted file mode 100644 index d6cd674b..00000000 --- a/src/vs/base/common/stream.ts +++ /dev/null @@ -1,772 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CancellationToken } from 'vs/base/common/cancellation'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; - -/** - * The payload that flows in readable stream events. - */ -export type ReadableStreamEventPayload = T | Error | 'end'; - -export interface ReadableStreamEvents { - - /** - * The 'data' event is emitted whenever the stream is - * relinquishing ownership of a chunk of data to a consumer. - * - * NOTE: PLEASE UNDERSTAND THAT ADDING A DATA LISTENER CAN - * TURN THE STREAM INTO FLOWING MODE. IT IS THEREFOR THE - * LAST LISTENER THAT SHOULD BE ADDED AND NOT THE FIRST - * - * Use `listenStream` as a helper method to listen to - * stream events in the right order. - */ - on(event: 'data', callback: (data: T) => void): void; - - /** - * Emitted when any error occurs. - */ - on(event: 'error', callback: (err: Error) => void): void; - - /** - * The 'end' event is emitted when there is no more data - * to be consumed from the stream. The 'end' event will - * not be emitted unless the data is completely consumed. - */ - on(event: 'end', callback: () => void): void; -} - -/** - * A interface that emulates the API shape of a node.js readable - * stream for use in native and web environments. - */ -export interface ReadableStream extends ReadableStreamEvents { - - /** - * Stops emitting any events until resume() is called. - */ - pause(): void; - - /** - * Starts emitting events again after pause() was called. - */ - resume(): void; - - /** - * Destroys the stream and stops emitting any event. - */ - destroy(): void; - - /** - * Allows to remove a listener that was previously added. - */ - removeListener(event: string, callback: Function): void; -} - -/** - * A interface that emulates the API shape of a node.js readable - * for use in native and web environments. - */ -export interface Readable { - - /** - * Read data from the underlying source. Will return - * null to indicate that no more data can be read. - */ - read(): T | null; -} - -export function isReadable(obj: unknown): obj is Readable { - const candidate = obj as Readable | undefined; - if (!candidate) { - return false; - } - - return typeof candidate.read === 'function'; -} - -/** - * A interface that emulates the API shape of a node.js writeable - * stream for use in native and web environments. - */ -export interface WriteableStream extends ReadableStream { - - /** - * Writing data to the stream will trigger the on('data') - * event listener if the stream is flowing and buffer the - * data otherwise until the stream is flowing. - * - * If a `highWaterMark` is configured and writing to the - * stream reaches this mark, a promise will be returned - * that should be awaited on before writing more data. - * Otherwise there is a risk of buffering a large number - * of data chunks without consumer. - */ - write(data: T): void | Promise; - - /** - * Signals an error to the consumer of the stream via the - * on('error') handler if the stream is flowing. - * - * NOTE: call `end` to signal that the stream has ended, - * this DOES NOT happen automatically from `error`. - */ - error(error: Error): void; - - /** - * Signals the end of the stream to the consumer. If the - * result is provided, will trigger the on('data') event - * listener if the stream is flowing and buffer the data - * otherwise until the stream is flowing. - */ - end(result?: T): void; -} - -/** - * A stream that has a buffer already read. Returns the original stream - * that was read as well as the chunks that got read. - * - * The `ended` flag indicates if the stream has been fully consumed. - */ -export interface ReadableBufferedStream { - - /** - * The original stream that is being read. - */ - stream: ReadableStream; - - /** - * An array of chunks already read from this stream. - */ - buffer: T[]; - - /** - * Signals if the stream has ended or not. If not, consumers - * should continue to read from the stream until consumed. - */ - ended: boolean; -} - -export function isReadableStream(obj: unknown): obj is ReadableStream { - const candidate = obj as ReadableStream | undefined; - if (!candidate) { - return false; - } - - return [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function'); -} - -export function isReadableBufferedStream(obj: unknown): obj is ReadableBufferedStream { - const candidate = obj as ReadableBufferedStream | undefined; - if (!candidate) { - return false; - } - - return isReadableStream(candidate.stream) && Array.isArray(candidate.buffer) && typeof candidate.ended === 'boolean'; -} - -export interface IReducer { - (data: T[]): R; -} - -export interface IDataTransformer { - (data: Original): Transformed; -} - -export interface IErrorTransformer { - (error: Error): Error; -} - -export interface ITransformer { - data: IDataTransformer; - error?: IErrorTransformer; -} - -export function newWriteableStream(reducer: IReducer, options?: WriteableStreamOptions): WriteableStream { - return new WriteableStreamImpl(reducer, options); -} - -export interface WriteableStreamOptions { - - /** - * The number of objects to buffer before WriteableStream#write() - * signals back that the buffer is full. Can be used to reduce - * the memory pressure when the stream is not flowing. - */ - highWaterMark?: number; -} - -class WriteableStreamImpl implements WriteableStream { - - private readonly state = { - flowing: false, - ended: false, - destroyed: false - }; - - private readonly buffer = { - data: [] as T[], - error: [] as Error[] - }; - - private readonly listeners = { - data: [] as { (data: T): void }[], - error: [] as { (error: Error): void }[], - end: [] as { (): void }[] - }; - - private readonly pendingWritePromises: Function[] = []; - - constructor(private reducer: IReducer, private options?: WriteableStreamOptions) { } - - pause(): void { - if (this.state.destroyed) { - return; - } - - this.state.flowing = false; - } - - resume(): void { - if (this.state.destroyed) { - return; - } - - if (!this.state.flowing) { - this.state.flowing = true; - - // emit buffered events - this.flowData(); - this.flowErrors(); - this.flowEnd(); - } - } - - write(data: T): void | Promise { - if (this.state.destroyed) { - return; - } - - // flowing: directly send the data to listeners - if (this.state.flowing) { - this.emitData(data); - } - - // not yet flowing: buffer data until flowing - else { - this.buffer.data.push(data); - - // highWaterMark: if configured, signal back when buffer reached limits - if (typeof this.options?.highWaterMark === 'number' && this.buffer.data.length > this.options.highWaterMark) { - return new Promise(resolve => this.pendingWritePromises.push(resolve)); - } - } - } - - error(error: Error): void { - if (this.state.destroyed) { - return; - } - - // flowing: directly send the error to listeners - if (this.state.flowing) { - this.emitError(error); - } - - // not yet flowing: buffer errors until flowing - else { - this.buffer.error.push(error); - } - } - - end(result?: T): void { - if (this.state.destroyed) { - return; - } - - // end with data if provided - if (typeof result !== 'undefined') { - this.write(result); - } - - // flowing: send end event to listeners - if (this.state.flowing) { - this.emitEnd(); - - this.destroy(); - } - - // not yet flowing: remember state - else { - this.state.ended = true; - } - } - - private emitData(data: T): void { - this.listeners.data.slice(0).forEach(listener => listener(data)); // slice to avoid listener mutation from delivering event - } - - private emitError(error: Error): void { - if (this.listeners.error.length === 0) { - onUnexpectedError(error); // nobody listened to this error so we log it as unexpected - } else { - this.listeners.error.slice(0).forEach(listener => listener(error)); // slice to avoid listener mutation from delivering event - } - } - - private emitEnd(): void { - this.listeners.end.slice(0).forEach(listener => listener()); // slice to avoid listener mutation from delivering event - } - - on(event: 'data', callback: (data: T) => void): void; - on(event: 'error', callback: (err: Error) => void): void; - on(event: 'end', callback: () => void): void; - on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void { - if (this.state.destroyed) { - return; - } - - switch (event) { - case 'data': - this.listeners.data.push(callback); - - // switch into flowing mode as soon as the first 'data' - // listener is added and we are not yet in flowing mode - this.resume(); - - break; - - case 'end': - this.listeners.end.push(callback); - - // emit 'end' event directly if we are flowing - // and the end has already been reached - // - // finish() when it went through - if (this.state.flowing && this.flowEnd()) { - this.destroy(); - } - - break; - - case 'error': - this.listeners.error.push(callback); - - // emit buffered 'error' events unless done already - // now that we know that we have at least one listener - if (this.state.flowing) { - this.flowErrors(); - } - - break; - } - } - - removeListener(event: string, callback: Function): void { - if (this.state.destroyed) { - return; - } - - let listeners: unknown[] | undefined = undefined; - - switch (event) { - case 'data': - listeners = this.listeners.data; - break; - - case 'end': - listeners = this.listeners.end; - break; - - case 'error': - listeners = this.listeners.error; - break; - } - - if (listeners) { - const index = listeners.indexOf(callback); - if (index >= 0) { - listeners.splice(index, 1); - } - } - } - - private flowData(): void { - if (this.buffer.data.length > 0) { - const fullDataBuffer = this.reducer(this.buffer.data); - - this.emitData(fullDataBuffer); - - this.buffer.data.length = 0; - - // When the buffer is empty, resolve all pending writers - const pendingWritePromises = [...this.pendingWritePromises]; - this.pendingWritePromises.length = 0; - pendingWritePromises.forEach(pendingWritePromise => pendingWritePromise()); - } - } - - private flowErrors(): void { - if (this.listeners.error.length > 0) { - for (const error of this.buffer.error) { - this.emitError(error); - } - - this.buffer.error.length = 0; - } - } - - private flowEnd(): boolean { - if (this.state.ended) { - this.emitEnd(); - - return this.listeners.end.length > 0; - } - - return false; - } - - destroy(): void { - if (!this.state.destroyed) { - this.state.destroyed = true; - this.state.ended = true; - - this.buffer.data.length = 0; - this.buffer.error.length = 0; - - this.listeners.data.length = 0; - this.listeners.error.length = 0; - this.listeners.end.length = 0; - - this.pendingWritePromises.length = 0; - } - } -} - -/** - * Helper to fully read a T readable into a T. - */ -export function consumeReadable(readable: Readable, reducer: IReducer): T { - const chunks: T[] = []; - - let chunk: T | null; - while ((chunk = readable.read()) !== null) { - chunks.push(chunk); - } - - return reducer(chunks); -} - -/** - * Helper to read a T readable up to a maximum of chunks. If the limit is - * reached, will return a readable instead to ensure all data can still - * be read. - */ -export function peekReadable(readable: Readable, reducer: IReducer, maxChunks: number): T | Readable { - const chunks: T[] = []; - - let chunk: T | null | undefined = undefined; - while ((chunk = readable.read()) !== null && chunks.length < maxChunks) { - chunks.push(chunk); - } - - // If the last chunk is null, it means we reached the end of - // the readable and return all the data at once - if (chunk === null && chunks.length > 0) { - return reducer(chunks); - } - - // Otherwise, we still have a chunk, it means we reached the maxChunks - // value and as such we return a new Readable that first returns - // the existing read chunks and then continues with reading from - // the underlying readable. - return { - read: () => { - - // First consume chunks from our array - if (chunks.length > 0) { - return chunks.shift()!; - } - - // Then ensure to return our last read chunk - if (typeof chunk !== 'undefined') { - const lastReadChunk = chunk; - - // explicitly use undefined here to indicate that we consumed - // the chunk, which could have either been null or valued. - chunk = undefined; - - return lastReadChunk; - } - - // Finally delegate back to the Readable - return readable.read(); - } - }; -} - -/** - * Helper to fully read a T stream into a T or consuming - * a stream fully, awaiting all the events without caring - * about the data. - */ -export function consumeStream(stream: ReadableStreamEvents, reducer: IReducer): Promise; -export function consumeStream(stream: ReadableStreamEvents): Promise; -export function consumeStream(stream: ReadableStreamEvents, reducer?: IReducer): Promise { - return new Promise((resolve, reject) => { - const chunks: T[] = []; - - listenStream(stream, { - onData: chunk => { - if (reducer) { - chunks.push(chunk); - } - }, - onError: error => { - if (reducer) { - reject(error); - } else { - resolve(undefined); - } - }, - onEnd: () => { - if (reducer) { - resolve(reducer(chunks)); - } else { - resolve(undefined); - } - } - }); - }); -} - -export interface IStreamListener { - - /** - * The 'data' event is emitted whenever the stream is - * relinquishing ownership of a chunk of data to a consumer. - */ - onData(data: T): void; - - /** - * Emitted when any error occurs. - */ - onError(err: Error): void; - - /** - * The 'end' event is emitted when there is no more data - * to be consumed from the stream. The 'end' event will - * not be emitted unless the data is completely consumed. - */ - onEnd(): void; -} - -/** - * Helper to listen to all events of a T stream in proper order. - */ -export function listenStream(stream: ReadableStreamEvents, listener: IStreamListener, token?: CancellationToken): void { - - stream.on('error', error => { - if (!token?.isCancellationRequested) { - listener.onError(error); - } - }); - - stream.on('end', () => { - if (!token?.isCancellationRequested) { - listener.onEnd(); - } - }); - - // Adding the `data` listener will turn the stream - // into flowing mode. As such it is important to - // add this listener last (DO NOT CHANGE!) - stream.on('data', data => { - if (!token?.isCancellationRequested) { - listener.onData(data); - } - }); -} - -/** - * Helper to peek up to `maxChunks` into a stream. The return type signals if - * the stream has ended or not. If not, caller needs to add a `data` listener - * to continue reading. - */ -export function peekStream(stream: ReadableStream, maxChunks: number): Promise> { - return new Promise((resolve, reject) => { - const streamListeners = new DisposableStore(); - const buffer: T[] = []; - - // Data Listener - const dataListener = (chunk: T) => { - - // Add to buffer - buffer.push(chunk); - - // We reached maxChunks and thus need to return - if (buffer.length > maxChunks) { - - // Dispose any listeners and ensure to pause the - // stream so that it can be consumed again by caller - streamListeners.dispose(); - stream.pause(); - - return resolve({ stream, buffer, ended: false }); - } - }; - - // Error Listener - const errorListener = (error: Error) => { - streamListeners.dispose(); - - return reject(error); - }; - - // End Listener - const endListener = () => { - streamListeners.dispose(); - - return resolve({ stream, buffer, ended: true }); - }; - - streamListeners.add(toDisposable(() => stream.removeListener('error', errorListener))); - stream.on('error', errorListener); - - streamListeners.add(toDisposable(() => stream.removeListener('end', endListener))); - stream.on('end', endListener); - - // Important: leave the `data` listener last because - // this can turn the stream into flowing mode and we - // want `error` events to be received as well. - streamListeners.add(toDisposable(() => stream.removeListener('data', dataListener))); - stream.on('data', dataListener); - }); -} - -/** - * Helper to create a readable stream from an existing T. - */ -export function toStream(t: T, reducer: IReducer): ReadableStream { - const stream = newWriteableStream(reducer); - - stream.end(t); - - return stream; -} - -/** - * Helper to create an empty stream - */ -export function emptyStream(): ReadableStream { - const stream = newWriteableStream(() => { throw new Error('not supported'); }); - stream.end(); - - return stream; -} - -/** - * Helper to convert a T into a Readable. - */ -export function toReadable(t: T): Readable { - let consumed = false; - - return { - read: () => { - if (consumed) { - return null; - } - - consumed = true; - - return t; - } - }; -} - -/** - * Helper to transform a readable stream into another stream. - */ -export function transform(stream: ReadableStreamEvents, transformer: ITransformer, reducer: IReducer): ReadableStream { - const target = newWriteableStream(reducer); - - listenStream(stream, { - onData: data => target.write(transformer.data(data)), - onError: error => target.error(transformer.error ? transformer.error(error) : error), - onEnd: () => target.end() - }); - - return target; -} - -/** - * Helper to take an existing readable that will - * have a prefix injected to the beginning. - */ -export function prefixedReadable(prefix: T, readable: Readable, reducer: IReducer): Readable { - let prefixHandled = false; - - return { - read: () => { - const chunk = readable.read(); - - // Handle prefix only once - if (!prefixHandled) { - prefixHandled = true; - - // If we have also a read-result, make - // sure to reduce it to a single result - if (chunk !== null) { - return reducer([prefix, chunk]); - } - - // Otherwise, just return prefix directly - return prefix; - } - - return chunk; - } - }; -} - -/** - * Helper to take an existing stream that will - * have a prefix injected to the beginning. - */ -export function prefixedStream(prefix: T, stream: ReadableStream, reducer: IReducer): ReadableStream { - let prefixHandled = false; - - const target = newWriteableStream(reducer); - - listenStream(stream, { - onData: data => { - - // Handle prefix only once - if (!prefixHandled) { - prefixHandled = true; - - return target.write(reducer([prefix, data])); - } - - return target.write(data); - }, - onError: error => target.error(error), - onEnd: () => { - - // Handle prefix only once - if (!prefixHandled) { - prefixHandled = true; - - target.write(prefix); - } - - target.end(); - } - }); - - return target; -} diff --git a/src/vs/base/common/tfIdf.ts b/src/vs/base/common/tfIdf.ts deleted file mode 100644 index 45042759..00000000 --- a/src/vs/base/common/tfIdf.ts +++ /dev/null @@ -1,240 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CancellationToken } from 'vs/base/common/cancellation'; - -type SparseEmbedding = Record; -type TermFrequencies = Map; -type DocumentOccurrences = Map; - -function countMapFrom(values: Iterable): Map { - const map = new Map(); - for (const value of values) { - map.set(value, (map.get(value) ?? 0) + 1); - } - return map; -} - -interface DocumentChunkEntry { - readonly text: string; - readonly tf: TermFrequencies; -} - -export interface TfIdfDocument { - readonly key: string; - readonly textChunks: readonly string[]; -} - -export interface TfIdfScore { - readonly key: string; - /** - * An unbounded number. - */ - readonly score: number; -} - -export interface NormalizedTfIdfScore { - readonly key: string; - /** - * A number between 0 and 1. - */ - readonly score: number; -} - -/** - * Implementation of tf-idf (term frequency-inverse document frequency) for a set of - * documents where each document contains one or more chunks of text. - * Each document is identified by a key, and the score for each document is computed - * by taking the max score over all the chunks in the document. - */ -export class TfIdfCalculator { - calculateScores(query: string, token: CancellationToken): TfIdfScore[] { - const embedding = this.computeEmbedding(query); - const idfCache = new Map(); - const scores: TfIdfScore[] = []; - // For each document, generate one score - for (const [key, doc] of this.documents) { - if (token.isCancellationRequested) { - return []; - } - - for (const chunk of doc.chunks) { - const score = this.computeSimilarityScore(chunk, embedding, idfCache); - if (score > 0) { - scores.push({ key, score }); - } - } - } - - return scores; - } - - /** - * Count how many times each term (word) appears in a string. - */ - private static termFrequencies(input: string): TermFrequencies { - return countMapFrom(TfIdfCalculator.splitTerms(input)); - } - - /** - * Break a string into terms (words). - */ - private static *splitTerms(input: string): Iterable { - const normalize = (word: string) => word.toLowerCase(); - - // Only match on words that are at least 3 characters long and start with a letter - for (const [word] of input.matchAll(/\b\p{Letter}[\p{Letter}\d]{2,}\b/gu)) { - yield normalize(word); - - const camelParts = word.replace(/([a-z])([A-Z])/g, '$1 $2').split(/\s+/g); - if (camelParts.length > 1) { - for (const part of camelParts) { - // Require at least 3 letters in the parts of a camel case word - if (part.length > 2 && /\p{Letter}{3,}/gu.test(part)) { - yield normalize(part); - } - } - } - } - } - - /** - * Total number of chunks - */ - private chunkCount = 0; - - private readonly chunkOccurrences: DocumentOccurrences = new Map(); - - private readonly documents = new Map; - }>(); - - updateDocuments(documents: ReadonlyArray): this { - for (const { key } of documents) { - this.deleteDocument(key); - } - - for (const doc of documents) { - const chunks: Array<{ text: string; tf: TermFrequencies }> = []; - for (const text of doc.textChunks) { - // TODO: See if we can compute the tf lazily - // The challenge is that we need to also update the `chunkOccurrences` - // and all of those updates need to get flushed before the real TF-IDF of - // anything is computed. - const tf = TfIdfCalculator.termFrequencies(text); - - // Update occurrences list - for (const term of tf.keys()) { - this.chunkOccurrences.set(term, (this.chunkOccurrences.get(term) ?? 0) + 1); - } - - chunks.push({ text, tf }); - } - - this.chunkCount += chunks.length; - this.documents.set(doc.key, { chunks }); - } - return this; - } - - deleteDocument(key: string) { - const doc = this.documents.get(key); - if (!doc) { - return; - } - - this.documents.delete(key); - this.chunkCount -= doc.chunks.length; - - // Update term occurrences for the document - for (const chunk of doc.chunks) { - for (const term of chunk.tf.keys()) { - const currentOccurrences = this.chunkOccurrences.get(term); - if (typeof currentOccurrences === 'number') { - const newOccurrences = currentOccurrences - 1; - if (newOccurrences <= 0) { - this.chunkOccurrences.delete(term); - } else { - this.chunkOccurrences.set(term, newOccurrences); - } - } - } - } - } - - private computeSimilarityScore(chunk: DocumentChunkEntry, queryEmbedding: SparseEmbedding, idfCache: Map): number { - // Compute the dot product between the chunk's embedding and the query embedding - - // Note that the chunk embedding is computed lazily on a per-term basis. - // This lets us skip a large number of calculations because the majority - // of chunks do not share any terms with the query. - - let sum = 0; - for (const [term, termTfidf] of Object.entries(queryEmbedding)) { - const chunkTf = chunk.tf.get(term); - if (!chunkTf) { - // Term does not appear in chunk so it has no contribution - continue; - } - - let chunkIdf = idfCache.get(term); - if (typeof chunkIdf !== 'number') { - chunkIdf = this.computeIdf(term); - idfCache.set(term, chunkIdf); - } - - const chunkTfidf = chunkTf * chunkIdf; - sum += chunkTfidf * termTfidf; - } - return sum; - } - - private computeEmbedding(input: string): SparseEmbedding { - const tf = TfIdfCalculator.termFrequencies(input); - return this.computeTfidf(tf); - } - - private computeIdf(term: string): number { - const chunkOccurrences = this.chunkOccurrences.get(term) ?? 0; - return chunkOccurrences > 0 - ? Math.log((this.chunkCount + 1) / chunkOccurrences) - : 0; - } - - private computeTfidf(termFrequencies: TermFrequencies): SparseEmbedding { - const embedding = Object.create(null); - for (const [word, occurrences] of termFrequencies) { - const idf = this.computeIdf(word); - if (idf > 0) { - embedding[word] = occurrences * idf; - } - } - return embedding; - } -} - -/** - * Normalize the scores to be between 0 and 1 and sort them decending. - * @param scores array of scores from {@link TfIdfCalculator.calculateScores} - * @returns normalized scores - */ -export function normalizeTfIdfScores(scores: TfIdfScore[]): NormalizedTfIdfScore[] { - - // copy of scores - const result = scores.slice(0) as { score: number }[]; - - // sort descending - result.sort((a, b) => b.score - a.score); - - // normalize - const max = result[0]?.score ?? 0; - if (max > 0) { - for (const score of result) { - score.score /= max; - } - } - - return result as TfIdfScore[]; -} diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts deleted file mode 100644 index 1acab57b..00000000 --- a/src/vs/base/common/types.ts +++ /dev/null @@ -1,250 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * @returns whether the provided parameter is a JavaScript String or not. - */ -export function isString(str: unknown): str is string { - return (typeof str === 'string'); -} - -/** - * @returns whether the provided parameter is a JavaScript Array and each element in the array is a string. - */ -export function isStringArray(value: unknown): value is string[] { - return Array.isArray(value) && (value).every(elem => isString(elem)); -} - -/** - * @returns whether the provided parameter is of type `object` but **not** - * `null`, an `array`, a `regexp`, nor a `date`. - */ -export function isObject(obj: unknown): obj is Object { - // The method can't do a type cast since there are type (like strings) which - // are subclasses of any put not positvely matched by the function. Hence type - // narrowing results in wrong results. - return typeof obj === 'object' - && obj !== null - && !Array.isArray(obj) - && !(obj instanceof RegExp) - && !(obj instanceof Date); -} - -/** - * @returns whether the provided parameter is of type `Buffer` or Uint8Array dervived type - */ -export function isTypedArray(obj: unknown): obj is Object { - const TypedArray = Object.getPrototypeOf(Uint8Array); - return typeof obj === 'object' - && obj instanceof TypedArray; -} - -/** - * In **contrast** to just checking `typeof` this will return `false` for `NaN`. - * @returns whether the provided parameter is a JavaScript Number or not. - */ -export function isNumber(obj: unknown): obj is number { - return (typeof obj === 'number' && !isNaN(obj)); -} - -/** - * @returns whether the provided parameter is an Iterable, casting to the given generic - */ -export function isIterable(obj: unknown): obj is Iterable { - return !!obj && typeof (obj as any)[Symbol.iterator] === 'function'; -} - -/** - * @returns whether the provided parameter is a JavaScript Boolean or not. - */ -export function isBoolean(obj: unknown): obj is boolean { - return (obj === true || obj === false); -} - -/** - * @returns whether the provided parameter is undefined. - */ -export function isUndefined(obj: unknown): obj is undefined { - return (typeof obj === 'undefined'); -} - -/** - * @returns whether the provided parameter is defined. - */ -export function isDefined(arg: T | null | undefined): arg is T { - return !isUndefinedOrNull(arg); -} - -/** - * @returns whether the provided parameter is undefined or null. - */ -export function isUndefinedOrNull(obj: unknown): obj is undefined | null { - return (isUndefined(obj) || obj === null); -} - - -export function assertType(condition: unknown, type?: string): asserts condition { - if (!condition) { - throw new Error(type ? `Unexpected type, expected '${type}'` : 'Unexpected type'); - } -} - -/** - * Asserts that the argument passed in is neither undefined nor null. - */ -export function assertIsDefined(arg: T | null | undefined): T { - if (isUndefinedOrNull(arg)) { - throw new Error('Assertion Failed: argument is undefined or null'); - } - - return arg; -} - -/** - * Asserts that each argument passed in is neither undefined nor null. - */ -export function assertAllDefined(t1: T1 | null | undefined, t2: T2 | null | undefined): [T1, T2]; -export function assertAllDefined(t1: T1 | null | undefined, t2: T2 | null | undefined, t3: T3 | null | undefined): [T1, T2, T3]; -export function assertAllDefined(t1: T1 | null | undefined, t2: T2 | null | undefined, t3: T3 | null | undefined, t4: T4 | null | undefined): [T1, T2, T3, T4]; -export function assertAllDefined(...args: (unknown | null | undefined)[]): unknown[] { - const result = []; - - for (let i = 0; i < args.length; i++) { - const arg = args[i]; - - if (isUndefinedOrNull(arg)) { - throw new Error(`Assertion Failed: argument at index ${i} is undefined or null`); - } - - result.push(arg); - } - - return result; -} - -const hasOwnProperty = Object.prototype.hasOwnProperty; - -/** - * @returns whether the provided parameter is an empty JavaScript Object or not. - */ -export function isEmptyObject(obj: unknown): obj is object { - if (!isObject(obj)) { - return false; - } - - for (const key in obj) { - if (hasOwnProperty.call(obj, key)) { - return false; - } - } - - return true; -} - -/** - * @returns whether the provided parameter is a JavaScript Function or not. - */ -export function isFunction(obj: unknown): obj is Function { - return (typeof obj === 'function'); -} - -/** - * @returns whether the provided parameters is are JavaScript Function or not. - */ -export function areFunctions(...objects: unknown[]): boolean { - return objects.length > 0 && objects.every(isFunction); -} - -export type TypeConstraint = string | Function; - -export function validateConstraints(args: unknown[], constraints: Array): void { - const len = Math.min(args.length, constraints.length); - for (let i = 0; i < len; i++) { - validateConstraint(args[i], constraints[i]); - } -} - -export function validateConstraint(arg: unknown, constraint: TypeConstraint | undefined): void { - - if (isString(constraint)) { - if (typeof arg !== constraint) { - throw new Error(`argument does not match constraint: typeof ${constraint}`); - } - } else if (isFunction(constraint)) { - try { - if (arg instanceof constraint) { - return; - } - } catch { - // ignore - } - if (!isUndefinedOrNull(arg) && (arg as any).constructor === constraint) { - return; - } - if (constraint.length === 1 && constraint.call(undefined, arg) === true) { - return; - } - throw new Error(`argument does not match one of these constraints: arg instanceof constraint, arg.constructor === constraint, nor constraint(arg) === true`); - } -} - -type AddFirstParameterToFunction = T extends (...args: any[]) => TargetFunctionsReturnType ? - // Function: add param to function - (firstArg: FirstParameter, ...args: Parameters) => ReturnType : - - // Else: just leave as is - T; - -/** - * Allows to add a first parameter to functions of a type. - */ -export type AddFirstParameterToFunctions = { - // For every property - [K in keyof Target]: AddFirstParameterToFunction; -}; - -/** - * Given an object with all optional properties, requires at least one to be defined. - * i.e. AtLeastOne; - */ -export type AtLeastOne }> = Partial & U[keyof U]; - -/** - * Only picks the non-optional properties of a type. - */ -export type OmitOptional = { [K in keyof T as T[K] extends Required[K] ? K : never]: T[K] }; - -/** - * A type that removed readonly-less from all properties of `T` - */ -export type Mutable = { - -readonly [P in keyof T]: T[P] -}; - -/** - * A single object or an array of the objects. - */ -export type SingleOrMany = T | T[]; - - -/** - * A type that recursively makes all properties of `T` required - */ -export type DeepRequiredNonNullable = { - [P in keyof T]-?: T[P] extends object ? DeepRequiredNonNullable : Required>; -}; - - -/** - * Represents a type that is a partial version of a given type `T`, where all properties are optional and can be deeply nested. - */ -export type DeepPartial = { - [P in keyof T]?: T[P] extends object ? DeepPartial : Partial; -}; - -/** - * Represents a type that is a partial version of a given type `T`, except a subset. - */ -export type PartialExcept = Partial> & Pick; diff --git a/src/vs/base/common/uuid.ts b/src/vs/base/common/uuid.ts deleted file mode 100644 index 0bd0c937..00000000 --- a/src/vs/base/common/uuid.ts +++ /dev/null @@ -1,81 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - - -const _UUIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - -export function isUUID(value: string): boolean { - return _UUIDPattern.test(value); -} - -declare const crypto: undefined | { - //https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#browser_compatibility - getRandomValues?(data: Uint8Array): Uint8Array; - //https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID#browser_compatibility - randomUUID?(): string; -}; - -export const generateUuid = (function (): () => string { - - // use `randomUUID` if possible - if (typeof crypto === 'object' && typeof crypto.randomUUID === 'function') { - return crypto.randomUUID.bind(crypto); - } - - // use `randomValues` if possible - let getRandomValues: (bucket: Uint8Array) => Uint8Array; - if (typeof crypto === 'object' && typeof crypto.getRandomValues === 'function') { - getRandomValues = crypto.getRandomValues.bind(crypto); - - } else { - getRandomValues = function (bucket: Uint8Array): Uint8Array { - for (let i = 0; i < bucket.length; i++) { - bucket[i] = Math.floor(Math.random() * 256); - } - return bucket; - }; - } - - // prep-work - const _data = new Uint8Array(16); - const _hex: string[] = []; - for (let i = 0; i < 256; i++) { - _hex.push(i.toString(16).padStart(2, '0')); - } - - return function generateUuid(): string { - // get data - getRandomValues(_data); - - // set version bits - _data[6] = (_data[6] & 0x0f) | 0x40; - _data[8] = (_data[8] & 0x3f) | 0x80; - - // print as string - let i = 0; - let result = ''; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += '-'; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += '-'; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += '-'; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += '-'; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - result += _hex[_data[i++]]; - return result; - }; -})(); diff --git a/src/vs/base/common/verifier.ts b/src/vs/base/common/verifier.ts deleted file mode 100644 index cf77d180..00000000 --- a/src/vs/base/common/verifier.ts +++ /dev/null @@ -1,87 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { isObject } from 'vs/base/common/types'; - -interface IVerifier { - verify(value: unknown): T; -} - -abstract class Verifier implements IVerifier { - - constructor(protected readonly defaultValue: T) { } - - verify(value: unknown): T { - if (!this.isType(value)) { - return this.defaultValue; - } - - return value; - } - - protected abstract isType(value: unknown): value is T; -} - -export class BooleanVerifier extends Verifier { - protected isType(value: unknown): value is boolean { - return typeof value === 'boolean'; - } -} - -export class NumberVerifier extends Verifier { - protected isType(value: unknown): value is number { - return typeof value === 'number'; - } -} - -export class SetVerifier extends Verifier> { - protected isType(value: unknown): value is Set { - return value instanceof Set; - } -} - -export class EnumVerifier extends Verifier { - private readonly allowedValues: ReadonlyArray; - - constructor(defaultValue: T, allowedValues: ReadonlyArray) { - super(defaultValue); - this.allowedValues = allowedValues; - } - - protected isType(value: unknown): value is T { - return this.allowedValues.includes(value as T); - } -} - -export class ObjectVerifier extends Verifier { - - constructor(defaultValue: T, private readonly verifier: { [K in keyof T]: IVerifier }) { - super(defaultValue); - } - - override verify(value: unknown): T { - if (!this.isType(value)) { - return this.defaultValue; - } - return verifyObject(this.verifier, value); - } - - protected isType(value: unknown): value is T { - return isObject(value); - } -} - -export function verifyObject(verifiers: { [K in keyof T]: IVerifier }, value: Object): T { - const result = Object.create(null); - - for (const key in verifiers) { - if (Object.hasOwnProperty.call(verifiers, key)) { - const verifier = verifiers[key]; - result[key] = verifier.verify((value as any)[key]); - } - } - - return result; -}