Merge from vscode merge-base (#22780)

* Revert "Revert "Merge from vscode merge-base (#22769)" (#22779)"

This reverts commit 47a1745180.

* Fix notebook download task

* Remove done call from extensions-ci
This commit is contained in:
Karl Burtram
2023-04-19 21:48:46 -07:00
committed by GitHub
parent decbe8dded
commit e7d3d047ec
2389 changed files with 92155 additions and 42602 deletions

View File

@@ -0,0 +1,69 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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<T> extends Disposable {
private broadcastChannel: BroadcastChannel | undefined;
private readonly _onDidReceiveData = this._register(new Emitter<T>());
readonly onDidReceiveData = this._onDidReceiveData.event;
constructor(private readonly channelName: string) {
super();
// Use BroadcastChannel
if ('BroadcastChannel' in window) {
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));
}
};
window.addEventListener('storage', listener);
this._register(toDisposable(() => window.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
window.localStorage.removeItem(this.channelName);
window.localStorage.setItem(this.channelName, JSON.stringify(data));
}
}
}

View File

@@ -71,9 +71,7 @@ class DevicePixelRatioMonitor extends Disposable {
}
private _handleChange(fireEvent: boolean): void {
if (this._mediaQueryList) {
this._mediaQueryList.removeEventListener('change', this._listener);
}
this._mediaQueryList?.removeEventListener('change', this._listener);
this._mediaQueryList = matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
this._mediaQueryList.addEventListener('change', this._listener);

View File

@@ -86,15 +86,11 @@ class WebWorker implements IWorker {
}
public postMessage(message: any, transfer: Transferable[]): void {
if (this.worker) {
this.worker.then(w => w.postMessage(message, transfer));
}
this.worker?.then(w => w.postMessage(message, transfer));
}
public dispose(): void {
if (this.worker) {
this.worker.then(w => w.terminate());
}
this.worker?.then(w => w.terminate());
this.worker = null;
}
}
@@ -112,7 +108,7 @@ export class DefaultWorkerFactory implements IWorkerFactory {
}
public create(moduleId: string, onMessageCallback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker {
let workerId = (++DefaultWorkerFactory.LAST_WORKER_ID);
const workerId = (++DefaultWorkerFactory.LAST_WORKER_ID);
if (this._webWorkerFailedBeforeError) {
throw this._webWorkerFailedBeforeError;

View File

@@ -0,0 +1,108 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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<UsbDeviceData | undefined> {
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<SerialPortData | undefined> {
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<HidDeviceData | undefined> {
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
};
}

View File

@@ -88,7 +88,7 @@ function _wrapAsStandardKeyboardEvent(handler: (e: IKeyboardEvent) => void): (e:
return handler(new StandardKeyboardEvent(e));
};
}
export let addStandardDisposableListener: IAddStandardDisposableListenerSignature = function addStandardDisposableListener(node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable {
export const addStandardDisposableListener: IAddStandardDisposableListenerSignature = function addStandardDisposableListener(node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable {
let wrapHandler = handler;
if (type === 'click' || type === 'mousedown') {
@@ -100,14 +100,14 @@ export let addStandardDisposableListener: IAddStandardDisposableListenerSignatur
return addDisposableListener(node, type, wrapHandler, useCapture);
};
export let addStandardDisposableGenericMouseDownListener = function addStandardDisposableListener(node: HTMLElement, handler: (event: any) => void, useCapture?: boolean): IDisposable {
let wrapHandler = _wrapAsStandardMouseEvent(handler);
export const addStandardDisposableGenericMouseDownListener = function addStandardDisposableListener(node: HTMLElement, handler: (event: any) => void, useCapture?: boolean): IDisposable {
const wrapHandler = _wrapAsStandardMouseEvent(handler);
return addDisposableGenericMouseDownListener(node, wrapHandler, useCapture);
};
export let addStandardDisposableGenericMouseUpListener = function addStandardDisposableListener(node: HTMLElement, handler: (event: any) => void, useCapture?: boolean): IDisposable {
let wrapHandler = _wrapAsStandardMouseEvent(handler);
export const addStandardDisposableGenericMouseUpListener = function addStandardDisposableListener(node: HTMLElement, handler: (event: any) => void, useCapture?: boolean): IDisposable {
const wrapHandler = _wrapAsStandardMouseEvent(handler);
return addDisposableGenericMouseUpListener(node, wrapHandler, useCapture);
};
@@ -122,35 +122,6 @@ export function addDisposableGenericMouseMoveListener(node: EventTarget, handler
export function addDisposableGenericMouseUpListener(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_UP : EventType.MOUSE_UP, handler, useCapture);
}
export function addDisposableNonBubblingMouseOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable {
return addDisposableListener(node, 'mouseout', (e: MouseEvent) => {
// Mouse out bubbles, so this is an attempt to ignore faux mouse outs coming from children elements
let toElement: Node | null = <Node>(e.relatedTarget);
while (toElement && toElement !== node) {
toElement = toElement.parentNode;
}
if (toElement === node) {
return;
}
handler(e);
});
}
export function addDisposableNonBubblingPointerOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable {
return addDisposableListener(node, 'pointerout', (e: MouseEvent) => {
// Mouse out bubbles, so this is an attempt to ignore faux mouse outs coming from children elements
let toElement: Node | null = <Node>(e.relatedTarget);
while (toElement && toElement !== node) {
toElement = toElement.parentNode;
}
if (toElement === node) {
return;
}
handler(e);
});
}
export function createEventEmitter<K extends keyof HTMLElementEventMap>(target: HTMLElement, type: K, options?: boolean | AddEventListenerOptions): event.Emitter<HTMLElementEventMap[K]> {
let domListener: DomListener | null = null;
@@ -258,7 +229,7 @@ class AnimationFrameQueueItem implements IDisposable {
*/
let inAnimationFrameRunner = false;
let animationFrameRunner = () => {
const animationFrameRunner = () => {
animFrameRequested = false;
CURRENT_QUEUE = NEXT_QUEUE;
@@ -267,14 +238,14 @@ class AnimationFrameQueueItem implements IDisposable {
inAnimationFrameRunner = true;
while (CURRENT_QUEUE.length > 0) {
CURRENT_QUEUE.sort(AnimationFrameQueueItem.sort);
let top = CURRENT_QUEUE.shift()!;
const top = CURRENT_QUEUE.shift()!;
top.execute();
}
inAnimationFrameRunner = false;
};
scheduleAtNextAnimationFrame = (runner: () => void, priority: number = 0) => {
let item = new AnimationFrameQueueItem(runner, priority);
const item = new AnimationFrameQueueItem(runner, priority);
NEXT_QUEUE.push(item);
if (!animFrameRequested) {
@@ -287,7 +258,7 @@ class AnimationFrameQueueItem implements IDisposable {
runAtThisOrScheduleAtNextAnimationFrame = (runner: () => void, priority?: number) => {
if (inAnimationFrameRunner) {
let item = new AnimationFrameQueueItem(runner, priority);
const item = new AnimationFrameQueueItem(runner, priority);
CURRENT_QUEUE!.push(item);
return item;
} else {
@@ -323,9 +294,9 @@ class TimeoutThrottledDomListener<R, E extends Event> extends Disposable {
let lastEvent: R | null = null;
let lastHandlerTime = 0;
let timeout = this._register(new TimeoutTimer());
const timeout = this._register(new TimeoutTimer());
let invokeHandler = () => {
const invokeHandler = () => {
lastHandlerTime = (new Date()).getTime();
handler(<R>lastEvent);
lastEvent = null;
@@ -334,7 +305,7 @@ class TimeoutThrottledDomListener<R, E extends Event> extends Disposable {
this._register(addDisposableListener(node, type, (e) => {
lastEvent = eventMerger(lastEvent, e);
let elapsedTime = (new Date()).getTime() - lastHandlerTime;
const elapsedTime = (new Date()).getTime() - lastHandlerTime;
if (elapsedTime >= minimumTimeMs) {
timeout.cancel();
@@ -392,7 +363,7 @@ class SizeUtils {
}
private static getDimension(element: HTMLElement, cssPropertyName: string, jsPropertyName: string): number {
let computedStyle: CSSStyleDeclaration = getComputedStyle(element);
const computedStyle: CSSStyleDeclaration = getComputedStyle(element);
let value = '0';
if (computedStyle) {
if (computedStyle.getPropertyValue) {
@@ -568,7 +539,7 @@ export function position(element: HTMLElement, top: number, right?: number, bott
* Returns the position of a dom node relative to the entire page.
*/
export function getDomNodePagePosition(domNode: HTMLElement): IDomNodePagePosition {
let bb = domNode.getBoundingClientRect();
const bb = domNode.getBoundingClientRect();
return {
left: bb.left + StandardWindow.scrollX,
top: bb.top + StandardWindow.scrollY,
@@ -577,6 +548,24 @@ export function getDomNodePagePosition(domNode: HTMLElement): IDomNodePagePositi
};
}
/**
* Returns the effective zoom on a given element before window zoom level is applied
*/
export function getDomNodeZoomLevel(domNode: HTMLElement): number {
let testElement: HTMLElement | null = domNode;
let zoom = 1.0;
do {
const elementZoomLevel = (getComputedStyle(testElement) as any).zoom;
if (elementZoomLevel !== null && elementZoomLevel !== undefined && elementZoomLevel !== '1') {
zoom *= elementZoomLevel;
}
testElement = testElement.parentElement;
} while (testElement !== null && testElement !== document.documentElement);
return zoom;
}
export interface IStandardWindow {
readonly scrollX: number;
readonly scrollY: number;
@@ -605,33 +594,33 @@ export const StandardWindow: IStandardWindow = new class implements IStandardWin
// Adapted from WinJS
// Gets the width of the element, including margins.
export function getTotalWidth(element: HTMLElement): number {
let margin = SizeUtils.getMarginLeft(element) + SizeUtils.getMarginRight(element);
const margin = SizeUtils.getMarginLeft(element) + SizeUtils.getMarginRight(element);
return element.offsetWidth + margin;
}
export function getContentWidth(element: HTMLElement): number {
let border = SizeUtils.getBorderLeftWidth(element) + SizeUtils.getBorderRightWidth(element);
let padding = SizeUtils.getPaddingLeft(element) + SizeUtils.getPaddingRight(element);
const border = SizeUtils.getBorderLeftWidth(element) + SizeUtils.getBorderRightWidth(element);
const padding = SizeUtils.getPaddingLeft(element) + SizeUtils.getPaddingRight(element);
return element.offsetWidth - border - padding;
}
export function getTotalScrollWidth(element: HTMLElement): number {
let margin = SizeUtils.getMarginLeft(element) + SizeUtils.getMarginRight(element);
const margin = SizeUtils.getMarginLeft(element) + SizeUtils.getMarginRight(element);
return element.scrollWidth + margin;
}
// Adapted from WinJS
// Gets the height of the content of the specified element. The content height does not include borders or padding.
export function getContentHeight(element: HTMLElement): number {
let border = SizeUtils.getBorderTopWidth(element) + SizeUtils.getBorderBottomWidth(element);
let padding = SizeUtils.getPaddingTop(element) + SizeUtils.getPaddingBottom(element);
const border = SizeUtils.getBorderTopWidth(element) + SizeUtils.getBorderBottomWidth(element);
const padding = SizeUtils.getPaddingTop(element) + SizeUtils.getPaddingBottom(element);
return element.offsetHeight - border - padding;
}
// Adapted from WinJS
// Gets the height of the element, including its margins.
export function getTotalHeight(element: HTMLElement): number {
let margin = SizeUtils.getMarginTop(element) + SizeUtils.getMarginBottom(element);
const margin = SizeUtils.getMarginTop(element) + SizeUtils.getMarginBottom(element);
return element.offsetHeight + margin;
}
@@ -641,16 +630,16 @@ function getRelativeLeft(element: HTMLElement, parent: HTMLElement): number {
return 0;
}
let elementPosition = getTopLeftOffset(element);
let parentPosition = getTopLeftOffset(parent);
const elementPosition = getTopLeftOffset(element);
const parentPosition = getTopLeftOffset(parent);
return elementPosition.left - parentPosition.left;
}
export function getLargestChildWidth(parent: HTMLElement, children: HTMLElement[]): number {
let childWidths = children.map((child) => {
const childWidths = children.map((child) => {
return Math.max(getTotalScrollWidth(child), getTotalWidth(child)) + getRelativeLeft(child, parent) || 0;
});
let maxWidth = Math.max(...childWidths);
const maxWidth = Math.max(...childWidths);
return maxWidth;
}
@@ -769,7 +758,7 @@ export function getActiveElement(): Element | null {
}
export function createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0]): HTMLStyleElement {
let style = document.createElement('style');
const style = document.createElement('style');
style.type = 'text/css';
style.media = 'screen';
container.appendChild(style);
@@ -777,7 +766,7 @@ export function createStyleSheet(container: HTMLElement = document.getElementsBy
}
export function createMetaElement(container: HTMLElement = document.getElementsByTagName('head')[0]): HTMLMetaElement {
let meta = document.createElement('meta');
const meta = document.createElement('meta');
container.appendChild(meta);
return meta;
}
@@ -815,10 +804,10 @@ export function removeCSSRulesContainingSelector(ruleName: string, style: HTMLSt
return;
}
let rules = getDynamicStyleSheetRules(style);
let toDelete: number[] = [];
const rules = getDynamicStyleSheetRules(style);
const toDelete: number[] = [];
for (let i = 0; i < rules.length; i++) {
let rule = rules[i];
const rule = rules[i];
if (rule.selectorText.indexOf(ruleName) !== -1) {
toDelete.push(i);
}
@@ -852,6 +841,7 @@ export const EventType = {
POINTER_UP: 'pointerup',
POINTER_DOWN: 'pointerdown',
POINTER_MOVE: 'pointermove',
POINTER_LEAVE: 'pointerleave',
CONTEXT_MENU: 'contextmenu',
WHEEL: 'wheel',
// Keyboard
@@ -928,7 +918,7 @@ export interface IFocusTracker extends Disposable {
}
export function saveParentsScrollTop(node: Element): number[] {
let r: number[] = [];
const r: number[] = [];
for (let i = 0; node && node.nodeType === node.ELEMENT_NODE; i++) {
r[i] = node.scrollTop;
node = <Element>node.parentNode;
@@ -988,7 +978,7 @@ class FocusTracker extends Disposable implements IFocusTracker {
};
this._refreshStateHandler = () => {
let currentNodeHasFocus = FocusTracker.hasFocusWithin(<HTMLElement>element);
const currentNodeHasFocus = FocusTracker.hasFocusWithin(<HTMLElement>element);
if (currentNodeHasFocus !== hasFocus) {
if (hasFocus) {
onBlur();
@@ -1048,7 +1038,7 @@ export enum Namespace {
}
function _$<T extends Element>(namespace: Namespace, description: string, attrs?: { [key: string]: any }, ...children: Array<Node | string>): T {
let match = SELECTOR_REGEX.exec(description);
const match = SELECTOR_REGEX.exec(description);
if (!match) {
throw new Error('Bad use of emmet');
@@ -1056,7 +1046,7 @@ function _$<T extends Element>(namespace: Namespace, description: string, attrs?
attrs = { ...(attrs || {}) };
let tagName = match[1] || 'div';
const tagName = match[1] || 'div';
let result: T;
if (namespace !== Namespace.HTML) {
@@ -1123,14 +1113,14 @@ export function join(nodes: Node[], separator: Node | string): Node[] {
}
export function show(...elements: HTMLElement[]): void {
for (let element of elements) {
for (const element of elements) {
element.style.display = '';
element.removeAttribute('aria-hidden');
}
}
export function hide(...elements: HTMLElement[]): void {
for (let element of elements) {
for (const element of elements) {
element.style.display = 'none';
element.setAttribute('aria-hidden', 'true');
}
@@ -1158,7 +1148,7 @@ export function removeTabIndexAndUpdateFocus(node: HTMLElement): void {
// typically never want that, rather put focus to the closest element
// in the hierarchy of the parent DOM nodes.
if (document.activeElement === node) {
let parentFocusable = findParentWithAttribute(node.parentElement, 'tabIndex');
const parentFocusable = findParentWithAttribute(node.parentElement, 'tabIndex');
if (parentFocusable) {
parentFocusable.focus();
}
@@ -1289,7 +1279,7 @@ RemoteAuthorities.setPreferredWebSchema(/^https:/.test(window.location.href) ? '
/**
* returns url('...')
*/
export function asCSSUrl(uri: URI): string {
export function asCSSUrl(uri: URI | null | undefined): string {
if (!uri) {
return `url('')`;
}
@@ -1669,25 +1659,12 @@ export function getCookieValue(name: string): string | undefined {
return match ? match.pop() : undefined;
}
export const enum ZIndex {
SASH = 35,
SuggestWidget = 40,
Hover = 50,
DragImage = 1000,
MenubarMenuItemsHolder = 2000, // quick-input-widget
ContextView = 2500,
ModalDialog = 2600,
PaneDropOverlay = 10000
}
export interface IDragAndDropObserverCallbacks {
readonly onDragEnter: (e: DragEvent) => void;
readonly onDragLeave: (e: DragEvent) => void;
readonly onDrop: (e: DragEvent) => void;
readonly onDragEnd: (e: DragEvent) => void;
readonly onDragOver?: (e: DragEvent) => void;
readonly onDragOver?: (e: DragEvent, dragDuration: number) => void;
}
export class DragAndDropObserver extends Disposable {
@@ -1698,6 +1675,9 @@ export class DragAndDropObserver extends Disposable {
// repeadedly.
private counter: number = 0;
// Allows to measure the duration of the drag operation.
private dragStartTime = 0;
constructor(private readonly element: HTMLElement, private readonly callbacks: IDragAndDropObserverCallbacks) {
super();
@@ -1707,6 +1687,7 @@ export class DragAndDropObserver extends Disposable {
private registerListeners(): void {
this._register(addDisposableListener(this.element, EventType.DRAG_ENTER, (e: DragEvent) => {
this.counter++;
this.dragStartTime = e.timeStamp;
this.callbacks.onDragEnter(e);
}));
@@ -1714,27 +1695,194 @@ export class DragAndDropObserver extends Disposable {
this._register(addDisposableListener(this.element, EventType.DRAG_OVER, (e: DragEvent) => {
e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
if (this.callbacks.onDragOver) {
this.callbacks.onDragOver(e);
}
this.callbacks.onDragOver?.(e, e.timeStamp - this.dragStartTime);
}));
this._register(addDisposableListener(this.element, EventType.DRAG_LEAVE, (e: DragEvent) => {
this.counter--;
if (this.counter === 0) {
this.dragStartTime = 0;
this.callbacks.onDragLeave(e);
}
}));
this._register(addDisposableListener(this.element, EventType.DRAG_END, (e: DragEvent) => {
this.counter = 0;
this.dragStartTime = 0;
this.callbacks.onDragEnd(e);
}));
this._register(addDisposableListener(this.element, EventType.DROP, (e: DragEvent) => {
this.counter = 0;
this.dragStartTime = 0;
this.callbacks.onDrop(e);
}));
}
}
export function computeClippingRect(elementOrRect: HTMLElement | DOMRectReadOnly, clipper: HTMLElement) {
const frameRect = (elementOrRect instanceof HTMLElement ? elementOrRect.getBoundingClientRect() : elementOrRect);
const rootRect = clipper.getBoundingClientRect();
const top = Math.max(rootRect.top - frameRect.top, 0);
const right = Math.max(frameRect.width - (frameRect.right - rootRect.right), 0);
const bottom = Math.max(frameRect.height - (frameRect.bottom - rootRect.bottom), 0);
const left = Math.max(rootRect.left - frameRect.left, 0);
return { top, right, bottom, left };
}
type HTMLElementAttributeKeys<T> = Partial<{ [K in keyof T]: T[K] extends Function ? never : T[K] extends object ? HTMLElementAttributeKeys<T[K]> : T[K] }>;
type ElementAttributes<T> = HTMLElementAttributeKeys<T> & Record<string, any>;
type RemoveHTMLElement<T> = T extends HTMLElement ? never : T;
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
type ArrayToObj<T extends readonly any[]> = UnionToIntersection<RemoveHTMLElement<T[number]>>;
type HHTMLElementTagNameMap = HTMLElementTagNameMap & { '': HTMLDivElement };
type TagToElement<T> = T extends `${infer TStart}#${string}`
? TStart extends keyof HHTMLElementTagNameMap
? HHTMLElementTagNameMap[TStart]
: HTMLElement
: T extends `${infer TStart}.${string}`
? TStart extends keyof HHTMLElementTagNameMap
? HHTMLElementTagNameMap[TStart]
: HTMLElement
: T extends keyof HTMLElementTagNameMap
? HTMLElementTagNameMap[T]
: HTMLElement;
type TagToElementAndId<TTag> = TTag extends `${infer TTag}@${infer TId}`
? { element: TagToElement<TTag>; id: TId }
: { element: TagToElement<TTag>; id: 'root' };
type TagToRecord<TTag> = TagToElementAndId<TTag> extends { element: infer TElement; id: infer TId }
? Record<(TId extends string ? TId : never) | 'root', TElement>
: never;
type Child = HTMLElement | string | Record<string, HTMLElement>;
type Children = []
| [Child]
| [Child, Child]
| [Child, Child, Child]
| [Child, Child, Child, Child]
| [Child, Child, Child, Child, Child]
| [Child, Child, Child, Child, Child, Child]
| [Child, Child, Child, Child, Child, Child, Child]
| [Child, Child, Child, Child, Child, Child, Child, Child]
| [Child, Child, Child, Child, Child, Child, Child, Child, Child]
| [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]
| [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]
| [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]
| [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]
| [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]
| [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]
| [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child];
const H_REGEX = /(?<tag>[\w\-]+)?(?:#(?<id>[\w\-]+))?(?<class>(?:\.(?:[\w\-]+))*)(?:@(?<name>(?:[\w\_])+))?/;
/**
* A helper function to create nested dom nodes.
*
*
* ```ts
* const elements = h('div.code-view', [
* h('div.title@title'),
* h('div.container', [
* h('div.gutter@gutterDiv'),
* h('div@editor'),
* ]),
* ]);
* const editor = createEditor(elements.editor);
* ```
*/
export function h<TTag extends string>
(tag: TTag):
TagToRecord<TTag> extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
export function h<TTag extends string, T extends Children>
(tag: TTag, children: T):
(ArrayToObj<T> & TagToRecord<TTag>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
export function h<TTag extends string>
(tag: TTag, attributes: Partial<ElementAttributes<TagToElement<TTag>>>):
TagToRecord<TTag> extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
export function h<TTag extends string, T extends Children>
(tag: TTag, attributes: Partial<ElementAttributes<TagToElement<TTag>>>, children: T):
(ArrayToObj<T> & TagToRecord<TTag>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
export function h(tag: string, ...args: [] | [attributes: { $: string } & Partial<ElementAttributes<HTMLElement>> | Record<string, any>, children?: any[]] | [children: any[]]): Record<string, HTMLElement> {
let attributes: { $?: string } & Partial<ElementAttributes<HTMLElement>>;
let children: (Record<string, HTMLElement> | HTMLElement)[] | undefined;
if (Array.isArray(args[0])) {
attributes = {};
children = args[0];
} else {
attributes = args[0] as any || {};
children = args[1];
}
const match = H_REGEX.exec(tag);
if (!match || !match.groups) {
throw new Error('Bad use of h');
}
const tagName = match.groups['tag'] || 'div';
const el = document.createElement(tagName);
if (match.groups['id']) {
el.id = match.groups['id'];
}
if (match.groups['class']) {
el.className = match.groups['class'].replace(/\./g, ' ').trim();
}
const result: Record<string, HTMLElement> = {};
if (match.groups['name']) {
result[match.groups['name']] = el;
}
if (children) {
for (const c of children) {
if (c instanceof HTMLElement) {
el.appendChild(c);
} else if (typeof c === 'string') {
el.append(c);
} else {
Object.assign(result, c);
el.appendChild(c.root);
}
}
}
for (const [key, value] of Object.entries(attributes)) {
if (key === 'style') {
for (const [cssKey, cssValue] of Object.entries(value)) {
el.style.setProperty(
camelCaseToHyphenCase(cssKey),
typeof cssValue === 'number' ? cssValue + 'px' : '' + cssValue
);
}
} else if (key === 'tabIndex') {
el.tabIndex = value;
} else {
el.setAttribute(camelCaseToHyphenCase(key), value.toString());
}
}
result['root'] = el;
return result;
}
function camelCaseToHyphenCase(str: string) {
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}

View File

@@ -8,7 +8,7 @@ import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { DisposableStore } from 'vs/base/common/lifecycle';
export interface IContentActionHandler {
callback: (content: string, event?: IMouseEvent) => void;
callback: (content: string, event: IMouseEvent) => void;
readonly disposables: DisposableStore;
}

View File

@@ -6,40 +6,18 @@
import * as dom from 'vs/base/browser/dom';
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
export interface IPointerMoveEventData {
leftButton: boolean;
buttons: number;
pageX: number;
pageY: number;
}
export interface IEventMerger<R> {
(lastEvent: R | null, currentEvent: PointerEvent): R;
}
export interface IPointerMoveCallback<R> {
(pointerMoveData: R): void;
export interface IPointerMoveCallback {
(event: PointerEvent): void;
}
export interface IOnStopCallback {
(browserEvent?: PointerEvent | KeyboardEvent): void;
}
export function standardPointerMoveMerger(lastEvent: IPointerMoveEventData | null, currentEvent: PointerEvent): IPointerMoveEventData {
currentEvent.preventDefault();
return {
leftButton: (currentEvent.button === 0),
buttons: currentEvent.buttons,
pageX: currentEvent.pageX,
pageY: currentEvent.pageY
};
}
export class GlobalPointerMoveMonitor<R extends { buttons: number } = IPointerMoveEventData> implements IDisposable {
export class GlobalPointerMoveMonitor implements IDisposable {
private readonly _hooks = new DisposableStore();
private _pointerMoveEventMerger: IEventMerger<R> | null = null;
private _pointerMoveCallback: IPointerMoveCallback<R> | null = null;
private _pointerMoveCallback: IPointerMoveCallback | null = null;
private _onStopCallback: IOnStopCallback | null = null;
public dispose(): void {
@@ -55,7 +33,6 @@ export class GlobalPointerMoveMonitor<R extends { buttons: number } = IPointerMo
// Unhook
this._hooks.clear();
this._pointerMoveEventMerger = null;
this._pointerMoveCallback = null;
const onStopCallback = this._onStopCallback;
this._onStopCallback = null;
@@ -66,21 +43,19 @@ export class GlobalPointerMoveMonitor<R extends { buttons: number } = IPointerMo
}
public isMonitoring(): boolean {
return !!this._pointerMoveEventMerger;
return !!this._pointerMoveCallback;
}
public startMonitoring(
initialElement: Element,
pointerId: number,
initialButtons: number,
pointerMoveEventMerger: IEventMerger<R>,
pointerMoveCallback: IPointerMoveCallback<R>,
pointerMoveCallback: IPointerMoveCallback,
onStopCallback: IOnStopCallback
): void {
if (this.isMonitoring()) {
this.stopMonitoring(false);
}
this._pointerMoveEventMerger = pointerMoveEventMerger;
this._pointerMoveCallback = pointerMoveCallback;
this._onStopCallback = onStopCallback;
@@ -103,18 +78,19 @@ export class GlobalPointerMoveMonitor<R extends { buttons: number } = IPointerMo
eventSource = window;
}
this._hooks.add(dom.addDisposableThrottledListener<R, PointerEvent>(
this._hooks.add(dom.addDisposableListener(
eventSource,
dom.EventType.POINTER_MOVE,
(data: R) => {
if (data.buttons !== initialButtons) {
(e) => {
if (e.buttons !== initialButtons) {
// Buttons state has changed in the meantime
this.stopMonitoring(true);
return;
}
this._pointerMoveCallback!(data);
},
(lastEvent: R | null, currentEvent) => this._pointerMoveEventMerger!(lastEvent, currentEvent)
e.preventDefault();
this._pointerMoveCallback!(e);
}
));
this._hooks.add(dom.addDisposableListener(

View File

@@ -3,10 +3,18 @@
* Licensed under the Source EULA. 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<void>;
onDidBlur: Event<void>;
}

View File

@@ -27,8 +27,8 @@ function getParentWindowIfSameOrigin(w: Window): Window | null {
// Cannot really tell if we have access to the parent window unless we try to access something in it
try {
let location = w.location;
let parentLocation = w.parent.location;
const location = w.location;
const parentLocation = w.parent.location;
if (location.origin !== 'null' && parentLocation.origin !== 'null' && location.origin !== parentLocation.origin) {
hasDifferentOriginAncestorFlag = true;
return null;
@@ -97,7 +97,7 @@ export class IframeUtils {
let top = 0, left = 0;
let windowChain = this.getSameOriginWindowChain();
const windowChain = this.getSameOriginWindowChain();
for (const windowChainEl of windowChain) {
@@ -112,7 +112,7 @@ export class IframeUtils {
break;
}
let boundingRect = windowChainEl.iframeElement.getBoundingClientRect();
const boundingRect = windowChainEl.iframeElement.getBoundingClientRect();
top += boundingRect.top;
left += boundingRect.left;
}

View File

@@ -14,6 +14,13 @@ class MissingStoresError extends Error {
}
}
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<IndexedDB> {
@@ -21,7 +28,7 @@ export class IndexedDB {
return new IndexedDB(database, name);
}
static async openDatabase(name: string, version: number | undefined, stores: string[]): Promise<IDBDatabase> {
private static async openDatabase(name: string, version: number | undefined, stores: string[]): Promise<IDBDatabase> {
mark(`code/willOpenDatabase/${name}`);
try {
return await IndexedDB.doOpenDatabase(name, version, stores);
@@ -109,7 +116,7 @@ export class IndexedDB {
runInTransaction<T>(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest<T>): Promise<T>;
async runInTransaction<T>(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest<T> | IDBRequest<T>[]): Promise<T | T[]> {
if (!this.database) {
throw new Error(`IndexedDB database '${this.name}' is not opened.`);
throw new DBClosedError(this.name);
}
const transaction = this.database.transaction(store, transactionMode);
this.pendingTransactions.push(transaction);
@@ -128,7 +135,7 @@ export class IndexedDB {
async getKeyValues<V>(store: string, isValid: (value: unknown) => value is V): Promise<Map<string, V>> {
if (!this.database) {
throw new Error(`IndexedDB database '${this.name}' is not opened.`);
throw new DBClosedError(this.name);
}
const transaction = this.database.transaction(store, 'readonly');
this.pendingTransactions.push(transaction);

View File

@@ -13,7 +13,7 @@ import * as platform from 'vs/base/common/platform';
function extractKeyCode(e: KeyboardEvent): KeyCode {
if (e.charCode) {
// "keypress" events mostly
let char = String.fromCharCode(e.charCode).toUpperCase();
const char = String.fromCharCode(e.charCode).toUpperCase();
return KeyCodeUtils.fromString(char);
}
@@ -77,7 +77,7 @@ const shiftKeyMod = KeyMod.Shift;
const metaKeyMod = (platform.isMacintosh ? KeyMod.CtrlCmd : KeyMod.WinCtrl);
export function printKeyboardEvent(e: KeyboardEvent): string {
let modifiers: string[] = [];
const modifiers: string[] = [];
if (e.ctrlKey) {
modifiers.push(`ctrl`);
}
@@ -94,7 +94,7 @@ export function printKeyboardEvent(e: KeyboardEvent): string {
}
export function printStandardKeyboardEvent(e: StandardKeyboardEvent): string {
let modifiers: string[] = [];
const modifiers: string[] = [];
if (e.ctrlKey) {
modifiers.push(`ctrl`);
}
@@ -128,7 +128,7 @@ export class StandardKeyboardEvent implements IKeyboardEvent {
private _asRuntimeKeybinding: SimpleKeybinding;
constructor(source: KeyboardEvent) {
let e = source;
const e = source;
this.browserEvent = e;
this.target = <HTMLElement>e.target;

View File

@@ -9,11 +9,9 @@ import { DomEmitter } from 'vs/base/browser/event';
import { createElement, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { raceCancellation } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Event } from 'vs/base/common/event';
import { IMarkdownString, parseHrefAndDimensions, removeMarkdownEscapes } from 'vs/base/common/htmlContent';
import { IMarkdownString, escapeDoubleQuotes, parseHrefAndDimensions, removeMarkdownEscapes } from 'vs/base/common/htmlContent';
import { markdownEscapeEscapedIcons } from 'vs/base/common/iconLabels';
import { defaultGenerator } from 'vs/base/common/idGenerator';
import { DisposableStore } from 'vs/base/common/lifecycle';
@@ -44,8 +42,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
const disposables = new DisposableStore();
let isDisposed = false;
const cts = disposables.add(new CancellationTokenSource());
const element = createElement(options);
const _uriMassage = function (part: string): string {
@@ -96,11 +92,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
return uri.toString();
};
// signal to code-block render that the
// element has been created
let signalInnerHTML: () => void;
const withInnerHTML = new Promise<void>(c => signalInnerHTML = c);
const renderer = new marked.Renderer();
renderer.image = (href: string, title: string, text: string) => {
@@ -108,13 +99,13 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
let attributes: string[] = [];
if (href) {
({ href, dimensions } = parseHrefAndDimensions(href));
attributes.push(`src="${href}"`);
attributes.push(`src="${escapeDoubleQuotes(href)}"`);
}
if (text) {
attributes.push(`alt="${text}"`);
attributes.push(`alt="${escapeDoubleQuotes(text)}"`);
}
if (title) {
attributes.push(`title="${title}"`);
attributes.push(`title="${escapeDoubleQuotes(title)}"`);
}
if (dimensions.length) {
attributes = attributes.concat(dimensions);
@@ -130,53 +121,30 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
if (href === text) { // raw link case
text = removeMarkdownEscapes(text);
}
href = _href(href, false);
if (markdown.baseUri) {
href = resolveWithBaseUri(URI.from(markdown.baseUri), href);
}
title = typeof title === 'string' ? removeMarkdownEscapes(title) : '';
href = removeMarkdownEscapes(href);
if (
!href
|| /^data:|javascript:/i.test(href)
|| (/^command:/i.test(href) && !markdown.isTrusted)
|| /^command:(\/\/\/)?_workbench\.downloadResource/i.test(href)
) {
// drop the link
return text;
} else {
// HTML Encode href
href = href.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
return `<a data-href="${href}" title="${title || href}">${text}</a>`;
}
title = typeof title === 'string' ? escapeDoubleQuotes(removeMarkdownEscapes(title)) : '';
href = removeMarkdownEscapes(href);
// HTML Encode href
href = href.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
return `<a href="${href}" title="${title || href}">${text}</a>`;
};
renderer.paragraph = (text): string => {
return `<p>${text}</p>`;
};
// Will collect [id, renderedElement] tuples
const codeBlocks: Promise<[string, HTMLElement]>[] = [];
if (options.codeBlockRenderer) {
renderer.code = (code, lang) => {
const value = options.codeBlockRenderer!(lang ?? '', code);
// when code-block rendering is async we return sync
// but update the node with the real result later.
const id = defaultGenerator.nextId();
raceCancellation(Promise.all([value, withInnerHTML]), cts.token).then(values => {
if (!isDisposed && values) {
const span = element.querySelector<HTMLDivElement>(`div[data-code="${id}"]`);
if (span) {
DOM.reset(span, values[0]);
}
options.asyncRenderCallback?.();
}
}).catch(() => {
// ignore
});
const value = options.codeBlockRenderer!(lang ?? '', code);
codeBlocks.push(value.then(element => [id, element]));
return `<div class="code" data-code="${id}">${escape(code)}</div>`;
};
}
@@ -268,10 +236,45 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
}
});
markdownHtmlDoc.body.querySelectorAll('a')
.forEach(a => {
const href = a.getAttribute('href'); // Get the raw 'href' attribute value as text, not the resolved 'href'
a.setAttribute('href', ''); // Clear out href. We use the `data-href` for handling clicks instead
if (
!href
|| /^data:|javascript:/i.test(href)
|| (/^command:/i.test(href) && !markdown.isTrusted)
|| /^command:(\/\/\/)?_workbench\.downloadResource/i.test(href)
) {
// drop the link
a.replaceWith(...a.childNodes);
} else {
let resolvedHref = _href(href, false);
if (markdown.baseUri) {
resolvedHref = resolveWithBaseUri(URI.from(markdown.baseUri), href);
}
a.dataset.href = resolvedHref;
}
});
element.innerHTML = sanitizeRenderedMarkdown(markdown, markdownHtmlDoc.body.innerHTML) as unknown as string;
// signal that async code blocks can be now be inserted
signalInnerHTML!();
if (codeBlocks.length > 0) {
Promise.all(codeBlocks).then((tuples) => {
if (isDisposed) {
return;
}
const renderedElements = new Map(tuples);
const placeholderElements = element.querySelectorAll<HTMLDivElement>(`div[data-code]`);
for (const placeholderElement of placeholderElements) {
const renderedElement = renderedElements.get(placeholderElement.dataset['code'] ?? '');
if (renderedElement) {
DOM.reset(placeholderElement, renderedElement);
}
}
options.asyncRenderCallback?.();
});
}
// signal size changes for image tags
if (options.asyncRenderCallback) {
@@ -287,7 +290,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
element,
dispose: () => {
isDisposed = true;
cts.cancel();
disposables.dispose();
}
};

View File

@@ -74,7 +74,7 @@ export class StandardMouseEvent implements IMouseEvent {
}
// Find the position of the iframe this code is executing in relative to the iframe where the event was captured.
let iframeOffsets = IframeUtils.getPositionOfChildWindowRelativeToAncestorWindow(self, e.view);
const iframeOffsets = IframeUtils.getPositionOfChildWindowRelativeToAncestorWindow(self, e.view);
this.posx -= iframeOffsets.left;
this.posy -= iframeOffsets.top;
}
@@ -152,8 +152,8 @@ export class StandardWheelEvent {
if (e) {
// Old (deprecated) wheel events
let e1 = <IWebKitMouseWheelEvent><any>e;
let e2 = <IGeckoMouseWheelEvent><any>e;
const e1 = <IWebKitMouseWheelEvent><any>e;
const e2 = <IGeckoMouseWheelEvent><any>e;
// vertical delta scroll
if (typeof e1.wheelDeltaY !== 'undefined') {

View File

@@ -146,7 +146,7 @@ export class Gesture extends Disposable {
}
private onTouchStart(e: TouchEvent): void {
let timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
const timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
if (this.handle) {
this.handle.dispose();
@@ -154,7 +154,7 @@ export class Gesture extends Disposable {
}
for (let i = 0, len = e.targetTouches.length; i < len; i++) {
let touch = e.targetTouches.item(i);
const touch = e.targetTouches.item(i);
this.activeTouches[touch.identifier] = {
id: touch.identifier,
@@ -167,7 +167,7 @@ export class Gesture extends Disposable {
rollingPageY: [touch.pageY]
};
let evt = this.newGestureEvent(EventType.Start, touch.target);
const evt = this.newGestureEvent(EventType.Start, touch.target);
evt.pageX = touch.pageX;
evt.pageY = touch.pageY;
this.dispatchEvent(evt);
@@ -181,27 +181,27 @@ export class Gesture extends Disposable {
}
private onTouchEnd(e: TouchEvent): void {
let timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
const timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
let activeTouchCount = Object.keys(this.activeTouches).length;
const activeTouchCount = Object.keys(this.activeTouches).length;
for (let i = 0, len = e.changedTouches.length; i < len; i++) {
let touch = e.changedTouches.item(i);
const touch = e.changedTouches.item(i);
if (!this.activeTouches.hasOwnProperty(String(touch.identifier))) {
console.warn('move of an UNKNOWN touch', touch);
continue;
}
let data = this.activeTouches[touch.identifier],
const data = this.activeTouches[touch.identifier],
holdTime = Date.now() - data.initialTimeStamp;
if (holdTime < Gesture.HOLD_DELAY
&& Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)) < 30
&& Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)) < 30) {
let evt = this.newGestureEvent(EventType.Tap, data.initialTarget);
const evt = this.newGestureEvent(EventType.Tap, data.initialTarget);
evt.pageX = arrays.tail(data.rollingPageX);
evt.pageY = arrays.tail(data.rollingPageY);
this.dispatchEvent(evt);
@@ -210,18 +210,18 @@ export class Gesture extends Disposable {
&& Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)) < 30
&& Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)) < 30) {
let evt = this.newGestureEvent(EventType.Contextmenu, data.initialTarget);
const evt = this.newGestureEvent(EventType.Contextmenu, data.initialTarget);
evt.pageX = arrays.tail(data.rollingPageX);
evt.pageY = arrays.tail(data.rollingPageY);
this.dispatchEvent(evt);
} else if (activeTouchCount === 1) {
let finalX = arrays.tail(data.rollingPageX);
let finalY = arrays.tail(data.rollingPageY);
const finalX = arrays.tail(data.rollingPageX);
const finalY = arrays.tail(data.rollingPageY);
let deltaT = arrays.tail(data.rollingTimestamps) - data.rollingTimestamps[0];
let deltaX = finalX - data.rollingPageX[0];
let deltaY = finalY - data.rollingPageY[0];
const deltaT = arrays.tail(data.rollingTimestamps) - data.rollingTimestamps[0];
const deltaX = finalX - data.rollingPageX[0];
const deltaY = finalY - data.rollingPageY[0];
// We need to get all the dispatch targets on the start of the inertia event
const dispatchTo = this.targets.filter(t => data.initialTarget instanceof Node && t.contains(data.initialTarget));
@@ -249,7 +249,7 @@ export class Gesture extends Disposable {
}
private newGestureEvent(type: string, initialTarget?: EventTarget): GestureEvent {
let event = document.createEvent('CustomEvent') as unknown as GestureEvent;
const event = document.createEvent('CustomEvent') as unknown as GestureEvent;
event.initEvent(type, false, true);
event.initialTarget = initialTarget;
event.tapCount = 0;
@@ -289,12 +289,12 @@ export class Gesture extends Disposable {
private inertia(dispatchTo: EventTarget[], t1: number, vX: number, dirX: number, x: number, vY: number, dirY: number, y: number): void {
this.handle = DomUtils.scheduleAtNextAnimationFrame(() => {
let now = Date.now();
const now = Date.now();
// velocity: old speed + accel_over_time
let deltaT = now - t1,
delta_pos_x = 0, delta_pos_y = 0,
stopped = true;
const deltaT = now - t1;
let delta_pos_x = 0, delta_pos_y = 0;
let stopped = true;
vX += Gesture.SCROLL_FRICTION * deltaT;
vY += Gesture.SCROLL_FRICTION * deltaT;
@@ -310,7 +310,7 @@ export class Gesture extends Disposable {
}
// dispatch translation event
let evt = this.newGestureEvent(EventType.Change);
const evt = this.newGestureEvent(EventType.Change);
evt.translationX = delta_pos_x;
evt.translationY = delta_pos_y;
dispatchTo.forEach(d => d.dispatchEvent(evt));
@@ -322,20 +322,20 @@ export class Gesture extends Disposable {
}
private onTouchMove(e: TouchEvent): void {
let timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
const timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
for (let i = 0, len = e.changedTouches.length; i < len; i++) {
let touch = e.changedTouches.item(i);
const touch = e.changedTouches.item(i);
if (!this.activeTouches.hasOwnProperty(String(touch.identifier))) {
console.warn('end of an UNKNOWN touch', touch);
continue;
}
let data = this.activeTouches[touch.identifier];
const data = this.activeTouches[touch.identifier];
let evt = this.newGestureEvent(EventType.Change, data.initialTarget);
const evt = this.newGestureEvent(EventType.Change, data.initialTarget);
evt.translationX = touch.pageX - arrays.tail(data.rollingPageX);
evt.translationY = touch.pageY - arrays.tail(data.rollingPageY);
evt.pageX = touch.pageX;

View File

@@ -9,6 +9,8 @@ import { $, addDisposableListener, append, EventHelper, EventLike, EventType } f
import { EventType as TouchEventType, Gesture } from 'vs/base/browser/touch';
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
import { ISelectBoxOptions, ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
import { Action, ActionRunner, IAction, IActionChangeEvent, IActionRunner, Separator } from 'vs/base/common/actions';
import { Disposable } from 'vs/base/common/lifecycle';
@@ -21,6 +23,7 @@ export interface IBaseActionViewItemOptions {
draggable?: boolean;
isMenu?: boolean;
useEventAsContext?: boolean;
hoverDelegate?: IHoverDelegate;
}
export class BaseActionViewItem extends Disposable implements IActionViewItem {
@@ -28,7 +31,9 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem {
element: HTMLElement | undefined;
_context: unknown;
_action: IAction;
readonly _action: IAction;
private customHover?: ICustomHover;
get action() {
return this._action;
@@ -213,8 +218,27 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem {
// implement in subclass
}
protected getTooltip(): string | undefined {
return this.getAction().tooltip;
}
protected updateTooltip(): void {
// implement in subclass
if (!this.element) {
return;
}
const title = this.getTooltip() ?? '';
this.element.setAttribute('aria-label', title);
if (!this.options.hoverDelegate) {
this.element.title = title;
} else {
this.element.title = '';
if (!this.customHover) {
this.customHover = setupCustomHover(this.options.hoverDelegate, this.element, title);
this._store.add(this.customHover);
} else {
this.customHover.update(title);
}
}
}
protected updateClass(): void {
@@ -323,7 +347,7 @@ export class ActionViewItem extends BaseActionViewItem {
}
}
override updateTooltip(): void {
override getTooltip() {
let title: string | null = null;
if (this.getAction().tooltip) {
@@ -336,11 +360,7 @@ export class ActionViewItem extends BaseActionViewItem {
title = nls.localize({ key: 'titleLabel', comment: ['action title', 'action keybinding'] }, "{0} ({1})", title, this.options.keybinding);
}
}
if (title && this.label) {
this.label.title = title;
this.label.setAttribute('aria-label', title);
}
return title ?? undefined;
}
override updateClass(): void {
@@ -372,9 +392,7 @@ export class ActionViewItem extends BaseActionViewItem {
this.updateEnabled();
} else {
if (this.label) {
this.label.classList.remove('codicon');
}
this.label?.classList.remove('codicon');
}
}
@@ -385,18 +403,14 @@ export class ActionViewItem extends BaseActionViewItem {
this.label.classList.remove('disabled');
}
if (this.element) {
this.element.classList.remove('disabled');
}
this.element?.classList.remove('disabled');
} else {
if (this.label) {
this.label.setAttribute('aria-disabled', 'true');
this.label.classList.add('disabled');
}
if (this.element) {
this.element.classList.add('disabled');
}
this.element?.classList.add('disabled');
}
}

View File

@@ -6,6 +6,7 @@
import * as DOM from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
import { ActionRunner, IAction, IActionRunner, IRunEvent, Separator } from 'vs/base/common/actions';
import { Emitter } from 'vs/base/common/event';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
@@ -49,6 +50,7 @@ export interface IActionBarOptions {
readonly allowContextMenu?: boolean;
readonly preventLoopNavigation?: boolean;
readonly focusOnlyEnabledItems?: boolean;
readonly hoverDelegate?: IHoverDelegate;
}
export interface IActionOptions extends IActionViewItemOptions {
@@ -327,7 +329,7 @@ export class ActionBar extends Disposable implements IActionRunner {
}
if (!item) {
item = new ActionViewItem(this.context, action, options);
item = new ActionViewItem(this.context, action, { hoverDelegate: this.options.hoverDelegate, ...options });
}
// Prevent native context menu on actions

View File

@@ -170,7 +170,7 @@ export class BreadcrumbsWidget {
}
domFocus(): void {
let idx = this._focusedItemIdx >= 0 ? this._focusedItemIdx : this._items.length - 1;
const idx = this._focusedItemIdx >= 0 ? this._focusedItemIdx : this._items.length - 1;
if (idx >= 0 && idx < this._items.length) {
this._focus(idx, undefined);
} else {
@@ -226,7 +226,7 @@ export class BreadcrumbsWidget {
}
reveal(item: BreadcrumbsItem): void {
let idx = this._items.indexOf(item);
const idx = this._items.indexOf(item);
if (idx >= 0) {
this._reveal(idx, false);
}
@@ -281,7 +281,7 @@ export class BreadcrumbsWidget {
dispose(removed);
this._focus(-1, undefined);
} catch (e) {
let newError = new Error(`BreadcrumbsItem#setItems: newItems: ${items.length}, prefix: ${prefix}, removed: ${removed.length}`);
const newError = new Error(`BreadcrumbsItem#setItems: newItems: ${items.length}, prefix: ${prefix}, removed: ${removed.length}`);
newError.name = e.name;
newError.stack = e.stack;
throw newError;
@@ -291,8 +291,8 @@ export class BreadcrumbsWidget {
private _render(start: number): void {
let didChange = false;
for (; start < this._items.length && start < this._nodes.length; start++) {
let item = this._items[start];
let node = this._nodes[start];
const item = this._items[start];
const node = this._nodes[start];
this._renderItem(item, node);
didChange = true;
}
@@ -308,8 +308,8 @@ export class BreadcrumbsWidget {
// case b: more items -> render them
for (; start < this._items.length; start++) {
let item = this._items[start];
let node = this._freeNodes.length > 0 ? this._freeNodes.pop() : document.createElement('div');
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);
@@ -343,7 +343,7 @@ export class BreadcrumbsWidget {
return;
}
for (let el: HTMLElement | null = event.target; el; el = el.parentElement) {
let idx = this._nodes.indexOf(el as HTMLDivElement);
const idx = this._nodes.indexOf(el as HTMLDivElement);
if (idx >= 0) {
this._focus(idx, event);
this._select(idx, event);

View File

@@ -38,8 +38,36 @@
cursor: pointer;
}
.monaco-button-dropdown > .monaco-dropdown-button {
margin-left: 1px;
.monaco-button-dropdown.disabled {
cursor: default;
}
.monaco-button-dropdown > .monaco-button:focus {
outline-offset: -1px !important;
}
.monaco-button-dropdown.disabled > .monaco-button.disabled,
.monaco-button-dropdown.disabled > .monaco-button.disabled:focus,
.monaco-button-dropdown.disabled > .monaco-button-dropdown-separator {
opacity: 0.4 !important;
}
.monaco-button-dropdown > .monaco-button.monaco-text-button {
border-right-width: 0 !important;
}
.monaco-button-dropdown .monaco-button-dropdown-separator {
padding: 4px 0;
cursor: default;
}
.monaco-button-dropdown .monaco-button-dropdown-separator > div {
height: 100%;
width: 1px;
}
.monaco-button-dropdown > .monaco-button.monaco-dropdown-button {
border-left-width: 0 !important;
}
.monaco-description-button {

View File

@@ -15,6 +15,7 @@ import { Emitter, Event as BaseEvent } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { mixin } from 'vs/base/common/objects';
import { localize } from 'vs/nls';
import 'vs/css!./button';
export interface IButtonOptions extends IButtonStyles {
@@ -27,6 +28,7 @@ export interface IButtonStyles {
buttonBackground?: Color;
buttonHoverBackground?: Color;
buttonForeground?: Color;
buttonSeparator?: Color;
buttonSecondaryBackground?: Color;
buttonSecondaryHoverBackground?: Color;
buttonSecondaryForeground?: Color;
@@ -41,6 +43,7 @@ export interface IButtonStyles {
const defaultOptions: IButtonStyles = {
buttonBackground: Color.fromHex('#0E639C'),
buttonHoverBackground: Color.fromHex('#006BB3'),
buttonSeparator: Color.white,
buttonForeground: Color.white
};
@@ -154,8 +157,8 @@ export class Button extends Disposable implements IButton {
// Also set hover background when button is focused for feedback
this.focusTracker = this._register(trackFocus(this._element));
this._register(this.focusTracker.onDidFocus(() => this.setHoverBackground()));
this._register(this.focusTracker.onDidBlur(() => this.applyStyles())); // restore standard styles
this._register(this.focusTracker.onDidFocus(() => { if (this.enabled) { this.setHoverBackground(); } }));
this._register(this.focusTracker.onDidBlur(() => { if (this.enabled) { this.applyStyles(); } }));
this.applyStyles();
}
@@ -317,6 +320,7 @@ export interface IButtonWithDropdownOptions extends IButtonOptions {
readonly contextMenuProvider: IContextMenuProvider;
readonly actions: IAction[];
readonly actionRunner?: IActionRunner;
readonly addPrimaryActionToDropdown?: boolean;
}
export class ButtonWithDropdown extends Disposable implements IButton {
@@ -324,6 +328,8 @@ export class ButtonWithDropdown extends Disposable implements IButton {
private readonly button: Button;
private readonly action: Action;
private readonly dropdownButton: Button;
private readonly separatorContainer: HTMLDivElement;
private readonly separator: HTMLDivElement;
readonly element: HTMLElement;
private readonly _onDidClick = this._register(new Emitter<Event | undefined>());
@@ -340,13 +346,23 @@ export class ButtonWithDropdown extends Disposable implements IButton {
this._register(this.button.onDidClick(e => this._onDidClick.fire(e)));
this.action = this._register(new Action('primaryAction', this.button.label, undefined, true, async () => this._onDidClick.fire(undefined)));
this.separatorContainer = document.createElement('div');
this.separatorContainer.classList.add('monaco-button-dropdown-separator');
this.separator = document.createElement('div');
this.separatorContainer.appendChild(this.separator);
this.element.appendChild(this.separatorContainer);
this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true }));
this.dropdownButton.element.title = localize("button dropdown more actions", 'More Actions...');
this.dropdownButton.element.setAttribute('aria-haspopup', 'true');
this.dropdownButton.element.setAttribute('aria-expanded', 'false');
this.dropdownButton.element.classList.add('monaco-dropdown-button');
this.dropdownButton.icon = Codicon.dropDownButton;
this._register(this.dropdownButton.onDidClick(e => {
options.contextMenuProvider.showContextMenu({
getAnchor: () => this.dropdownButton.element,
getActions: () => [this.action, ...options.actions],
getActions: () => options.addPrimaryActionToDropdown === false ? [...options.actions] : [this.action, ...options.actions],
actionRunner: options.actionRunner,
onHide: () => this.dropdownButton.element.setAttribute('aria-expanded', 'false')
});
@@ -366,6 +382,8 @@ export class ButtonWithDropdown extends Disposable implements IButton {
set enabled(enabled: boolean) {
this.button.enabled = enabled;
this.dropdownButton.enabled = enabled;
this.element.classList.toggle('disabled', !enabled);
}
get enabled(): boolean {
@@ -375,6 +393,20 @@ export class ButtonWithDropdown extends Disposable implements IButton {
style(styles: IButtonStyles): void {
this.button.style(styles);
this.dropdownButton.style(styles);
// Separator
const border = styles.buttonBorder ? styles.buttonBorder.toString() : '';
this.separatorContainer.style.borderTopWidth = border ? '1px' : '';
this.separatorContainer.style.borderTopStyle = border ? 'solid' : '';
this.separatorContainer.style.borderTopColor = border;
this.separatorContainer.style.borderBottomWidth = border ? '1px' : '';
this.separatorContainer.style.borderBottomStyle = border ? 'solid' : '';
this.separatorContainer.style.borderBottomColor = border;
this.separatorContainer.style.backgroundColor = styles.buttonBackground?.toString() ?? '';
this.separator.style.backgroundColor = styles.buttonSeparator?.toString() ?? '';
}
focus(): void {
@@ -398,12 +430,10 @@ export class ButtonWithDescription extends Button implements IButtonWithDescript
this._labelElement = document.createElement('div');
this._labelElement.classList.add('monaco-button-label');
this._labelElement.tabIndex = -1;
this._element.appendChild(this._labelElement);
this._descriptionElement = document.createElement('div');
this._descriptionElement.classList.add('monaco-button-description');
this._descriptionElement.tabIndex = -1;
this._element.appendChild(this._descriptionElement);
}

View File

@@ -5,7 +5,6 @@
.context-view {
position: absolute;
z-index: 2500;
}
.context-view.fixed {
@@ -13,6 +12,5 @@
font-family: inherit;
font-size: 13px;
position: fixed;
z-index: 2500;
color: inherit;
}

View File

@@ -206,7 +206,7 @@ export class ContextView extends Disposable {
this.view.className = 'context-view';
this.view.style.top = '0px';
this.view.style.left = '0px';
this.view.style.zIndex = '2500';
this.view.style.zIndex = '2575';
this.view.style.position = this.useFixedPosition ? 'fixed' : 'absolute';
DOM.show(this.view);
@@ -220,9 +220,7 @@ export class ContextView extends Disposable {
this.doLayout();
// Focus
if (this.delegate.focus) {
this.delegate.focus();
}
this.delegate.focus?.();
}
getViewElement(): HTMLElement {
@@ -253,20 +251,25 @@ export class ContextView extends Disposable {
}
// Get anchor
let anchor = this.delegate!.getAnchor();
const anchor = this.delegate!.getAnchor();
// Compute around
let around: IView;
// Get the element's position and size (to anchor the view)
if (DOM.isHTMLElement(anchor)) {
let elementPosition = DOM.getDomNodePagePosition(anchor);
const elementPosition = DOM.getDomNodePagePosition(anchor);
// In areas where zoom is applied to the element or its ancestors, we need to adjust the size of the element
// e.g. The title bar has counter zoom behavior meaning it applies the inverse of zoom level.
// Window Zoom Level: 1.5, Title Bar Zoom: 1/1.5, Size Multiplier: 1.5
const zoom = DOM.getDomNodeZoomLevel(anchor);
around = {
top: elementPosition.top,
left: elementPosition.left,
width: elementPosition.width,
height: elementPosition.height
top: elementPosition.top * zoom,
left: elementPosition.left * zoom,
width: elementPosition.width * zoom,
height: elementPosition.height * zoom
};
} else {
around = {
@@ -359,7 +362,7 @@ export class ContextView extends Disposable {
}
}
let SHADOW_ROOT_CSS = /* css */ `
const SHADOW_ROOT_CSS = /* css */ `
:host {
all: initial; /* 1st rule so subsequent properties are reset. */
}

View File

@@ -165,7 +165,7 @@ export class Dialog extends Disposable {
}
private getIconAriaLabel(): string {
let typeLabel = nls.localize('dialogInfoMessage', 'Info');
const typeLabel = nls.localize('dialogInfoMessage', 'Info');
switch (this.options.type) {
case 'error':
nls.localize('dialogErrorMessage', 'Error');
@@ -427,13 +427,9 @@ export class Dialog extends Disposable {
this.element.style.backgroundColor = bgColor?.toString() ?? '';
this.element.style.border = border;
if (this.buttonBar) {
this.buttonBar.buttons.forEach(button => button.style(style));
}
this.buttonBar?.buttons.forEach(button => button.style(style));
if (this.checkbox) {
this.checkbox.style(style);
}
this.checkbox?.style(style);
if (fgColor && bgColor) {
const messageDetailColor = fgColor.transparent(.9);

View File

@@ -56,8 +56,10 @@ export class BaseDropdown extends ActionRunner {
for (const event of [EventType.MOUSE_DOWN, GestureEventType.Tap]) {
this._register(addDisposableListener(this._label, event, e => {
if (e instanceof MouseEvent && e.detail > 1) {
return; // prevent multiple clicks to open multiple context menus (https://github.com/microsoft/vscode/issues/41363)
if (e instanceof MouseEvent && (e.detail > 1 || e.button !== 0)) {
// prevent right click trigger to allow separate context menu (https://github.com/microsoft/vscode/issues/151064)
// prevent multiple clicks to open multiple context menus (https://github.com/microsoft/vscode/issues/41363)
return;
}
if (this.visible) {

View File

@@ -126,9 +126,22 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
};
}
this.updateTooltip();
this.updateEnabled();
}
override getTooltip(): string | undefined {
let title: string | null = null;
if (this.getAction().tooltip) {
title = this.getAction().tooltip;
} else if (this.getAction().label) {
title = this.getAction().label;
}
return title ?? undefined;
}
override setActionContext(newContext: unknown): void {
super.setActionContext(newContext);

View File

@@ -6,7 +6,7 @@
import * as dom from 'vs/base/browser/dom';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IToggleStyles } from 'vs/base/browser/ui/toggle/toggle';
import { IToggleStyles, Toggle } from 'vs/base/browser/ui/toggle/toggle';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { CaseSensitiveToggle, RegexToggle, WholeWordsToggle } from 'vs/base/browser/ui/findinput/findInputToggles';
import { HistoryInputBox, IInputBoxStyles, IInputValidator, IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox';
@@ -31,6 +31,7 @@ export interface IFindInputOptions extends IFindInputStyles {
readonly appendWholeWordsLabel?: string;
readonly appendRegexLabel?: string;
readonly history?: string[];
readonly additionalToggles?: Toggle[];
readonly showHistoryHint?: () => boolean;
}
@@ -74,6 +75,7 @@ export class FindInput extends Widget {
protected regex: RegexToggle;
protected wholeWords: WholeWordsToggle;
protected caseSensitive: CaseSensitiveToggle;
protected additionalToggles: Toggle[] = [];
public domNode: HTMLElement;
public inputBox: HistoryInputBox;
@@ -209,15 +211,11 @@ export class FindInput extends Widget {
this._onCaseSensitiveKeyDown.fire(e);
}));
if (this._showOptionButtons) {
this.inputBox.paddingRight = this.caseSensitive.width() + this.wholeWords.width() + this.regex.width();
}
// Arrow-Key support to navigate between options
let indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode];
const indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode];
this.onkeydown(this.domNode, (event: IKeyboardEvent) => {
if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) {
let index = indexes.indexOf(<HTMLElement>document.activeElement);
const index = indexes.indexOf(<HTMLElement>document.activeElement);
if (index >= 0) {
let newIndex: number = -1;
if (event.equals(KeyCode.RightArrow)) {
@@ -250,11 +248,37 @@ export class FindInput extends Widget {
this.controls.appendChild(this.wholeWords.domNode);
this.controls.appendChild(this.regex.domNode);
if (!this._showOptionButtons) {
this.caseSensitive.domNode.style.display = 'none';
this.wholeWords.domNode.style.display = 'none';
this.regex.domNode.style.display = 'none';
}
for (const toggle of options?.additionalToggles ?? []) {
this._register(toggle);
this.controls.appendChild(toggle.domNode);
this._register(toggle.onChange(viaKeyboard => {
this._onDidOptionChange.fire(viaKeyboard);
if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {
this.inputBox.focus();
}
}));
this.additionalToggles.push(toggle);
}
if (this.additionalToggles.length > 0) {
this.controls.style.display = 'block';
}
this.inputBox.paddingRight =
(this._showOptionButtons ? this.caseSensitive.width() + this.wholeWords.width() + this.regex.width() : 0)
+ this.additionalToggles.reduce((r, t) => r + t.width(), 0);
this.domNode.appendChild(this.controls);
if (parent) {
parent.appendChild(this.domNode);
}
parent?.appendChild(this.domNode);
this._register(dom.addDisposableListener(this.inputBox.inputElement, 'compositionstart', (e: CompositionEvent) => {
this.imeSessionInProgress = true;
@@ -284,6 +308,10 @@ export class FindInput extends Widget {
this.regex.enable();
this.wholeWords.enable();
this.caseSensitive.enable();
for (const toggle of this.additionalToggles) {
toggle.enable();
}
}
public disable(): void {
@@ -292,6 +320,10 @@ export class FindInput extends Widget {
this.regex.disable();
this.wholeWords.disable();
this.caseSensitive.disable();
for (const toggle of this.additionalToggles) {
toggle.disable();
}
}
public setFocusInputOnOptionClick(value: boolean): void {
@@ -358,6 +390,10 @@ export class FindInput extends Widget {
this.wholeWords.style(toggleStyles);
this.caseSensitive.style(toggleStyles);
for (const toggle of this.additionalToggles) {
toggle.style(toggleStyles);
}
const inputBoxStyles: IInputBoxStyles = {
inputBackground: this.inputBackground,
inputForeground: this.inputForeground,

View File

@@ -189,10 +189,10 @@ export class ReplaceInput extends Widget {
}
// Arrow-Key support to navigate between options
let indexes = [this.preserveCase.domNode];
const indexes = [this.preserveCase.domNode];
this.onkeydown(this.domNode, (event: IKeyboardEvent) => {
if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) {
let index = indexes.indexOf(<HTMLElement>document.activeElement);
const index = indexes.indexOf(<HTMLElement>document.activeElement);
if (index >= 0) {
let newIndex: number = -1;
if (event.equals(KeyCode.RightArrow)) {
@@ -218,16 +218,14 @@ export class ReplaceInput extends Widget {
});
let controls = document.createElement('div');
const controls = document.createElement('div');
controls.className = 'controls';
controls.style.display = this._showOptionButtons ? 'block' : 'none';
controls.appendChild(this.preserveCase.domNode);
this.domNode.appendChild(controls);
if (parent) {
parent.appendChild(this.domNode);
}
parent?.appendChild(this.domNode);
this.onkeydown(this.inputBox.inputElement, (e) => this._onKeyDown.fire(e));
this.onkeyup(this.inputBox.inputElement, (e) => this._onKeyUp.fire(e));
@@ -361,9 +359,7 @@ export class ReplaceInput extends Widget {
}
public showMessage(message: InputBoxMessage): void {
if (this.inputBox) {
this.inputBox.showMessage(message);
}
this.inputBox?.showMessage(message);
}
public clearMessage(): void {

View File

@@ -527,6 +527,16 @@ export class Grid<T extends IView = IView> extends Disposable {
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}.
*/
isViewSizeMaximized(view: T): boolean {
const location = this.getViewLocation(view);
return this.gridview.isViewSizeMaximized(location);
}
/**
* Get the size of a {@link IView view}.
*
@@ -748,6 +758,16 @@ export class SerializableGrid<T extends ISerializableView> extends Grid<T> {
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<T extends ISerializableView>(gridDescriptor: GridDescriptor<T>, options: IGridOptions = {}): SerializableGrid<T> {
return SerializableGrid.deserialize(createSerializedGrid(gridDescriptor), { fromJSON: view => view }, options);
}
/**
* Useful information in order to proportionally restore view sizes
* upon the very first layout call.
@@ -776,15 +796,21 @@ export class SerializableGrid<T extends ISerializableView> extends Grid<T> {
}
}
export type GridNodeDescriptor = { size?: number; groups?: GridNodeDescriptor[] };
export type GridDescriptor = { orientation: Orientation; groups?: GridNodeDescriptor[] };
export type GridLeafNodeDescriptor<T> = { size?: number; data?: any };
export type GridBranchNodeDescriptor<T> = { size?: number; groups: GridNodeDescriptor<T>[] };
export type GridNodeDescriptor<T> = GridBranchNodeDescriptor<T> | GridLeafNodeDescriptor<T>;
export type GridDescriptor<T> = { orientation: Orientation } & GridBranchNodeDescriptor<T>;
export function sanitizeGridNodeDescriptor(nodeDescriptor: GridNodeDescriptor, rootNode: boolean): void {
if (!rootNode && nodeDescriptor.groups && nodeDescriptor.groups.length <= 1) {
nodeDescriptor.groups = undefined;
function isGridBranchNodeDescriptor<T>(nodeDescriptor: GridNodeDescriptor<T>): nodeDescriptor is GridBranchNodeDescriptor<T> {
return !!(nodeDescriptor as GridBranchNodeDescriptor<T>).groups;
}
export function sanitizeGridNodeDescriptor<T>(nodeDescriptor: GridNodeDescriptor<T>, rootNode: boolean): void {
if (!rootNode && (nodeDescriptor as any).groups && (nodeDescriptor as any).groups.length <= 1) {
(nodeDescriptor as any).groups = undefined;
}
if (!nodeDescriptor.groups) {
if (!isGridBranchNodeDescriptor(nodeDescriptor)) {
return;
}
@@ -811,11 +837,11 @@ export function sanitizeGridNodeDescriptor(nodeDescriptor: GridNodeDescriptor, r
}
}
function createSerializedNode(nodeDescriptor: GridNodeDescriptor): ISerializedNode {
if (nodeDescriptor.groups) {
function createSerializedNode<T>(nodeDescriptor: GridNodeDescriptor<T>): ISerializedNode {
if (isGridBranchNodeDescriptor(nodeDescriptor)) {
return { type: 'branch', data: nodeDescriptor.groups.map(c => createSerializedNode(c)), size: nodeDescriptor.size! };
} else {
return { type: 'leaf', data: null, size: nodeDescriptor.size! };
return { type: 'leaf', data: nodeDescriptor.data, size: nodeDescriptor.size! };
}
}
@@ -843,7 +869,7 @@ function getDimensions(node: ISerializedNode, orientation: Orientation): { width
* Creates a new JSON object from a {@link GridDescriptor}, which can
* be deserialized by {@link SerializableGrid.deserialize}.
*/
export function createSerializedGrid(gridDescriptor: GridDescriptor): ISerializedGrid {
export function createSerializedGrid<T>(gridDescriptor: GridDescriptor<T>): ISerializedGrid {
sanitizeGridNodeDescriptor(gridDescriptor, true);
const root = createSerializedNode(gridDescriptor);

View File

@@ -592,6 +592,10 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
this.splitview.resizeView(index, size);
}
isChildSizeMaximized(index: number): boolean {
return this.splitview.isViewSizeMaximized(index);
}
distributeViewSizes(recursive = false): void {
this.splitview.distributeViewSizes();
@@ -857,9 +861,7 @@ class LeafNode implements ISplitView<ILayoutContext>, IDisposable {
set boundarySashes(boundarySashes: IRelativeBoundarySashes) {
this._boundarySashes = boundarySashes;
if (this.view.setBoundarySashes) {
this.view.setBoundarySashes(toAbsoluteBoundarySashes(boundarySashes, this.orientation));
}
this.view.setBoundarySashes?.(toAbsoluteBoundarySashes(boundarySashes, this.orientation));
}
layout(size: number, offset: number, ctx: ILayoutContext | undefined): void {
@@ -897,9 +899,7 @@ class LeafNode implements ISplitView<ILayoutContext>, IDisposable {
}
setVisible(visible: boolean): void {
if (this.view.setVisible) {
this.view.setVisible(visible);
}
this.view.setVisible?.(visible);
}
dispose(): void {
@@ -1435,6 +1435,27 @@ export class GridView implements IDisposable {
}
}
/**
* Returns whether all other {@link IView views} are at their minimum size.
*
* @param location The {@link GridLocation location} of the view.
*/
isViewSizeMaximized(location: GridLocation): boolean {
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].isChildSizeMaximized(location[i])) {
return false;
}
}
return true;
}
/**
* Distribute the size among all {@link IView views} within the entire
* grid or within a single {@link SplitView}.

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
import { IUpdatableHoverOptions } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { IDisposable } from 'vs/base/common/lifecycle';
@@ -12,7 +13,7 @@ export interface IHoverDelegateTarget extends IDisposable {
x?: number;
}
export interface IHoverDelegateOptions {
export interface IHoverDelegateOptions extends IUpdatableHoverOptions {
content: IMarkdownString | string | HTMLElement;
target: IHoverDelegateTarget | HTMLElement;
hoverPosition?: HoverPosition;

View File

@@ -33,6 +33,21 @@ export function setupNativeHover(htmlElement: HTMLElement, tooltip: string | ITo
export type IHoverContent = string | ITooltipMarkdownString | HTMLElement | undefined;
type IResolvedHoverContent = IMarkdownString | string | HTMLElement | undefined;
/**
* Copied from src\vs\workbench\services\hover\browser\hover.ts
* @deprecated Use IHoverService
*/
export interface IHoverAction {
label: string;
commandId: string;
iconClass?: string;
run(target: HTMLElement): void;
}
export interface IUpdatableHoverOptions {
actions?: IHoverAction[];
linkHandler?(url: string): void;
}
export interface ICustomHover extends IDisposable {
@@ -49,7 +64,7 @@ export interface ICustomHover extends IDisposable {
/**
* Updates the contents of the hover.
*/
update(tooltip: IHoverContent): void;
update(tooltip: IHoverContent, options?: IUpdatableHoverOptions): void;
}
@@ -61,7 +76,7 @@ class UpdatableHoverWidget implements IDisposable {
constructor(private hoverDelegate: IHoverDelegate, private target: IHoverDelegateTarget | HTMLElement, private fadeInAnimation: boolean) {
}
async update(content: IHoverContent, focus?: boolean): Promise<void> {
async update(content: IHoverContent, focus?: boolean, options?: IUpdatableHoverOptions): Promise<void> {
if (this._cancellationTokenSource) {
// there's an computation ongoing, cancel it
this._cancellationTokenSource.dispose(true);
@@ -99,10 +114,10 @@ class UpdatableHoverWidget implements IDisposable {
}
}
this.show(resolvedContent, focus);
this.show(resolvedContent, focus, options);
}
private show(content: IResolvedHoverContent, focus?: boolean): void {
private show(content: IResolvedHoverContent, focus?: boolean, options?: IUpdatableHoverOptions): void {
const oldHoverWidget = this._hoverWidget;
if (this.hasContent(content)) {
@@ -111,7 +126,8 @@ class UpdatableHoverWidget implements IDisposable {
target: this.target,
showPointer: this.hoverDelegate.placement === 'element',
hoverPosition: HoverPosition.BELOW,
skipFadeInAnimation: !this.fadeInAnimation || !!oldHoverWidget // do not fade in if the hover is already showing
skipFadeInAnimation: !this.fadeInAnimation || !!oldHoverWidget, // do not fade in if the hover is already showing
...options
};
this._hoverWidget = this.hoverDelegate.showHover(hoverOptions, focus);
@@ -142,7 +158,7 @@ class UpdatableHoverWidget implements IDisposable {
}
}
export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IHoverContent): ICustomHover {
export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IHoverContent, options?: IUpdatableHoverOptions): ICustomHover {
let hoverPreparation: IDisposable | undefined;
let hoverWidget: UpdatableHoverWidget | undefined;
@@ -163,7 +179,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM
return new TimeoutTimer(async () => {
if (!hoverWidget || hoverWidget.isDisposed) {
hoverWidget = new UpdatableHoverWidget(hoverDelegate, target || htmlElement, delay > 0);
await hoverWidget.update(content, focus);
await hoverWidget.update(content, focus, options);
}
}, delay);
};
@@ -208,9 +224,9 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM
hide: () => {
hideHover(true, true);
},
update: async newContent => {
update: async (newContent, hoverOptions) => {
content = newContent;
await hoverWidget?.update(content);
await hoverWidget?.update(content, undefined, hoverOptions);
},
dispose: () => {
mouseOverDomEmitter.dispose();

View File

@@ -171,9 +171,9 @@ export class InputBox extends Widget {
this.element = dom.append(container, $('.monaco-inputbox.idle'));
let tagName = this.options.flexibleHeight ? 'textarea' : 'input';
const tagName = this.options.flexibleHeight ? 'textarea' : 'input';
let wrapper = dom.append(this.element, $('.ibwrapper'));
const wrapper = dom.append(this.element, $('.ibwrapper'));
this.input = dom.append(wrapper, $(tagName + '.input.empty'));
this.input.setAttribute('autocorrect', 'off');
this.input.setAttribute('autocapitalize', 'off');
@@ -257,14 +257,14 @@ export class InputBox extends Widget {
this.applyStyles();
}
private onBlur(): void {
protected onBlur(): void {
this._hideMessage();
if (this.options.showPlaceholderOnFocus) {
this.input.setAttribute('placeholder', '');
}
}
private onFocus(): void {
protected onFocus(): void {
this._showMessage();
if (this.options.showPlaceholderOnFocus) {
this.input.setAttribute('placeholder', this.placeholder || '');
@@ -491,7 +491,7 @@ export class InputBox extends Widget {
}
let div: HTMLElement;
let layout = () => div.style.width = dom.getTotalWidth(this.element) + 'px';
const layout = () => div.style.width = dom.getTotalWidth(this.element) + 'px';
this.contextViewProvider.showContextView({
getAnchor: () => this.element,
@@ -672,11 +672,19 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge
private readonly history: HistoryNavigator<string>;
private observer: MutationObserver | undefined;
private readonly _onDidFocus = this._register(new Emitter<void>());
readonly onDidFocus = this._onDidFocus.event;
private readonly _onDidBlur = this._register(new Emitter<void>());
readonly onDidBlur = this._onDidBlur.event;
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options: IHistoryInputOptions) {
super(container, contextViewProvider, options);
const NLS_PLACEHOLDER_HISTORY_HINT = nls.localize({ key: 'history.inputbox.hint', comment: ['Text will be prefixed with \u21C5 plus a single space, then used as a hint where input field keeps history'] }, "for history");
const NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX = ` or \u21C5 ${NLS_PLACEHOLDER_HISTORY_HINT}`;
const NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS = ` (\u21C5 ${NLS_PLACEHOLDER_HISTORY_HINT})`;
super(container, contextViewProvider, options);
this.history = new HistoryNavigator<string>(options.history, 100);
// Function to append the history suffix to the placeholder if necessary
@@ -781,6 +789,16 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge
this.history.clear();
}
protected override onBlur(): void {
super.onBlur();
this._onDidBlur.fire();
}
protected override onFocus(): void {
super.onFocus();
this._onDidFocus.fire();
}
private getCurrentValue(): string | null {
let currentValue = this.history.current();
if (!currentValue) {

View File

@@ -89,7 +89,7 @@ export class KeybindingLabel implements IThemable {
this.clear();
if (this.keybinding) {
let [firstPart, chordPart] = this.keybinding.getParts();
const [firstPart, chordPart] = this.keybinding.getParts();
if (firstPart) {
this.renderPart(this.domNode, firstPart, this.matches ? this.matches.firstPart : null);
}

View File

@@ -65,72 +65,7 @@
z-index: 1000;
}
/* Type filter */
.monaco-list-type-filter {
display: flex;
align-items: center;
position: absolute;
border-radius: 2px;
padding: 0px 3px;
max-width: calc(100% - 10px);
text-overflow: ellipsis;
overflow: hidden;
text-align: right;
box-sizing: border-box;
cursor: all-scroll;
font-size: 13px;
line-height: 18px;
height: 20px;
z-index: 1;
top: 4px;
}
.monaco-list-type-filter.dragging {
transition: top 0.2s, left 0.2s;
}
.monaco-list-type-filter.ne {
right: 4px;
}
.monaco-list-type-filter.nw {
left: 4px;
}
.monaco-list-type-filter > .controls {
display: flex;
align-items: center;
box-sizing: border-box;
transition: width 0.2s;
width: 0;
}
.monaco-list-type-filter.dragging > .controls,
.monaco-list-type-filter:hover > .controls {
width: 36px;
}
.monaco-list-type-filter > .controls > * {
border: none;
box-sizing: border-box;
-webkit-appearance: none;
-moz-appearance: none;
background: none;
width: 16px;
height: 16px;
flex-shrink: 0;
margin: 0;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.monaco-list-type-filter > .controls > .filter {
margin-left: 4px;
}
/* Filter */
.monaco-list-type-filter-message {
position: absolute;
@@ -149,13 +84,3 @@
.monaco-list-type-filter-message:empty {
display: none;
}
/* Electron */
.monaco-list-type-filter {
cursor: grab;
}
.monaco-list-type-filter.dragging {
cursor: grabbing;
}

View File

@@ -12,7 +12,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IThemable } from 'vs/base/common/styler';
import 'vs/css!./list';
import { IListContextMenuEvent, IListEvent, IListMouseEvent, IListRenderer, IListVirtualDelegate } from './list';
import { IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IListStyles, List } from './listWidget';
import { IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IListStyles, List, TypeNavigationMode } from './listWidget';
export interface IPagedRenderer<TElement, TTemplateData> extends IListRenderer<TElement, TTemplateData> {
renderPlaceholder(index: number, templateData: TTemplateData): void;
@@ -95,8 +95,8 @@ class PagedAccessibilityProvider<T> implements IListAccessibilityProvider<number
}
export interface IPagedListOptions<T> {
readonly enableKeyboardNavigation?: boolean;
readonly automaticKeyboardNavigation?: boolean;
readonly typeNavigationEnabled?: boolean;
readonly typeNavigationMode?: TypeNavigationMode;
readonly ariaLabel?: string;
readonly keyboardSupport?: boolean;
readonly multipleSelectionSupport?: boolean;
@@ -282,8 +282,8 @@ export class PagedList<T> implements IThemable, IDisposable {
this.list.layout(height, width);
}
toggleKeyboardNavigation(): void {
this.list.toggleKeyboardNavigation();
triggerTypeNavigation(): void {
this.list.triggerTypeNavigation();
}
reveal(index: number, relativeTop?: number): void {

View File

@@ -15,7 +15,6 @@ import { Delayer, disposableTimeout } from 'vs/base/common/async';
import { memoize } from 'vs/base/common/decorators';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { getOrDefault } from 'vs/base/common/objects';
import { IRange, Range } from 'vs/base/common/range';
import { INewScrollDimensions, Scrollable, ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable';
import { ISpliceable } from 'vs/base/common/sequence';
@@ -257,7 +256,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private readonly disposables: DisposableStore = new DisposableStore();
private readonly _onDidChangeContentHeight = new Emitter<number>();
readonly onDidChangeContentHeight: Event<number> = Event.latch(this._onDidChangeContentHeight.event);
readonly onDidChangeContentHeight: Event<number> = Event.latch(this._onDidChangeContentHeight.event, undefined, this.disposables);
get contentHeight(): number { return this.rangeMap.size; }
get onDidScroll(): Event<ScrollEvent> { return this.scrollableElement.onScroll; }
@@ -325,7 +324,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.domNode.classList.toggle('mouse-support', typeof options.mouseSupport === 'boolean' ? options.mouseSupport : true);
this._horizontalScrolling = getOrDefault(options, o => o.horizontalScrolling, DefaultOptions.horizontalScrolling);
this._horizontalScrolling = options.horizontalScrolling ?? DefaultOptions.horizontalScrolling;
this.domNode.classList.toggle('horizontal-scrolling', this._horizontalScrolling);
this.additionalScrollHeight = typeof options.additionalScrollHeight === 'undefined' ? 0 : options.additionalScrollHeight;
@@ -335,7 +334,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.rowsContainer = document.createElement('div');
this.rowsContainer.className = 'monaco-list-rows';
const transformOptimization = getOrDefault(options, o => o.transformOptimization, DefaultOptions.transformOptimization);
const transformOptimization = options.transformOptimization ?? DefaultOptions.transformOptimization;
if (transformOptimization) {
this.rowsContainer.style.transform = 'translate3d(0px, 0px, 0px)';
}
@@ -344,14 +343,14 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.scrollable = new Scrollable({
forceIntegerValues: true,
smoothScrollDuration: getOrDefault(options, o => o.smoothScrolling, false) ? 125 : 0,
smoothScrollDuration: (options.smoothScrolling ?? false) ? 125 : 0,
scheduleAtNextAnimationFrame: cb => scheduleAtNextAnimationFrame(cb)
});
this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, {
alwaysConsumeMouseWheel: getOrDefault(options, o => o.alwaysConsumeMouseWheel, DefaultOptions.alwaysConsumeMouseWheel),
alwaysConsumeMouseWheel: options.alwaysConsumeMouseWheel ?? DefaultOptions.alwaysConsumeMouseWheel,
horizontal: ScrollbarVisibility.Auto,
vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows),
vertical: options.verticalScrollMode ?? DefaultOptions.verticalScrollMode,
useShadows: options.useShadows ?? DefaultOptions.useShadows,
mouseWheelScrollSensitivity: options.mouseWheelScrollSensitivity,
fastScrollSensitivity: options.fastScrollSensitivity
}, this.scrollable));
@@ -371,10 +370,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.disposables.add(addDisposableListener(this.domNode, 'dragleave', e => this.onDragLeave(this.toDragEvent(e))));
this.disposables.add(addDisposableListener(this.domNode, 'dragend', e => this.onDragEnd(e)));
this.setRowLineHeight = getOrDefault(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight);
this.setRowHeight = getOrDefault(options, o => o.setRowHeight, DefaultOptions.setRowHeight);
this.supportDynamicHeights = getOrDefault(options, o => o.supportDynamicHeights, DefaultOptions.supportDynamicHeights);
this.dnd = getOrDefault<IListViewOptions<T>, IListViewDragAndDrop<T>>(options, o => o.dnd, DefaultOptions.dnd);
this.setRowLineHeight = options.setRowLineHeight ?? DefaultOptions.setRowLineHeight;
this.setRowHeight = options.setRowHeight ?? DefaultOptions.setRowHeight;
this.supportDynamicHeights = options.supportDynamicHeights ?? DefaultOptions.supportDynamicHeights;
this.dnd = options.dnd ?? DefaultOptions.dnd;
this.layout();
}
@@ -705,7 +704,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
}
layout(height?: number, width?: number): void {
let scrollDimensions: INewScrollDimensions = {
const scrollDimensions: INewScrollDimensions = {
height: typeof height === 'number' ? height : getContentHeight(this.domNode)
};
@@ -813,9 +812,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
throw new Error(`No renderer found for template id ${item.templateId}`);
}
if (renderer) {
renderer.renderElement(item.element, index, item.row.templateData, item.size);
}
renderer?.renderElement(item.element, index, item.row.templateData, item.size);
const uri = this.dnd.getDragURI(item.element);
item.dragStartDisposable.dispose();
@@ -938,17 +935,17 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
// Events
@memoize get onMouseClick(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'click')).event, e => this.toMouseEvent(e)); }
@memoize get onMouseDblClick(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'dblclick')).event, e => this.toMouseEvent(e)); }
@memoize get onMouseMiddleClick(): Event<IListMouseEvent<T>> { return Event.filter(Event.map(this.disposables.add(new DomEmitter(this.domNode, 'auxclick')).event, e => this.toMouseEvent(e as MouseEvent)), e => e.browserEvent.button === 1); }
@memoize get onMouseUp(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mouseup')).event, e => this.toMouseEvent(e)); }
@memoize get onMouseDown(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mousedown')).event, e => this.toMouseEvent(e)); }
@memoize get onMouseOver(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mouseover')).event, e => this.toMouseEvent(e)); }
@memoize get onMouseMove(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mousemove')).event, e => this.toMouseEvent(e)); }
@memoize get onMouseOut(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mouseout')).event, e => this.toMouseEvent(e)); }
@memoize get onContextMenu(): Event<IListMouseEvent<T> | IListGestureEvent<T>> { return Event.any(Event.map(this.disposables.add(new DomEmitter(this.domNode, 'contextmenu')).event, e => this.toMouseEvent(e)), Event.map(this.disposables.add(new DomEmitter(this.domNode, TouchEventType.Contextmenu)).event as Event<GestureEvent>, e => this.toGestureEvent(e))); }
@memoize get onTouchStart(): Event<IListTouchEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'touchstart')).event, e => this.toTouchEvent(e)); }
@memoize get onTap(): Event<IListGestureEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.rowsContainer, TouchEventType.Tap)).event, e => this.toGestureEvent(e as GestureEvent)); }
@memoize get onMouseClick(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'click')).event, e => this.toMouseEvent(e), this.disposables); }
@memoize get onMouseDblClick(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'dblclick')).event, e => this.toMouseEvent(e), this.disposables); }
@memoize get onMouseMiddleClick(): Event<IListMouseEvent<T>> { return Event.filter(Event.map(this.disposables.add(new DomEmitter(this.domNode, 'auxclick')).event, e => this.toMouseEvent(e as MouseEvent), this.disposables), e => e.browserEvent.button === 1, this.disposables); }
@memoize get onMouseUp(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mouseup')).event, e => this.toMouseEvent(e), this.disposables); }
@memoize get onMouseDown(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mousedown')).event, e => this.toMouseEvent(e), this.disposables); }
@memoize get onMouseOver(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mouseover')).event, e => this.toMouseEvent(e), this.disposables); }
@memoize get onMouseMove(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mousemove')).event, e => this.toMouseEvent(e), this.disposables); }
@memoize get onMouseOut(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mouseout')).event, e => this.toMouseEvent(e), this.disposables); }
@memoize get onContextMenu(): Event<IListMouseEvent<T> | IListGestureEvent<T>> { return Event.any(Event.map(this.disposables.add(new DomEmitter(this.domNode, 'contextmenu')).event, e => this.toMouseEvent(e), this.disposables), Event.map(this.disposables.add(new DomEmitter(this.domNode, TouchEventType.Contextmenu)).event as Event<GestureEvent>, e => this.toGestureEvent(e), this.disposables)); }
@memoize get onTouchStart(): Event<IListTouchEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'touchstart')).event, e => this.toTouchEvent(e), this.disposables); }
@memoize get onTap(): Event<IListGestureEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.rowsContainer, TouchEventType.Tap)).event, e => this.toGestureEvent(e as GestureEvent), this.disposables); }
private toMouseEvent(browserEvent: MouseEvent): IListMouseEvent<T> {
const index = this.getItemIndexFromEventTarget(browserEvent.target || null);
@@ -1032,9 +1029,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.currentDragData = new ElementsDragAndDropData(elements);
StaticDND.CurrentDragAndDropData = new ExternalElementsDragAndDropData(elements);
if (this.dnd.onDragStart) {
this.dnd.onDragStart(this.currentDragData, event);
}
this.dnd.onDragStart?.(this.currentDragData, event);
}
private onDragOver(event: IListDragEvent<T>): boolean {
@@ -1114,9 +1109,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const item = this.items[index]!;
item.dropTarget = true;
if (item.row) {
item.row.domNode.classList.add('drop-target');
}
item.row?.domNode.classList.add('drop-target');
}
this.currentDragFeedbackDisposable = toDisposable(() => {
@@ -1124,9 +1117,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const item = this.items[index]!;
item.dropTarget = false;
if (item.row) {
item.row.domNode.classList.remove('drop-target');
}
item.row?.domNode.classList.remove('drop-target');
}
});
}
@@ -1169,9 +1160,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.currentDragData = undefined;
StaticDND.CurrentDragAndDropData = undefined;
if (this.dnd.onDragEnd) {
this.dnd.onDragEnd(event);
}
this.dnd.onDragEnd?.(event);
}
private clearDragOverFeedback(): void {
@@ -1364,7 +1353,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const size = item.size;
if (!this.setRowHeight && item.row) {
let newSize = item.row.domNode.offsetHeight;
const newSize = item.row.domNode.offsetHeight;
item.size = newSize;
item.lastDynamicHeightWidth = this.renderWidth;
return newSize - size;
@@ -1379,16 +1368,12 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
if (renderer) {
renderer.renderElement(item.element, index, row.templateData, undefined);
if (renderer.disposeElement) {
renderer.disposeElement(item.element, index, row.templateData, undefined);
}
renderer.disposeElement?.(item.element, index, row.templateData, undefined);
}
item.size = row.domNode.offsetHeight;
if (this.virtualDelegate.setDynamicHeight) {
this.virtualDelegate.setDynamicHeight(item.element, item.size);
}
this.virtualDelegate.setDynamicHeight?.(item.element, item.size);
item.lastDynamicHeightWidth = this.renderWidth;
this.rowsContainer.removeChild(row.domNode);
@@ -1429,9 +1414,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
if (item.row) {
const renderer = this.renderers.get(item.row.templateId);
if (renderer) {
if (renderer.disposeElement) {
renderer.disposeElement(item.element, -1, item.row.templateData, undefined);
}
renderer.disposeElement?.(item.element, -1, item.row.templateData, undefined);
renderer.disposeTemplate(item.row.templateData);
}
}

View File

@@ -9,6 +9,7 @@ import { DomEmitter, stopEvent } from 'vs/base/browser/event';
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Gesture } from 'vs/base/browser/touch';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput';
import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice';
import { ScrollableElementChangeOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
import { binarySearch, firstOrDefault, range } from 'vs/base/common/arrays';
@@ -258,6 +259,23 @@ export function isMonacoEditor(e: HTMLElement): boolean {
return isMonacoEditor(e.parentElement);
}
export function isButton(e: HTMLElement): boolean {
if ((e.tagName === 'A' && e.classList.contains('monaco-button')) ||
(e.tagName === 'DIV' && e.classList.contains('monaco-button-dropdown'))) {
return true;
}
if (e.classList.contains('monaco-list')) {
return false;
}
if (!e.parentElement) {
return false;
}
return isButton(e.parentElement);
}
class KeyboardController<T> implements IDisposable {
private readonly disposables = new DisposableStore();
@@ -265,9 +283,9 @@ class KeyboardController<T> implements IDisposable {
@memoize
private get onKeyDown(): Event.IChainableEvent<StandardKeyboardEvent> {
return Event.chain(this.disposables.add(new DomEmitter(this.view.domNode, 'keydown')).event)
return this.disposables.add(Event.chain(this.disposables.add(new DomEmitter(this.view.domNode, 'keydown')).event)
.filter(e => !isInputElement(e.target as HTMLElement))
.map(e => new StandardKeyboardEvent(e));
.map(e => new StandardKeyboardEvent(e)));
}
constructor(
@@ -367,7 +385,12 @@ class KeyboardController<T> implements IDisposable {
}
}
enum TypeLabelControllerState {
export enum TypeNavigationMode {
Automatic,
Trigger
}
enum TypeNavigationControllerState {
Idle,
Typing
}
@@ -385,12 +408,12 @@ export const DefaultKeyboardNavigationDelegate = new class implements IKeyboardN
}
};
class TypeLabelController<T> implements IDisposable {
class TypeNavigationController<T> implements IDisposable {
private enabled = false;
private state: TypeLabelControllerState = TypeLabelControllerState.Idle;
private state: TypeNavigationControllerState = TypeNavigationControllerState.Idle;
private automaticKeyboardNavigation = true;
private mode = TypeNavigationMode.Automatic;
private triggered = false;
private previouslyFocused = -1;
@@ -401,26 +424,23 @@ class TypeLabelController<T> implements IDisposable {
private list: List<T>,
private view: ListView<T>,
private keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider<T>,
private keyboardNavigationEventFilter: IKeyboardNavigationEventFilter,
private delegate: IKeyboardNavigationDelegate
) {
this.updateOptions(list.options);
}
updateOptions(options: IListOptions<T>): void {
const enableKeyboardNavigation = typeof options.enableKeyboardNavigation === 'undefined' ? true : !!options.enableKeyboardNavigation;
if (enableKeyboardNavigation) {
if (options.typeNavigationEnabled ?? true) {
this.enable();
} else {
this.disable();
}
if (typeof options.automaticKeyboardNavigation !== 'undefined') {
this.automaticKeyboardNavigation = options.automaticKeyboardNavigation;
}
this.mode = options.typeNavigationMode ?? TypeNavigationMode.Automatic;
}
toggle(): void {
trigger(): void {
this.triggered = !this.triggered;
}
@@ -429,21 +449,27 @@ class TypeLabelController<T> implements IDisposable {
return;
}
const onChar = Event.chain(this.enabledDisposables.add(new DomEmitter(this.view.domNode, 'keydown')).event)
let typing = false;
const onChar = this.enabledDisposables.add(Event.chain(this.enabledDisposables.add(new DomEmitter(this.view.domNode, 'keydown')).event))
.filter(e => !isInputElement(e.target as HTMLElement))
.filter(() => this.automaticKeyboardNavigation || this.triggered)
.filter(() => this.mode === TypeNavigationMode.Automatic || this.triggered)
.map(event => new StandardKeyboardEvent(event))
.filter(e => typing || this.keyboardNavigationEventFilter(e))
.filter(e => this.delegate.mightProducePrintableCharacter(e))
.forEach(e => e.preventDefault())
.forEach(stopEvent)
.map(event => event.browserEvent.key)
.event;
const onClear = Event.debounce<string, null>(onChar, () => null, 800);
const onInput = Event.reduce<string | null, string | null>(Event.any(onChar, onClear), (r, i) => i === null ? null : ((r || '') + i));
const onClear = Event.debounce<string, null>(onChar, () => null, 800, undefined, undefined, this.enabledDisposables);
const onInput = Event.reduce<string | null, string | null>(Event.any(onChar, onClear), (r, i) => i === null ? null : ((r || '') + i), undefined, this.enabledDisposables);
onInput(this.onInput, this, this.enabledDisposables);
onClear(this.onClear, this, this.enabledDisposables);
onChar(() => typing = true, undefined, this.enabledDisposables);
onClear(() => typing = false, undefined, this.enabledDisposables);
this.enabled = true;
this.triggered = false;
}
@@ -473,15 +499,15 @@ class TypeLabelController<T> implements IDisposable {
private onInput(word: string | null): void {
if (!word) {
this.state = TypeLabelControllerState.Idle;
this.state = TypeNavigationControllerState.Idle;
this.triggered = false;
return;
}
const focus = this.list.getFocus();
const start = focus.length > 0 ? focus[0] : 0;
const delta = this.state === TypeLabelControllerState.Idle ? 1 : 0;
this.state = TypeLabelControllerState.Typing;
const delta = this.state === TypeNavigationControllerState.Idle ? 1 : 0;
this.state = TypeNavigationControllerState.Typing;
for (let i = 0; i < this.list.length; i++) {
const index = (start + i + delta) % this.list.length;
@@ -512,7 +538,7 @@ class DOMFocusController<T> implements IDisposable {
private list: List<T>,
private view: ListView<T>
) {
const onKeyDown = Event.chain(this.disposables.add(new DomEmitter(view.domNode, 'keydown')).event)
const onKeyDown = this.disposables.add(Event.chain(this.disposables.add(new DomEmitter(view.domNode, 'keydown')).event))
.filter(e => !isInputElement(e.target as HTMLElement))
.map(e => new StandardKeyboardEvent(e));
@@ -801,6 +827,10 @@ export class DefaultStyleController implements IStyleController {
content.push(`.monaco-list${suffix}:focus .monaco-list-row.selected .codicon { color: ${styles.listActiveSelectionIconForeground}; }`);
}
if (styles.listFocusAndSelectionOutline) {
content.push(`.monaco-list${suffix}:focus .monaco-list-row.selected { outline-color: ${styles.listFocusAndSelectionOutline} !important; }`);
}
if (styles.listFocusAndSelectionBackground) {
content.push(`
.monaco-drag-image,
@@ -874,22 +904,6 @@ export class DefaultStyleController implements IStyleController {
`);
}
if (styles.listFilterWidgetBackground) {
content.push(`.monaco-list-type-filter { background-color: ${styles.listFilterWidgetBackground} }`);
}
if (styles.listFilterWidgetOutline) {
content.push(`.monaco-list-type-filter { border: 1px solid ${styles.listFilterWidgetOutline}; }`);
}
if (styles.listFilterWidgetNoMatchesOutline) {
content.push(`.monaco-list-type-filter.no-matches { border: 1px solid ${styles.listFilterWidgetNoMatchesOutline}; }`);
}
if (styles.listMatchesShadow) {
content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`);
}
if (styles.tableColumnsBorder) {
content.push(`
.monaco-table:hover > .monaco-split-view2,
@@ -912,9 +926,13 @@ export class DefaultStyleController implements IStyleController {
}
}
export interface IKeyboardNavigationEventFilter {
(e: StandardKeyboardEvent): boolean;
}
export interface IListOptionsUpdate extends IListViewOptionsUpdate {
readonly enableKeyboardNavigation?: boolean;
readonly automaticKeyboardNavigation?: boolean;
readonly typeNavigationEnabled?: boolean;
readonly typeNavigationMode?: TypeNavigationMode;
readonly multipleSelectionSupport?: boolean;
}
@@ -927,6 +945,7 @@ export interface IListOptions<T> extends IListOptionsUpdate {
readonly multipleSelectionController?: IMultipleSelectionController<T>;
readonly styleController?: (suffix: string) => IStyleController;
readonly accessibilityProvider?: IListAccessibilityProvider<T>;
readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter;
// list view options
readonly useShadows?: boolean;
@@ -943,13 +962,14 @@ export interface IListOptions<T> extends IListOptionsUpdate {
readonly alwaysConsumeMouseWheel?: boolean;
}
export interface IListStyles {
export interface IListStyles extends IFindInputStyles {
listBackground?: Color;
listFocusBackground?: Color;
listFocusForeground?: Color;
listActiveSelectionBackground?: Color;
listActiveSelectionForeground?: Color;
listActiveSelectionIconForeground?: Color;
listFocusAndSelectionOutline?: Color;
listFocusAndSelectionBackground?: Color;
listFocusAndSelectionForeground?: Color;
listInactiveSelectionBackground?: Color;
@@ -967,7 +987,8 @@ export interface IListStyles {
listFilterWidgetBackground?: Color;
listFilterWidgetOutline?: Color;
listFilterWidgetNoMatchesOutline?: Color;
listMatchesShadow?: Color;
listFilterWidgetShadow?: Color;
listMatchesShadow?: Color; // {{SQL CARBON EDIT}}
treeIndentGuidesStroke?: Color;
tableColumnsBorder?: Color;
tableOddRowsBackgroundColor?: Color;
@@ -978,6 +999,7 @@ const defaultStyles: IListStyles = {
listActiveSelectionBackground: Color.fromHex('#0E639C'),
listActiveSelectionForeground: Color.fromHex('#FFFFFF'),
listActiveSelectionIconForeground: Color.fromHex('#FFFFFF'),
listFocusAndSelectionOutline: Color.fromHex('#90C2F9'),
listFocusAndSelectionBackground: Color.fromHex('#094771'),
listFocusAndSelectionForeground: Color.fromHex('#FFFFFF'),
listInactiveSelectionBackground: Color.fromHex('#3F3F46'),
@@ -1109,9 +1131,7 @@ class PipelineRenderer<T> implements IListRenderer<T, any> {
let i = 0;
for (const renderer of this.renderers) {
if (renderer.disposeElement) {
renderer.disposeElement(element, index, templateData[i], height);
}
renderer.disposeElement?.(element, index, templateData[i], height);
i += 1;
}
@@ -1182,9 +1202,7 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
}
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
if (this.dnd.onDragStart) {
this.dnd.onDragStart(data, originalEvent);
}
this.dnd.onDragStart?.(data, originalEvent);
}
onDragOver(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): boolean | IListDragOverReaction {
@@ -1196,9 +1214,7 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
}
onDragEnd(originalEvent: DragEvent): void {
if (this.dnd.onDragEnd) {
this.dnd.onDragEnd(originalEvent);
}
this.dnd.onDragEnd?.(originalEvent);
}
drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void {
@@ -1230,7 +1246,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
protected view: ListView<T>;
private spliceable: ISpliceable<T>;
private styleController: IStyleController;
private typeLabelController?: TypeLabelController<T>;
private typeNavigationController?: TypeNavigationController<T>;
private accessibilityProvider?: IListAccessibilityProvider<T>;
private keyboardController: KeyboardController<T> | undefined;
private mouseController: MouseController<T>;
@@ -1239,11 +1255,11 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
protected readonly disposables = new DisposableStore();
@memoize get onDidChangeFocus(): Event<IListEvent<T>> {
return Event.map(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e));
return Event.map(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e), this.disposables);
}
@memoize get onDidChangeSelection(): Event<IListEvent<T>> {
return Event.map(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e));
return Event.map(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e), this.disposables);
}
get domId(): string { return this.view.domId; }
@@ -1270,14 +1286,14 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
@memoize get onContextMenu(): Event<IListContextMenuEvent<T>> {
let didJustPressContextMenuKey = false;
const fromKeyDown = Event.chain(this.disposables.add(new DomEmitter(this.view.domNode, 'keydown')).event)
const fromKeyDown = this.disposables.add(Event.chain(this.disposables.add(new DomEmitter(this.view.domNode, 'keydown')).event))
.map(e => new StandardKeyboardEvent(e))
.filter(e => didJustPressContextMenuKey = e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10))
.map(stopEvent)
.filter(() => false)
.event as Event<any>;
const fromKeyUp = Event.chain(this.disposables.add(new DomEmitter(this.view.domNode, 'keyup')).event)
const fromKeyUp = this.disposables.add(Event.chain(this.disposables.add(new DomEmitter(this.view.domNode, 'keyup')).event))
.forEach(() => didJustPressContextMenuKey = false)
.map(e => new StandardKeyboardEvent(e))
.filter(e => e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10))
@@ -1291,7 +1307,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
})
.event;
const fromMouse = Event.chain(this.view.onContextMenu)
const fromMouse = this.disposables.add(Event.chain(this.view.onContextMenu))
.filter(_ => !didJustPressContextMenuKey)
.map(({ element, index, browserEvent }) => ({ element, index, anchor: { x: browserEvent.pageX + 1, y: browserEvent.pageY }, browserEvent }))
.event;
@@ -1329,9 +1345,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
if (this.accessibilityProvider) {
baseRenderers.push(new AccessibiltyRenderer<T>(this.accessibilityProvider));
if (this.accessibilityProvider.onDidChangeActiveDescendant) {
this.accessibilityProvider.onDidChangeActiveDescendant(this.onDidChangeActiveDescendant, this, this.disposables);
}
this.accessibilityProvider.onDidChangeActiveDescendant?.(this.onDidChangeActiveDescendant, this, this.disposables);
}
renderers = renderers.map(r => new PipelineRenderer(r.templateId, [...baseRenderers, r]));
@@ -1373,8 +1387,8 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
if (_options.keyboardNavigationLabelProvider) {
const delegate = _options.keyboardNavigationDelegate || DefaultKeyboardNavigationDelegate;
this.typeLabelController = new TypeLabelController(this, this.view, _options.keyboardNavigationLabelProvider, delegate);
this.disposables.add(this.typeLabelController);
this.typeNavigationController = new TypeNavigationController(this, this.view, _options.keyboardNavigationLabelProvider, _options.keyboardNavigationEventFilter ?? (() => true), delegate);
this.disposables.add(this.typeNavigationController);
}
this.mouseController = this.createMouseController(_options);
@@ -1399,9 +1413,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
updateOptions(optionsUpdate: IListOptionsUpdate = {}): void {
this._options = { ...this._options, ...optionsUpdate };
if (this.typeLabelController) {
this.typeLabelController.updateOptions(this._options);
}
this.typeNavigationController?.updateOptions(this._options);
if (this._options.multipleSelectionController !== undefined) {
if (this._options.multipleSelectionSupport) {
@@ -1517,9 +1529,9 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
this.view.layout(height, width);
}
toggleKeyboardNavigation(): void {
if (this.typeLabelController) {
this.typeLabelController.toggle();
triggerTypeNavigation(): void {
if (this.typeNavigationController) {
this.typeNavigationController.trigger();
}
}
@@ -1598,20 +1610,25 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
async focusNextPage(browserEvent?: UIEvent, filter?: (element: T) => boolean): Promise<void> {
let lastPageIndex = this.view.indexAt(this.view.getScrollTop() + this.view.renderHeight);
lastPageIndex = lastPageIndex === 0 ? 0 : lastPageIndex - 1;
const lastPageElement = this.view.element(lastPageIndex);
const currentlyFocusedElement = this.getFocusedElements()[0];
const currentlyFocusedElementIndex = this.getFocus()[0];
if (currentlyFocusedElement !== lastPageElement) {
if (currentlyFocusedElementIndex !== lastPageIndex && (currentlyFocusedElementIndex === undefined || lastPageIndex > currentlyFocusedElementIndex)) {
const lastGoodPageIndex = this.findPreviousIndex(lastPageIndex, false, filter);
if (lastGoodPageIndex > -1 && currentlyFocusedElement !== this.view.element(lastGoodPageIndex)) {
if (lastGoodPageIndex > -1 && currentlyFocusedElementIndex !== lastGoodPageIndex) {
this.setFocus([lastGoodPageIndex], browserEvent);
} else {
this.setFocus([lastPageIndex], browserEvent);
}
} else {
const previousScrollTop = this.view.getScrollTop();
this.view.setScrollTop(previousScrollTop + this.view.renderHeight - this.view.elementHeight(lastPageIndex));
let nextpageScrollTop = previousScrollTop + this.view.renderHeight;
if (lastPageIndex > currentlyFocusedElementIndex) {
// scroll last page element to the top only if the last page element is below the focused element
nextpageScrollTop -= this.view.elementHeight(lastPageIndex);
}
this.view.setScrollTop(nextpageScrollTop);
if (this.view.getScrollTop() !== previousScrollTop) {
this.setFocus([]);
@@ -1633,13 +1650,12 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
firstPageIndex = this.view.indexAfter(scrollTop - 1);
}
const firstPageElement = this.view.element(firstPageIndex);
const currentlyFocusedElement = this.getFocusedElements()[0];
const currentlyFocusedElementIndex = this.getFocus()[0];
if (currentlyFocusedElement !== firstPageElement) {
if (currentlyFocusedElementIndex !== firstPageIndex && (currentlyFocusedElementIndex === undefined || currentlyFocusedElementIndex >= firstPageIndex)) {
const firstGoodPageIndex = this.findNextIndex(firstPageIndex, false, filter);
if (firstGoodPageIndex > -1 && currentlyFocusedElement !== this.view.element(firstGoodPageIndex)) {
if (firstGoodPageIndex > -1 && currentlyFocusedElementIndex !== firstGoodPageIndex) {
this.setFocus([firstGoodPageIndex], browserEvent);
} else {
this.setFocus([firstPageIndex], browserEvent);
@@ -1783,6 +1799,10 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
return this.view.domNode;
}
getElementID(index: number): string {
return this.view.getElementDomId(index);
}
style(styles: IListStyles): void {
this.styleController.style(styles);
}

View File

@@ -21,7 +21,7 @@ export interface IRangedGroup {
export function groupIntersect(range: IRange, groups: IRangedGroup[]): IRangedGroup[] {
const result: IRangedGroup[] = [];
for (let r of groups) {
for (const r of groups) {
if (range.start >= r.range.end) {
continue;
}
@@ -62,7 +62,7 @@ export function consolidate(groups: IRangedGroup[]): IRangedGroup[] {
const result: IRangedGroup[] = [];
let previousGroup: IRangedGroup | null = null;
for (let group of groups) {
for (const group of groups) {
const start = group.range.start;
const end = group.range.end;
const size = group.size;
@@ -138,7 +138,7 @@ export class RangeMap {
let index = 0;
let size = 0;
for (let group of this.groups) {
for (const group of this.groups) {
const count = group.range.end - group.range.start;
const newSize = size + (count * group.size);
@@ -172,7 +172,7 @@ export class RangeMap {
let position = 0;
let count = 0;
for (let group of this.groups) {
for (const group of this.groups) {
const groupCount = group.range.end - group.range.start;
const newCount = count + groupCount;

View File

@@ -15,9 +15,7 @@ export interface IRow {
function removeFromParent(element: HTMLElement): void {
try {
if (element.parentElement) {
element.parentElement.removeChild(element);
}
element.parentElement?.removeChild(element);
} catch (e) {
// this will throw if this happens due to a blur event, nasty business
}

View File

@@ -90,14 +90,13 @@ export class Menu extends ActionBar {
context: options.context,
actionRunner: options.actionRunner,
ariaLabel: options.ariaLabel,
ariaRole: 'menu',
focusOnlyEnabledItems: true,
triggerKeys: { keys: [KeyCode.Enter, ...(isMacintosh || isLinux ? [KeyCode.Space] : [])], keyDown: true }
});
this.menuElement = menuElement;
this.actionsList.setAttribute('role', 'menu');
this.actionsList.tabIndex = 0;
this.menuDisposables = this._register(new DisposableStore());
@@ -160,7 +159,7 @@ export class Menu extends ActionBar {
}
this._register(addDisposableListener(this.domNode, EventType.MOUSE_OUT, e => {
let relatedTarget = e.relatedTarget as HTMLElement;
const relatedTarget = e.relatedTarget as HTMLElement;
if (!isAncestor(relatedTarget, this.domNode)) {
this.focusedItem = undefined;
this.updateFocus();
@@ -211,7 +210,7 @@ export class Menu extends ActionBar {
}));
let parentData: ISubMenuData = {
const parentData: ISubMenuData = {
parent: this
};
@@ -287,11 +286,13 @@ export class Menu extends ActionBar {
const fgColor = style.foregroundColor ? `${style.foregroundColor}` : '';
const bgColor = style.backgroundColor ? `${style.backgroundColor}` : '';
const border = style.borderColor ? `1px solid ${style.borderColor}` : '';
const shadow = style.shadowColor ? `0 2px 4px ${style.shadowColor}` : '';
const borderRadius = '5px';
const shadow = style.shadowColor ? `0 2px 8px ${style.shadowColor}` : '';
container.style.border = border;
this.domNode.style.color = fgColor;
this.domNode.style.backgroundColor = bgColor;
container.style.outline = border;
container.style.borderRadius = borderRadius;
container.style.color = fgColor;
container.style.backgroundColor = bgColor;
container.style.boxShadow = shadow;
if (this.viewItems) {
@@ -340,7 +341,7 @@ export class Menu extends ActionBar {
private setFocusedItem(element: HTMLElement): void {
for (let i = 0; i < this.actionsList.children.length; i++) {
let elem = this.actionsList.children[i];
const elem = this.actionsList.children[i];
if (element === elem) {
this.focusedItem = i;
break;
@@ -445,9 +446,9 @@ class BaseMenuActionViewItem extends BaseActionViewItem {
// Set mnemonic
if (this.options.label && options.enableMnemonics) {
let label = this.getAction().label;
const label = this.getAction().label;
if (label) {
let matches = MENU_MNEMONIC_REGEX.exec(label);
const matches = MENU_MNEMONIC_REGEX.exec(label);
if (matches) {
this.mnemonic = (!!matches[1] ? matches[1] : matches[3]).toLocaleLowerCase();
}
@@ -608,9 +609,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem {
this.label.innerText = replaceDoubleEscapes(label).trim();
}
if (this.item) {
this.item.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[3]).toLocaleLowerCase());
}
this.item?.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[3]).toLocaleLowerCase());
} else {
this.label.innerText = label.replace(/&&/g, '&').trim();
}
@@ -691,20 +690,19 @@ class BaseMenuActionViewItem extends BaseActionViewItem {
const isSelected = this.element && this.element.classList.contains('focused');
const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor;
const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : undefined;
const border = isSelected && this.menuStyle.selectionBorderColor ? `thin solid ${this.menuStyle.selectionBorderColor}` : '';
const outline = isSelected && this.menuStyle.selectionBorderColor ? `1px solid ${this.menuStyle.selectionBorderColor}` : '';
const outlineOffset = isSelected && this.menuStyle.selectionBorderColor ? `-1px` : '';
if (this.item) {
this.item.style.color = fgColor ? fgColor.toString() : '';
this.item.style.backgroundColor = bgColor ? bgColor.toString() : '';
this.item.style.outline = outline;
this.item.style.outlineOffset = outlineOffset;
}
if (this.check) {
this.check.style.color = fgColor ? fgColor.toString() : '';
}
if (this.container) {
this.container.style.border = border;
}
}
style(style: IMenuStyles): void {
@@ -765,7 +763,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
}
this._register(addDisposableListener(this.element, EventType.KEY_UP, e => {
let event = new StandardKeyboardEvent(e);
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Enter)) {
EventHelper.stop(e, true);
@@ -774,7 +772,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
}));
this._register(addDisposableListener(this.element, EventType.KEY_DOWN, e => {
let event = new StandardKeyboardEvent(e);
const event = new StandardKeyboardEvent(e);
if (getActiveElement() === this.item) {
if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Enter)) {
@@ -914,7 +912,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
this.submenuContainer.style.top = `${top - viewBox.top}px`;
this.submenuDisposables.add(addDisposableListener(this.submenuContainer, EventType.KEY_UP, e => {
let event = new StandardKeyboardEvent(e);
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.LeftArrow)) {
EventHelper.stop(e, true);
@@ -925,7 +923,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
}));
this.submenuDisposables.add(addDisposableListener(this.submenuContainer, EventType.KEY_DOWN, e => {
let event = new StandardKeyboardEvent(e);
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.LeftArrow)) {
EventHelper.stop(e, true);
}
@@ -966,9 +964,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
this.submenuIndicator.style.color = fgColor ? `${fgColor}` : '';
}
if (this.parentData.submenu) {
this.parentData.submenu.style(this.menuStyle);
}
this.parentData.submenu?.style(this.menuStyle);
}
override dispose(): void {
@@ -1012,7 +1008,8 @@ function getMenuWidgetCSS(style: IMenuStyles, isForShadowDom: boolean): string {
let result = /* css */`
.monaco-menu {
font-size: 13px;
border-radius: 5px;
min-width: 160px;
}
${formatRule(Codicon.menuSelection)}
@@ -1087,10 +1084,9 @@ ${formatRule(Codicon.menuSubmenu)}
.monaco-menu .monaco-action-bar.vertical .action-label.separator {
display: block;
border-bottom: 1px solid #bbb;
border-bottom: 1px solid var(--vscode-menu-separatorBackground);
padding-top: 1px;
margin-left: .8em;
margin-right: .8em;
padding: 30px;
}
.monaco-menu .secondary-actions .monaco-action-bar .action-label {
@@ -1136,6 +1132,11 @@ ${formatRule(Codicon.menuSubmenu)}
position: relative;
}
.monaco-menu .monaco-action-bar.vertical .action-menu-item:hover .keybinding,
.monaco-menu .monaco-action-bar.vertical .action-menu-item:focus .keybinding {
opacity: unset;
}
.monaco-menu .monaco-action-bar.vertical .action-label {
flex: 1 1 auto;
text-decoration: none;
@@ -1191,12 +1192,9 @@ ${formatRule(Codicon.menuSubmenu)}
}
.monaco-menu .monaco-action-bar.vertical .action-label.separator {
padding: 0.5em 0 0 0;
margin-bottom: 0.5em;
width: 100%;
height: 0px !important;
margin-left: .8em !important;
margin-right: .8em !important;
opacity: 1;
}
.monaco-menu .monaco-action-bar.vertical .action-label.separator.text {
@@ -1238,17 +1236,15 @@ ${formatRule(Codicon.menuSubmenu)}
outline: 0;
}
.monaco-menu .monaco-action-bar.vertical .action-item {
border: thin solid transparent; /* prevents jumping behaviour on hover or focus */
}
/* High Contrast Theming */
.hc-black .context-view.monaco-menu-container,
.hc-light .context-view.monaco-menu-container,
:host-context(.hc-black) .context-view.monaco-menu-container,
:host-context(.hc-light) .context-view.monaco-menu-container {
box-shadow: none;
}
.hc-black .monaco-menu .monaco-action-bar.vertical .action-item.focused,
.hc-light .monaco-menu .monaco-action-bar.vertical .action-item.focused,
:host-context(.hc-black) .monaco-menu .monaco-action-bar.vertical .action-item.focused,
:host-context(.hc-light) .monaco-menu .monaco-action-bar.vertical .action-item.focused {
background: none;
@@ -1257,11 +1253,11 @@ ${formatRule(Codicon.menuSubmenu)}
/* Vertical Action Bar Styles */
.monaco-menu .monaco-action-bar.vertical {
padding: .5em 0;
padding: .6em 0;
}
.monaco-menu .monaco-action-bar.vertical .action-menu-item {
height: 1.8em;
height: 2em;
}
.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator),
@@ -1277,10 +1273,12 @@ ${formatRule(Codicon.menuSubmenu)}
.monaco-menu .monaco-action-bar.vertical .action-label.separator {
font-size: inherit;
padding: 0.2em 0 0 0;
margin-bottom: 0.2em;
margin: 5px 0 !important;
padding: 0;
border-radius: 0;
}
.linux .monaco-menu .monaco-action-bar.vertical .action-label.separator,
:host-context(.linux) .monaco-menu .monaco-action-bar.vertical .action-label.separator {
margin-left: 0;
margin-right: 0;
@@ -1291,6 +1289,7 @@ ${formatRule(Codicon.menuSubmenu)}
padding: 0 1.8em;
}
.linux .monaco-menu .monaco-action-bar.vertical .submenu-indicator {
:host-context(.linux) .monaco-menu .monaco-action-bar.vertical .submenu-indicator {
height: 100%;
mask-size: 10px 10px;

View File

@@ -9,25 +9,35 @@
display: flex;
flex-shrink: 1;
box-sizing: border-box;
height: 30px;
height: 100%;
overflow: hidden;
flex-wrap: wrap;
}
.menubar.overflow-menu-only {
width: 38px;
}
.fullscreen .menubar:not(.compact) {
margin: 0px;
padding: 0px 5px;
padding: 4px 5px;
}
.menubar > .menubar-menu-button {
display: flex;
align-items: center;
box-sizing: border-box;
padding: 0px 8px;
cursor: default;
-webkit-app-region: no-drag;
zoom: 1;
white-space: nowrap;
outline: 0;
outline: 0 !important;
}
.monaco-workbench .menubar:not(.compact) > .menubar-menu-button:focus .menubar-menu-title {
outline-width: 1px;
outline-style: solid;
outline-offset: -1px;
outline-color: var(--vscode-focusBorder);
}
.menubar.compact {
@@ -41,6 +51,11 @@
padding: 0px;
}
.menubar-menu-title {
padding: 0px 8px;
border-radius: 5px;
}
.menubar .menubar-menu-items-holder {
position: fixed;
left: 0px;
@@ -62,8 +77,13 @@
}
.menubar .toolbar-toggle-more {
width: 20px;
height: 100%;
width: 22px;
height: 22px;
padding: 0 8px;
display: flex;
align-items: center;
justify-content: center;
vertical-align: sub;
}
.menubar.compact .toolbar-toggle-more {
@@ -77,19 +97,15 @@
justify-content: center;
}
.menubar .toolbar-toggle-more {
padding: 0;
vertical-align: sub;
}
.menubar:not(.compact) .menubar-menu-button:first-child .toolbar-toggle-more::before,
.menubar.compact .toolbar-toggle-more::before {
content: "\eb94" !important;
}
/* Match behavior of outline for activity bar icons */
.menubar.compact > .menubar-menu-button.open,
.menubar.compact > .menubar-menu-button:focus,
.menubar.compact > .menubar-menu-button:hover {
.menubar.compact > .menubar-menu-button.open .menubar-menu-title,
.menubar.compact > .menubar-menu-button:focus .menubar-menu-title,
.menubar.compact > .menubar-menu-button:hover .menubar-menu-title{
outline-width: 1px !important;
outline-offset: -8px !important;
}

View File

@@ -116,7 +116,7 @@ export class MenuBar extends Disposable {
this._register(DOM.ModifierKeyEmitter.getInstance().event(this.onModifierKeyToggled, this));
this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_DOWN, (e) => {
let event = new StandardKeyboardEvent(e as KeyboardEvent);
const event = new StandardKeyboardEvent(e as KeyboardEvent);
let eventHandled = true;
const key = !!e.key ? e.key.toLocaleLowerCase() : '';
@@ -154,7 +154,7 @@ export class MenuBar extends Disposable {
}));
this._register(DOM.addDisposableListener(this.container, DOM.EventType.FOCUS_IN, (e) => {
let event = e as FocusEvent;
const event = e as FocusEvent;
if (event.relatedTarget) {
if (!this.container.contains(event.relatedTarget as HTMLElement)) {
@@ -164,7 +164,7 @@ export class MenuBar extends Disposable {
}));
this._register(DOM.addDisposableListener(this.container, DOM.EventType.FOCUS_OUT, (e) => {
let event = e as FocusEvent;
const event = e as FocusEvent;
// We are losing focus and there is no related target, e.g. webview case
if (!event.relatedTarget) {
@@ -204,11 +204,11 @@ export class MenuBar extends Disposable {
const menuIndex = this.menus.length;
const cleanMenuLabel = cleanMnemonic(menuBarMenu.label);
let mnemonicMatches = MENU_MNEMONIC_REGEX.exec(menuBarMenu.label);
const mnemonicMatches = MENU_MNEMONIC_REGEX.exec(menuBarMenu.label);
// Register mnemonics
if (mnemonicMatches) {
let mnemonic = !!mnemonicMatches[1] ? mnemonicMatches[1] : mnemonicMatches[3];
const mnemonic = !!mnemonicMatches[1] ? mnemonicMatches[1] : mnemonicMatches[3];
this.registerMnemonic(this.menus.length, mnemonic);
}
@@ -225,7 +225,7 @@ export class MenuBar extends Disposable {
this.updateLabels(titleElement, buttonElement, menuBarMenu.label);
this._register(DOM.addDisposableListener(buttonElement, DOM.EventType.KEY_UP, (e) => {
let event = new StandardKeyboardEvent(e as KeyboardEvent);
const event = new StandardKeyboardEvent(e as KeyboardEvent);
let eventHandled = true;
if ((event.equals(KeyCode.DownArrow) || event.equals(KeyCode.Enter)) && !this.isOpen) {
@@ -313,8 +313,7 @@ export class MenuBar extends Disposable {
createOverflowMenu(): void {
const label = this.isCompact ? nls.localize('mAppMenu', 'Application Menu') : nls.localize('mMore', 'More');
const title = this.isCompact ? label : undefined;
const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': this.isCompact ? 0 : -1, 'aria-label': label, 'title': title, 'aria-haspopup': true });
const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': this.isCompact ? 0 : -1, 'aria-label': label, 'aria-haspopup': true });
const titleElement = $('div.menubar-menu-title.toolbar-toggle-more' + Codicon.menuBarMore.cssSelector, { 'role': 'none', 'aria-hidden': true });
buttonElement.appendChild(titleElement);
@@ -322,7 +321,7 @@ export class MenuBar extends Disposable {
buttonElement.style.visibility = 'hidden';
this._register(DOM.addDisposableListener(buttonElement, DOM.EventType.KEY_UP, (e) => {
let event = new StandardKeyboardEvent(e as KeyboardEvent);
const event = new StandardKeyboardEvent(e as KeyboardEvent);
let eventHandled = true;
const triggerKeys = [KeyCode.Enter];
@@ -330,7 +329,12 @@ export class MenuBar extends Disposable {
triggerKeys.push(KeyCode.DownArrow);
} else {
triggerKeys.push(KeyCode.Space);
triggerKeys.push(this.options.compactMode === Direction.Right ? KeyCode.RightArrow : KeyCode.LeftArrow);
if (this.options.compactMode === Direction.Right) {
triggerKeys.push(KeyCode.RightArrow);
} else if (this.options.compactMode === Direction.Left) {
triggerKeys.push(KeyCode.LeftArrow);
}
}
if ((triggerKeys.some(k => event.equals(k)) && !this.isOpen)) {
@@ -469,6 +473,11 @@ export class MenuBar extends Disposable {
return;
}
const overflowMenuOnlyClass = 'overflow-menu-only';
// Remove overflow only restriction to allow the most space
this.container.classList.toggle(overflowMenuOnlyClass, false);
const sizeAvailable = this.container.offsetWidth;
let currentSize = 0;
let full = this.isCompact;
@@ -476,7 +485,7 @@ export class MenuBar extends Disposable {
this.numMenusShown = 0;
const showableMenus = this.menus.filter(menu => menu.buttonElement !== undefined && menu.titleElement !== undefined) as (MenuBarMenuWithElements & { titleElement: HTMLElement; buttonElement: HTMLElement })[];
for (let menuBarMenu of showableMenus) {
for (const menuBarMenu of showableMenus) {
if (!full) {
const size = menuBarMenu.buttonElement.offsetWidth;
if (currentSize + size > sizeAvailable) {
@@ -495,6 +504,18 @@ export class MenuBar extends Disposable {
}
}
// If below minimium menu threshold, show the overflow menu only as hamburger menu
if (this.numMenusShown - 1 <= showableMenus.length / 2) {
for (const menuBarMenu of showableMenus) {
menuBarMenu.buttonElement.style.visibility = 'hidden';
}
full = true;
this.numMenusShown = 0;
currentSize = 0;
}
// Overflow
if (this.isCompact) {
this.overflowMenu.actions = [];
@@ -534,6 +555,9 @@ export class MenuBar extends Disposable {
this.container.appendChild(this.overflowMenu.buttonElement);
this.overflowMenu.buttonElement.style.visibility = 'hidden';
}
// If we are only showing the overflow, add this class to avoid taking up space
this.container.classList.toggle(overflowMenuOnlyClass, this.numMenusShown === 0);
}
private updateLabels(titleElement: HTMLElement, buttonElement: HTMLElement, label: string): void {
@@ -542,7 +566,7 @@ export class MenuBar extends Disposable {
// Update the button label to reflect mnemonics
if (this.options.enableMnemonics) {
let cleanLabel = strings.escape(label);
const cleanLabel = strings.escape(label);
// This is global so reset it
MENU_ESCAPED_MNEMONIC_REGEX.lastIndex = 0;
@@ -569,11 +593,11 @@ export class MenuBar extends Disposable {
titleElement.innerText = cleanMenuLabel.replace(/&&/g, '&');
}
let mnemonicMatches = MENU_MNEMONIC_REGEX.exec(label);
const mnemonicMatches = MENU_MNEMONIC_REGEX.exec(label);
// Register mnemonics
if (mnemonicMatches) {
let mnemonic = !!mnemonicMatches[1] ? mnemonicMatches[1] : mnemonicMatches[3];
const mnemonic = !!mnemonicMatches[1] ? mnemonicMatches[1] : mnemonicMatches[3];
if (this.options.enableMnemonics) {
buttonElement.setAttribute('aria-keyshortcuts', 'Alt+' + mnemonic.toLocaleLowerCase());
@@ -740,7 +764,7 @@ export class MenuBar extends Disposable {
this._onFocusStateChange.fire(this.focusState >= MenubarState.FOCUSED);
}
private get isVisible(): boolean {
get isVisible(): boolean {
return this.focusState >= MenubarState.VISIBLE;
}
@@ -838,7 +862,7 @@ export class MenuBar extends Disposable {
if (this.menus) {
this.menus.forEach(menuBarMenu => {
if (menuBarMenu.titleElement && menuBarMenu.titleElement.children.length) {
let child = menuBarMenu.titleElement.children.item(0) as HTMLElement;
const child = menuBarMenu.titleElement.children.item(0) as HTMLElement;
if (child) {
child.style.textDecoration = (this.options.alwaysOnMnemonics || visible) ? 'underline' : '';
}
@@ -956,9 +980,7 @@ export class MenuBar extends Disposable {
}
if (this.focusedMenu.holder) {
if (this.focusedMenu.holder.parentElement) {
this.focusedMenu.holder.parentElement.classList.remove('open');
}
this.focusedMenu.holder.parentElement?.classList.remove('open');
this.focusedMenu.holder.remove();
}
@@ -975,7 +997,7 @@ export class MenuBar extends Disposable {
const actualMenuIndex = menuIndex >= this.numMenusShown ? MenuBar.OVERFLOW_INDEX : menuIndex;
const customMenu = actualMenuIndex === MenuBar.OVERFLOW_INDEX ? this.overflowMenu : this.menus[actualMenuIndex];
if (!customMenu.actions || !customMenu.buttonElement) {
if (!customMenu.actions || !customMenu.buttonElement || !customMenu.titleElement) {
return;
}
@@ -983,23 +1005,24 @@ export class MenuBar extends Disposable {
customMenu.buttonElement.classList.add('open');
const buttonBoundingRect = customMenu.buttonElement.getBoundingClientRect();
const titleBoundingRect = customMenu.titleElement.getBoundingClientRect();
const titleBoundingRectZoom = DOM.getDomNodeZoomLevel(customMenu.titleElement);
if (this.options.compactMode === Direction.Right) {
menuHolder.style.top = `${buttonBoundingRect.top}px`;
menuHolder.style.left = `${buttonBoundingRect.left + this.container.clientWidth}px`;
menuHolder.style.top = `${titleBoundingRect.top}px`;
menuHolder.style.left = `${titleBoundingRect.left + this.container.clientWidth}px`;
} else if (this.options.compactMode === Direction.Left) {
menuHolder.style.top = `${buttonBoundingRect.top}px`;
menuHolder.style.top = `${titleBoundingRect.top}px`;
menuHolder.style.right = `${this.container.clientWidth}px`;
menuHolder.style.left = 'auto';
} else {
menuHolder.style.top = `${buttonBoundingRect.bottom}px`;
menuHolder.style.left = `${buttonBoundingRect.left}px`;
menuHolder.style.top = `${titleBoundingRect.bottom * titleBoundingRectZoom}px`;
menuHolder.style.left = `${titleBoundingRect.left * titleBoundingRectZoom}px`;
}
customMenu.buttonElement.appendChild(menuHolder);
let menuOptions: IMenuOptions = {
const menuOptions: IMenuOptions = {
getKeyBinding: this.options.getKeybinding,
actionRunner: this.actionRunner,
enableMnemonics: this.options.alwaysOnMnemonics || (this.mnemonicsInUse && this.options.enableMnemonics),
@@ -1008,7 +1031,7 @@ export class MenuBar extends Disposable {
useEventAsContext: true
};
let menuWidget = this._register(new Menu(menuHolder, customMenu.actions, menuOptions));
const menuWidget = this._register(new Menu(menuHolder, customMenu.actions, menuOptions));
if (this.menuStyle) {
menuWidget.style(this.menuStyle);
}

View File

@@ -17,7 +17,7 @@ import 'vs/css!./sash';
* Allow the sashes to be visible at runtime.
* @remark Use for development purposes only.
*/
let DEBUG = false;
const DEBUG = false;
// DEBUG = Boolean("true"); // done "weirdly" so that a lint warning prevents you from pushing this
/**

View File

@@ -5,7 +5,7 @@
import * as dom from 'vs/base/browser/dom';
import { createFastDomNode, FastDomNode } from 'vs/base/browser/fastDomNode';
import { GlobalPointerMoveMonitor, IPointerMoveEventData, standardPointerMoveMerger } from 'vs/base/browser/globalPointerMoveMonitor';
import { GlobalPointerMoveMonitor } from 'vs/base/browser/globalPointerMoveMonitor';
import { StandardWheelEvent } from 'vs/base/browser/mouseEvent';
import { ScrollbarArrow, ScrollbarArrowOptions } from 'vs/base/browser/ui/scrollbar/scrollbarArrow';
import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState';
@@ -245,8 +245,7 @@ export abstract class AbstractScrollbar extends Widget {
e.target,
e.pointerId,
e.buttons,
standardPointerMoveMerger,
(pointerMoveData: IPointerMoveEventData) => {
(pointerMoveData: PointerEvent) => {
const pointerOrthogonalPosition = this._sliderOrthogonalPointerPosition(pointerMoveData);
const pointerOrthogonalDelta = Math.abs(pointerOrthogonalPosition - initialPointerOrthogonalPosition);

View File

@@ -232,7 +232,7 @@ export abstract class AbstractScrollableElement extends Widget {
this._setListeningToMouseWheel(this._options.handleMouseWheel);
this.onmouseover(this._listenOnDomNode, (e) => this._onMouseOver(e));
this.onnonbubblingmouseout(this._listenOnDomNode, (e) => this._onMouseOut(e));
this.onmouseleave(this._listenOnDomNode, (e) => this._onMouseLeave(e));
this._hideTimeout = this._register(new TimeoutTimer());
this._isDragging = false;
@@ -525,7 +525,7 @@ export abstract class AbstractScrollableElement extends Widget {
this._hide();
}
private _onMouseOut(e: IMouseEvent): void {
private _onMouseLeave(e: IMouseEvent): void {
this._mouseIsOver = false;
this._hide();
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { GlobalPointerMoveMonitor, standardPointerMoveMerger } from 'vs/base/browser/globalPointerMoveMonitor';
import { GlobalPointerMoveMonitor } from 'vs/base/browser/globalPointerMoveMonitor';
import { Widget } from 'vs/base/browser/ui/widget';
import { IntervalTimer, TimeoutTimer } from 'vs/base/common/async';
import { Codicon } from 'vs/base/common/codicons';
@@ -103,7 +103,6 @@ export class ScrollbarArrow extends Widget {
e.target,
e.pointerId,
e.buttons,
standardPointerMoveMerger,
(pointerMoveData) => { /* Intentional empty */ },
() => {
this._pointerdownRepeatTimer.cancel();

View File

@@ -103,9 +103,7 @@ export class ScrollbarVisibilityController extends Disposable {
// The CSS animation doesn't play otherwise
this._revealTimer.setIfNotSet(() => {
if (this._domNode) {
this._domNode.setClassName(this._visibleClassName);
}
this._domNode?.setClassName(this._visibleClassName);
}, 0);
}
@@ -115,8 +113,6 @@ export class ScrollbarVisibilityController extends Disposable {
return;
}
this._isVisible = false;
if (this._domNode) {
this._domNode.setClassName(this._invisibleClassName + (withFadeAway ? ' fade' : ''));
}
this._domNode?.setClassName(this._invisibleClassName + (withFadeAway ? ' fade' : ''));
}
}

View File

@@ -131,6 +131,10 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
this.selectElement.setAttribute('aria-description', this.selectBoxOptions.ariaDescription);
}
if (typeof this.selectBoxOptions.ariaDescription === 'string') {
this.selectElement.setAttribute('aria-description', this.selectBoxOptions.ariaDescription);
}
this._onDidSelect = new Emitter<ISelectData>();
this._register(this._onDidSelect);
@@ -169,8 +173,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
this.selectionDetailsPane = dom.append(this.selectDropDownContainer, $('.select-box-details-pane'));
// Create span flex box item/div we can measure and control
let widthControlOuterDiv = dom.append(this.selectDropDownContainer, $('.select-box-dropdown-container-width-control'));
let widthControlInnerDiv = dom.append(widthControlOuterDiv, $('.width-control-div'));
const widthControlOuterDiv = dom.append(this.selectDropDownContainer, $('.select-box-dropdown-container-width-control'));
const widthControlInnerDiv = dom.append(widthControlOuterDiv, $('.width-control-div'));
this.widthControlElement = document.createElement('span');
this.widthControlElement.className = 'option-text-width-control';
dom.append(widthControlInnerDiv, this.widthControlElement);
@@ -291,10 +295,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
// Mirror options in drop-down
// Populate select list for non-native select mode
if (this.selectList) {
this.selectList.splice(0, this.selectList.length, this.options);
this.selectList?.setSelection(this.selected !== -1 ? [this.selected] : []); // {{SQL CARBON EDIT}} - Set the selected indexes.
}
this.selectList?.splice(0, this.selectList.length, this.options);
this.selectList?.setSelection(this.selected !== -1 ? [this.selected] : []); // {{SQL CARBON EDIT}} - Set the selected indexes.
}
public select(index: number): void {
@@ -449,7 +451,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
}
private createOption(value: string, index: number, disabled?: boolean): HTMLOptionElement {
let option = document.createElement('option');
const option = document.createElement('option');
option.value = value;
option.text = value;
option.disabled = !!disabled;

View File

@@ -24,7 +24,6 @@
height: 22px;
font-size: 11px;
font-weight: bold;
text-transform: uppercase;
overflow: hidden;
display: flex;
cursor: pointer;
@@ -32,6 +31,10 @@
box-sizing: border-box;
}
.monaco-pane-view .pane > .pane-header > .title {
text-transform: uppercase;
}
.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header {
flex-direction: column;
height: 100%;

View File

@@ -143,9 +143,7 @@ export abstract class Pane extends Disposable implements IView {
return false;
}
if (this.element) {
this.element.classList.toggle('expanded', expanded);
}
this.element?.classList.toggle('expanded', expanded);
this._expanded = !!expanded;
this.updateHeader();

View File

@@ -231,9 +231,7 @@ abstract class ViewItem<TLayoutContext> {
this.container.classList.toggle('visible', visible);
if (this.view.setVisible) {
this.view.setVisible(visible);
}
this.view.setVisible?.(visible);
}
get minimumSize(): number { return this.visible ? this.view.minimumSize : 0; }
@@ -933,6 +931,23 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
this.state = State.Idle;
}
/**
* Returns whether all other {@link IView views} are at their minimum size.
*/
isViewSizeMaximized(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}.
*/
@@ -1011,7 +1026,7 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
// Add sash
if (this.viewItems.length > 1) {
let opts = { orthogonalStartSash: this.orthogonalStartSash, orthogonalEndSash: this.orthogonalEndSash };
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 })

View File

@@ -265,8 +265,8 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable {
this.list.layout(listHeight, width);
}
toggleKeyboardNavigation(): void {
this.list.toggleKeyboardNavigation();
triggerTypeNavigation(): void {
this.list.triggerTypeNavigation();
}
style(styles: ITableStyles): void {
@@ -341,6 +341,10 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable {
return this.list.getFocusedElements();
}
getRelativeTop(index: number): number | null {
return this.list.getRelativeTop(index);
}
reveal(index: number, relativeTop?: number): void {
this.list.reveal(index, relativeTop);
}

View File

@@ -98,6 +98,7 @@ export class Toggle extends Widget {
readonly onKeyDown: Event<IKeyboardEvent> = this._onKeyDown.event;
private readonly _opts: IToggleOpts;
private _icon: CSSIcon | undefined;
readonly domNode: HTMLElement;
private _checked: boolean;
@@ -110,7 +111,8 @@ export class Toggle extends Widget {
const classes = ['monaco-custom-toggle'];
if (this._opts.icon) {
classes.push(...CSSIcon.asClassNameArray(this._opts.icon));
this._icon = this._opts.icon;
classes.push(...CSSIcon.asClassNameArray(this._icon));
}
if (this._opts.actionClassName) {
classes.push(...this._opts.actionClassName.split(' '));
@@ -146,6 +148,7 @@ export class Toggle extends Widget {
this.checked = !this._checked;
this._onChange.fire(true);
keyboardEvent.preventDefault();
keyboardEvent.stopPropagation();
return;
}
@@ -174,6 +177,16 @@ export class Toggle extends Widget {
this.applyStyles();
}
setIcon(icon: CSSIcon | undefined): void {
if (this._icon) {
this.domNode.classList.remove(...CSSIcon.asClassNameArray(this._icon));
}
this._icon = icon;
if (this._icon) {
this.domNode.classList.add(...CSSIcon.asClassNameArray(this._icon));
}
}
width(): number {
return 2 /*margin left*/ + 2 /*border*/ + 2 /*padding*/ + 16 /* icon width */;
}

View File

@@ -130,9 +130,7 @@ export class ToolBar extends Disposable {
set context(context: unknown) {
this.actionBar.context = context;
if (this.toggleMenuActionViewItem) {
this.toggleMenuActionViewItem.setActionContext(context);
}
this.toggleMenuActionViewItem?.setActionContext(context);
for (const actionViewItem of this.submenuActionViewItems) {
actionViewItem.setActionContext(context);
}
@@ -169,7 +167,7 @@ export class ToolBar extends Disposable {
setActions(primaryActions: ReadonlyArray<IAction>, secondaryActions?: ReadonlyArray<IAction>): void {
this.clear();
let primaryActionsToSet = primaryActions ? primaryActions.slice(0) : [];
const primaryActionsToSet = primaryActions ? primaryActions.slice(0) : [];
// Inject additional action to open secondary actions if present
this.hasSecondaryActions = !!(secondaryActions && secondaryActions.length > 0);

View File

@@ -3,27 +3,34 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DragAndDropData, IDragAndDropData, StaticDND } from 'vs/base/browser/dnd';
import { $, addDisposableListener, append, clearNode, createStyleSheet, getDomNodePagePosition, hasParentWithClass } from 'vs/base/browser/dom';
import { IDragAndDropData } from 'vs/base/browser/dnd';
import { $, append, clearNode, createStyleSheet, h, hasParentWithClass } from 'vs/base/browser/dom';
import { DomEmitter } from 'vs/base/browser/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IIdentityProvider, IKeyboardNavigationDelegate, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput';
import { IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { DefaultKeyboardNavigationDelegate, IListOptions, IListStyles, isInputElement, isMonacoEditor, List, MouseController } from 'vs/base/browser/ui/list/listWidget';
import { IListOptions, IListStyles, isButton, isInputElement, isMonacoEditor, List, MouseController, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget';
import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel';
import { ICollapseStateChangeEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeModel, ITreeModelSpliceEvent, ITreeMouseEvent, ITreeNavigator, ITreeNode, ITreeRenderer, TreeDragOverBubble, TreeError, TreeFilterResult, TreeMouseEventTarget, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
import { Action } from 'vs/base/common/actions';
import { distinct, equals, firstOrDefault, range } from 'vs/base/common/arrays';
import { disposableTimeout } from 'vs/base/common/async';
import { disposableTimeout, timeout } from 'vs/base/common/async';
import { Codicon } from 'vs/base/common/codicons';
import { SetMap } from 'vs/base/common/collections';
import { Color } from 'vs/base/common/color';
import { Emitter, Event, EventBufferer, Relay } from 'vs/base/common/event';
import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { clamp } from 'vs/base/common/numbers';
import { isMacintosh } from 'vs/base/common/platform';
import { ScrollEvent } from 'vs/base/common/scrollable';
import { ISpliceable } from 'vs/base/common/sequence';
import { isNumber } from 'vs/base/common/types';
import 'vs/css!./media/tree';
import { localize } from 'vs/nls';
@@ -70,9 +77,7 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
}
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
if (this.dnd.onDragStart) {
this.dnd.onDragStart(asTreeDragAndDropData(data), originalEvent);
}
this.dnd.onDragStart?.(asTreeDragAndDropData(data), originalEvent);
}
onDragOver(data: IDragAndDropData, targetNode: ITreeNode<T, TFilterData> | undefined, targetIndex: number | undefined, originalEvent: DragEvent, raw = true): boolean | IListDragOverReaction {
@@ -137,9 +142,7 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
}
onDragEnd(originalEvent: DragEvent): void {
if (this.dnd.onDragEnd) {
this.dnd.onDragEnd(originalEvent);
}
this.dnd.onDragEnd?.(originalEvent);
}
}
@@ -198,8 +201,7 @@ function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T,
getKeyboardNavigationLabel(node) {
return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(node.element);
}
},
enableKeyboardNavigation: options.simpleKeyboardNavigation
}
};
}
@@ -220,9 +222,7 @@ export class ComposedTreeDelegate<T, N extends { element: T }> implements IListV
}
setDynamicHeight(element: N, height: number): void {
if (this.delegate.setDynamicHeight) {
this.delegate.setDynamicHeight(element.element, height);
}
this.delegate.setDynamicHeight?.(element.element, height);
}
}
@@ -308,8 +308,9 @@ interface Collection<T> {
readonly onDidChange: Event<T[]>;
}
class EventCollection<T> implements Collection<T> {
class EventCollection<T> implements Collection<T>, IDisposable {
private readonly disposables = new DisposableStore();
readonly onDidChange: Event<T[]>;
get elements(): T[] {
@@ -317,7 +318,11 @@ class EventCollection<T> implements Collection<T> {
}
constructor(onDidChange: Event<T[]>, private _elements: T[] = []) {
this.onDidChange = Event.forEach(onDidChange, elements => this._elements = elements);
this.onDidChange = Event.forEach(onDidChange, elements => this._elements = elements, this.disposables);
}
dispose(): void {
this.disposables.dispose();
}
}
@@ -350,9 +355,7 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
Event.map(onDidChangeCollapseState, e => e.node)(this.onDidChangeNodeTwistieState, this, this.disposables);
if (renderer.onDidChangeTwistieState) {
renderer.onDidChangeTwistieState(this.onDidChangeTwistieState, this, this.disposables);
}
renderer.onDidChangeTwistieState?.(this.onDidChangeTwistieState, this, this.disposables);
}
updateOptions(options: ITreeRendererOptions = {}): void {
@@ -414,9 +417,7 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
disposeElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>, height: number | undefined): void {
templateData.indentGuidesDisposable.dispose();
if (this.renderer.disposeElement) {
this.renderer.disposeElement(node, index, templateData.templateData, height);
}
this.renderer.disposeElement?.(node, index, templateData.templateData, height);
if (typeof height === 'number') {
this.renderedNodes.delete(node);
@@ -568,7 +569,7 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
export type LabelFuzzyScore = { label: string; score: FuzzyScore };
class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDisposable {
class FindFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDisposable {
private _totalCount = 0;
get totalCount(): number { return this._totalCount; }
private _matchCount = 0;
@@ -592,15 +593,11 @@ class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi
}
filter(element: T, parentVisibility: TreeVisibility): TreeFilterResult<FuzzyScore | LabelFuzzyScore> {
let visibility = TreeVisibility.Visible;
if (this._filter) {
const result = this._filter.filter(element, parentVisibility);
if (this.tree.options.simpleKeyboardNavigation) {
return result;
}
let visibility: TreeVisibility;
if (typeof result === 'boolean') {
visibility = result ? TreeVisibility.Visible : TreeVisibility.Hidden;
} else if (isFilterResult(result)) {
@@ -616,9 +613,9 @@ class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi
this._totalCount++;
if (this.tree.options.simpleKeyboardNavigation || !this._pattern) {
if (!this._pattern) {
this._matchCount++;
return { data: FuzzyScore.Default, visibility: true };
return { data: FuzzyScore.Default, visibility };
}
const label = this.keyboardNavigationLabelProvider.getKeyboardNavigationLabel(element);
@@ -627,22 +624,22 @@ class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi
for (const l of labels) {
const labelStr = l && l.toString();
if (typeof labelStr === 'undefined') {
return { data: FuzzyScore.Default, visibility: true };
return { data: FuzzyScore.Default, visibility };
}
const score = fuzzyScore(this._pattern, this._lowercasePattern, 0, labelStr, labelStr.toLowerCase(), 0);
const score = fuzzyScore(this._pattern, this._lowercasePattern, 0, labelStr, labelStr.toLowerCase(), 0, { firstMatchCanBeWeak: true, boostFullMatch: true });
if (score) {
this._matchCount++;
return labels.length === 1 ?
{ data: score, visibility: true } :
{ data: { label: labelStr, score: score }, visibility: true };
{ data: score, visibility } :
{ data: { label: labelStr, score: score }, visibility };
}
}
if (this.tree.options.filterOnType) {
if (this.tree.findMode === TreeFindMode.Filter) {
return TreeVisibility.Recurse;
} else {
return { data: FuzzyScore.Default, visibility: true };
return { data: FuzzyScore.Default, visibility };
}
}
@@ -656,170 +653,282 @@ class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi
}
}
class TypeFilterController<T, TFilterData> implements IDisposable {
export interface ICaseSensitiveToggleOpts {
readonly isChecked: boolean;
readonly inputActiveOptionBorder?: Color;
readonly inputActiveOptionForeground?: Color;
readonly inputActiveOptionBackground?: Color;
}
private _enabled = false;
get enabled(): boolean { return this._enabled; }
export class ModeToggle extends Toggle {
constructor(opts?: ICaseSensitiveToggleOpts) {
super({
icon: Codicon.filter,
title: localize('filter', "Filter"),
isChecked: opts?.isChecked ?? false,
inputActiveOptionBorder: opts?.inputActiveOptionBorder,
inputActiveOptionForeground: opts?.inputActiveOptionForeground,
inputActiveOptionBackground: opts?.inputActiveOptionBackground
});
}
}
export interface IFindWidgetStyles extends IFindInputStyles, IListStyles { }
export interface IFindWidgetOpts extends IFindWidgetStyles { }
export enum TreeFindMode {
Highlight,
Filter
}
class FindWidget<T, TFilterData> extends Disposable {
private readonly elements = h('.monaco-tree-type-filter', [
h('.monaco-tree-type-filter-grab.codicon.codicon-debug-gripper@grab', { tabIndex: 0 }),
h('.monaco-tree-type-filter-input@findInput'),
h('.monaco-tree-type-filter-actionbar@actionbar'),
]);
set mode(mode: TreeFindMode) {
this.modeToggle.checked = mode === TreeFindMode.Filter;
this.findInput.inputBox.setPlaceHolder(mode === TreeFindMode.Filter ? localize('type to filter', "Type to filter") : localize('type to search', "Type to search"));
}
private readonly modeToggle: ModeToggle;
private readonly findInput: FindInput;
private readonly actionbar: ActionBar;
private width = 0;
private right = 0;
readonly _onDidDisable = new Emitter<void>();
readonly onDidDisable = this._onDidDisable.event;
readonly onDidChangeValue: Event<string>;
readonly onDidChangeMode: Event<TreeFindMode>;
constructor(
container: HTMLElement,
private tree: AbstractTree<T, TFilterData, any>,
contextViewProvider: IContextViewProvider,
mode: TreeFindMode,
options?: IFindWidgetOpts
) {
super();
container.appendChild(this.elements.root);
this._register(toDisposable(() => container.removeChild(this.elements.root)));
this.modeToggle = this._register(new ModeToggle({ ...options, isChecked: mode === TreeFindMode.Filter }));
this.onDidChangeMode = Event.map(this.modeToggle.onChange, () => this.modeToggle.checked ? TreeFindMode.Filter : TreeFindMode.Highlight, this._store);
this.findInput = this._register(new FindInput(this.elements.findInput, contextViewProvider, false, {
label: localize('type to search', "Type to search"),
additionalToggles: [this.modeToggle]
}));
this.actionbar = this._register(new ActionBar(this.elements.actionbar));
this.mode = mode;
const emitter = this._register(new DomEmitter(this.findInput.inputBox.inputElement, 'keydown'));
const onKeyDown = this._register(Event.chain(emitter.event))
.map(e => new StandardKeyboardEvent(e))
.event;
this._register(onKeyDown((e): any => {
switch (e.keyCode) {
case KeyCode.DownArrow:
e.preventDefault();
e.stopPropagation();
this.tree.domFocus();
return;
}
}));
const closeAction = this._register(new Action('close', localize('close', "Close"), 'codicon codicon-close', true, () => this.dispose()));
this.actionbar.push(closeAction, { icon: true, label: false });
const onGrabMouseDown = this._register(new DomEmitter(this.elements.grab, 'mousedown'));
this._register(onGrabMouseDown.event(e => {
const disposables = new DisposableStore();
const onWindowMouseMove = disposables.add(new DomEmitter(window, 'mousemove'));
const onWindowMouseUp = disposables.add(new DomEmitter(window, 'mouseup'));
const startRight = this.right;
const startX = e.pageX;
this.elements.grab.classList.add('grabbing');
const update = (e: MouseEvent) => {
const deltaX = e.pageX - startX;
this.right = startRight - deltaX;
this.layout();
};
disposables.add(onWindowMouseMove.event(update));
disposables.add(onWindowMouseUp.event(e => {
update(e);
this.elements.grab.classList.remove('grabbing');
disposables.dispose();
}));
}));
const onGrabKeyDown = this._register(Event.chain(this._register(new DomEmitter(this.elements.grab, 'keydown')).event))
.map(e => new StandardKeyboardEvent(e))
.event;
this._register(onGrabKeyDown((e): any => {
let right: number | undefined;
if (e.keyCode === KeyCode.LeftArrow) {
right = Number.POSITIVE_INFINITY;
} else if (e.keyCode === KeyCode.RightArrow) {
right = 0;
} else if (e.keyCode === KeyCode.Space) {
right = this.right === 0 ? Number.POSITIVE_INFINITY : 0;
}
if (right !== undefined) {
e.preventDefault();
e.stopPropagation();
this.right = right;
this.layout();
}
}));
this.onDidChangeValue = this.findInput.onDidChange;
this.style(options ?? {});
}
style(styles: IFindWidgetStyles): void {
this.findInput.style(styles);
if (styles.listFilterWidgetBackground) {
this.elements.root.style.backgroundColor = styles.listFilterWidgetBackground.toString();
}
if (styles.listFilterWidgetShadow) {
this.elements.root.style.boxShadow = `0 0 8px 2px ${styles.listFilterWidgetShadow}`;
}
}
focus() {
this.findInput.focus();
}
select() {
this.findInput.select();
}
layout(width: number = this.width): void {
this.width = width;
this.right = clamp(this.right, 0, Math.max(0, width - 212));
this.elements.root.style.right = `${this.right}px`;
}
showMessage(message: IMessage): void {
this.findInput.showMessage(message);
}
clearMessage(): void {
this.findInput.clearMessage();
}
override async dispose(): Promise<void> {
this._onDidDisable.fire();
this.elements.root.classList.add('disabled');
await timeout(300);
super.dispose();
}
}
class FindController<T, TFilterData> implements IDisposable {
private _pattern = '';
get pattern(): string { return this._pattern; }
private _filterOnType: boolean;
get filterOnType(): boolean { return this._filterOnType; }
private _mode: TreeFindMode;
get mode(): TreeFindMode { return this._mode; }
set mode(mode: TreeFindMode) {
if (mode === this._mode) {
return;
}
private _empty: boolean = false;
get empty(): boolean { return this._empty; }
this._mode = mode;
private readonly _onDidChangeEmptyState = new Emitter<boolean>();
readonly onDidChangeEmptyState: Event<boolean> = Event.latch(this._onDidChangeEmptyState.event);
if (this.widget) {
this.widget.mode = this._mode;
}
private positionClassName = 'ne';
private domNode: HTMLElement;
private messageDomNode: HTMLElement;
private labelDomNode: HTMLElement;
private filterOnTypeDomNode: HTMLInputElement;
private clearDomNode: HTMLElement;
private keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter;
this.tree.refilter();
this.render();
this._onDidChangeMode.fire(mode);
}
private automaticKeyboardNavigation = true;
private triggered = false;
private widget: FindWidget<T, TFilterData> | undefined;
private styles: IFindWidgetStyles | undefined;
private width = 0;
private readonly _onDidChangeMode = new Emitter<TreeFindMode>();
readonly onDidChangeMode = this._onDidChangeMode.event;
private readonly _onDidChangePattern = new Emitter<string>();
readonly onDidChangePattern = this._onDidChangePattern.event;
private readonly enabledDisposables = new DisposableStore();
private readonly _onDidChangeOpenState = new Emitter<boolean>();
readonly onDidChangeOpenState = this._onDidChangeOpenState.event;
private enabledDisposables = new DisposableStore();
private readonly disposables = new DisposableStore();
constructor(
private tree: AbstractTree<T, TFilterData, any>,
model: ITreeModel<T, TFilterData, any>,
private view: List<ITreeNode<T, TFilterData>>,
private filter: TypeFilter<T>,
private keyboardNavigationDelegate: IKeyboardNavigationDelegate
private filter: FindFilter<T>,
private readonly contextViewProvider: IContextViewProvider
) {
this.domNode = $(`.monaco-list-type-filter.${this.positionClassName}`);
this.domNode.draggable = true;
this.disposables.add(addDisposableListener(this.domNode, 'dragstart', () => this.onDragStart()));
this.messageDomNode = append(view.getHTMLElement(), $(`.monaco-list-type-filter-message`));
this.labelDomNode = append(this.domNode, $('span.label'));
const controls = append(this.domNode, $('.controls'));
this._filterOnType = !!tree.options.filterOnType;
this.filterOnTypeDomNode = append(controls, $<HTMLInputElement>('input.filter'));
this.filterOnTypeDomNode.type = 'checkbox';
this.filterOnTypeDomNode.checked = this._filterOnType;
this.filterOnTypeDomNode.tabIndex = -1;
this.updateFilterOnTypeTitleAndIcon();
this.disposables.add(addDisposableListener(this.filterOnTypeDomNode, 'input', () => this.onDidChangeFilterOnType()));
this.clearDomNode = append(controls, $<HTMLInputElement>('button.clear' + Codicon.treeFilterClear.cssSelector));
this.clearDomNode.tabIndex = -1;
this.clearDomNode.title = localize('clear', "Clear");
this.keyboardNavigationEventFilter = tree.options.keyboardNavigationEventFilter;
this._mode = tree.options.defaultFindMode ?? TreeFindMode.Highlight;
model.onDidSplice(this.onDidSpliceModel, this, this.disposables);
this.updateOptions(tree.options);
}
updateOptions(options: IAbstractTreeOptions<T, TFilterData>): void {
if (options.simpleKeyboardNavigation) {
this.disable();
} else {
this.enable();
}
if (typeof options.filterOnType !== 'undefined') {
this._filterOnType = !!options.filterOnType;
this.filterOnTypeDomNode.checked = this._filterOnType;
this.updateFilterOnTypeTitleAndIcon();
}
if (typeof options.automaticKeyboardNavigation !== 'undefined') {
this.automaticKeyboardNavigation = options.automaticKeyboardNavigation;
}
this.tree.refilter();
this.render();
if (!this.automaticKeyboardNavigation) {
this.onEventOrInput('');
}
}
toggle(): void {
this.triggered = !this.triggered;
if (!this.triggered) {
this.onEventOrInput('');
}
}
private enable(): void {
if (this._enabled) {
open(): void {
if (this.widget) {
this.widget.focus();
this.widget.select();
return;
}
const onRawKeyDown = this.enabledDisposables.add(new DomEmitter(this.view.getHTMLElement(), 'keydown'));
const onKeyDown = Event.chain(onRawKeyDown.event)
.filter(e => !isInputElement(e.target as HTMLElement) || e.target === this.filterOnTypeDomNode)
.filter(e => e.key !== 'Dead' && !/^Media/.test(e.key))
.map(e => new StandardKeyboardEvent(e))
.filter(this.keyboardNavigationEventFilter || (() => true))
.filter(() => this.automaticKeyboardNavigation || this.triggered)
.filter(e => (this.keyboardNavigationDelegate.mightProducePrintableCharacter(e) && !(e.keyCode === KeyCode.DownArrow || e.keyCode === KeyCode.UpArrow || e.keyCode === KeyCode.LeftArrow || e.keyCode === KeyCode.RightArrow)) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? (e.altKey && !e.metaKey) : e.ctrlKey) && !e.shiftKey)))
.forEach(e => { e.stopPropagation(); e.preventDefault(); })
.event;
this.mode = this.tree.options.defaultFindMode ?? TreeFindMode.Highlight;
this.widget = new FindWidget(this.view.getHTMLElement(), this.tree, this.contextViewProvider, this.mode, this.styles);
this.enabledDisposables.add(this.widget);
const onClearClick = this.enabledDisposables.add(new DomEmitter(this.clearDomNode, 'click'));
this.widget.onDidChangeValue(this.onDidChangeValue, this, this.enabledDisposables);
this.widget.onDidChangeMode(mode => this.mode = mode, undefined, this.enabledDisposables);
this.widget.onDidDisable(this.close, this, this.enabledDisposables);
Event.chain(Event.any<MouseEvent | StandardKeyboardEvent>(onKeyDown, onClearClick.event))
.event(this.onEventOrInput, this, this.enabledDisposables);
this.widget.layout(this.width);
this.widget.focus();
this.filter.pattern = '';
this.tree.refilter();
this.render();
this._enabled = true;
this.triggered = false;
this._onDidChangeOpenState.fire(true);
}
private disable(): void {
if (!this._enabled) {
close(): void {
if (!this.widget) {
return;
}
this.domNode.remove();
this.enabledDisposables.clear();
this.tree.refilter();
this.render();
this._enabled = false;
this.triggered = false;
this.widget = undefined;
this.enabledDisposables.dispose();
this.enabledDisposables = new DisposableStore();
this.onDidChangeValue('');
this.tree.domFocus();
this._onDidChangeOpenState.fire(false);
}
private onEventOrInput(e: MouseEvent | StandardKeyboardEvent | string): void {
if (typeof e === 'string') {
this.onInput(e);
} else if (e instanceof MouseEvent || e.keyCode === KeyCode.Escape || (e.keyCode === KeyCode.Backspace && (isMacintosh ? e.altKey : e.ctrlKey))) {
this.onInput('');
} else if (e.keyCode === KeyCode.Backspace) {
this.onInput(this.pattern.length === 0 ? '' : this.pattern.substr(0, this.pattern.length - 1));
} else {
this.onInput(this.pattern + e.browserEvent.key);
}
}
private onInput(pattern: string): void {
const container = this.view.getHTMLElement();
if (pattern && !this.domNode.parentElement) {
container.append(this.domNode);
} else if (!pattern && this.domNode.parentElement) {
this.domNode.remove();
this.tree.domFocus();
}
private onDidChangeValue(pattern: string): void {
this._pattern = pattern;
this._onDidChangePattern.fire(pattern);
@@ -841,75 +950,10 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
}
this.render();
if (!pattern) {
this.triggered = false;
}
}
private onDragStart(): void {
const container = this.view.getHTMLElement();
const { left } = getDomNodePagePosition(container);
const containerWidth = container.clientWidth;
const midContainerWidth = containerWidth / 2;
const width = this.domNode.clientWidth;
const disposables = new DisposableStore();
let positionClassName = this.positionClassName;
const updatePosition = () => {
switch (positionClassName) {
case 'nw':
this.domNode.style.top = `4px`;
this.domNode.style.left = `4px`;
break;
case 'ne':
this.domNode.style.top = `4px`;
this.domNode.style.left = `${containerWidth - width - 6}px`;
break;
}
};
const onDragOver = (event: DragEvent) => {
event.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
const x = event.clientX - left;
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'none';
}
if (x < midContainerWidth) {
positionClassName = 'nw';
} else {
positionClassName = 'ne';
}
updatePosition();
};
const onDragEnd = () => {
this.positionClassName = positionClassName;
this.domNode.className = `monaco-list-type-filter ${this.positionClassName}`;
this.domNode.style.top = '';
this.domNode.style.left = '';
dispose(disposables);
};
updatePosition();
this.domNode.classList.remove(positionClassName);
this.domNode.classList.add('dragging');
disposables.add(toDisposable(() => this.domNode.classList.remove('dragging')));
disposables.add(addDisposableListener(document, 'dragover', e => onDragOver(e)));
disposables.add(addDisposableListener(this.domNode, 'dragend', () => onDragEnd()));
StaticDND.CurrentDragAndDropData = new DragAndDropData('vscode-ui');
disposables.add(toDisposable(() => StaticDND.CurrentDragAndDropData = undefined));
}
private onDidSpliceModel(): void {
if (!this._enabled || this.pattern.length === 0) {
if (!this.widget || this.pattern.length === 0) {
return;
}
@@ -917,46 +961,18 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
this.render();
}
private onDidChangeFilterOnType(): void {
this.tree.updateOptions({ filterOnType: this.filterOnTypeDomNode.checked });
this.tree.refilter();
this.tree.domFocus();
this.render();
this.updateFilterOnTypeTitleAndIcon();
}
private updateFilterOnTypeTitleAndIcon(): void {
if (this.filterOnType) {
this.filterOnTypeDomNode.classList.remove(...Codicon.treeFilterOnTypeOff.classNamesArray);
this.filterOnTypeDomNode.classList.add(...Codicon.treeFilterOnTypeOn.classNamesArray);
this.filterOnTypeDomNode.title = localize('disable filter on type', "Disable Filter on Type");
} else {
this.filterOnTypeDomNode.classList.remove(...Codicon.treeFilterOnTypeOn.classNamesArray);
this.filterOnTypeDomNode.classList.add(...Codicon.treeFilterOnTypeOff.classNamesArray);
this.filterOnTypeDomNode.title = localize('enable filter on type', "Enable Filter on Type");
}
}
private render(): void {
const noMatches = this.filter.totalCount > 0 && this.filter.matchCount === 0;
if (this.pattern && this.tree.options.filterOnType && noMatches) {
this.messageDomNode.textContent = localize('empty', "No elements found");
this._empty = true;
if (this.pattern && noMatches) {
this.widget?.showMessage({ type: MessageType.WARNING, content: localize('not found', "No elements found.") });
} else {
this.messageDomNode.innerText = '';
this._empty = false;
this.widget?.clearMessage();
}
this.domNode.classList.toggle('no-matches', noMatches);
this.domNode.title = localize('found', "Matched {0} out of {1} elements", this.filter.matchCount, this.filter.totalCount);
this.labelDomNode.textContent = this.pattern.length > 16 ? '…' + this.pattern.substr(this.pattern.length - 16) : this.pattern;
this._onDidChangeEmptyState.fire(this._empty);
}
shouldAllowFocus(node: ITreeNode<T, TFilterData>): boolean {
if (!this.enabled || !this.pattern || this.filterOnType) {
if (!this.widget || !this.pattern || this._mode === TreeFindMode.Filter) {
return true;
}
@@ -967,16 +983,20 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
return !FuzzyScore.isDefault(node.filterData as any as FuzzyScore);
}
dispose() {
if (this._enabled) {
this.domNode.remove();
this.enabledDisposables.dispose();
this._enabled = false;
this.triggered = false;
}
style(styles: IFindWidgetStyles): void {
this.styles = styles;
this.widget?.style(styles);
}
layout(width: number): void {
this.width = width;
this.widget?.layout(width);
}
dispose() {
this._onDidChangePattern.dispose();
dispose(this.disposables);
this.enabledDisposables.dispose();
this.disposables.dispose();
}
}
@@ -987,6 +1007,8 @@ function asTreeMouseEvent<T>(event: IListMouseEvent<ITreeNode<T, any>>): ITreeMo
target = TreeMouseEventTarget.Twistie;
} else if (hasParentWithClass(event.browserEvent.target as HTMLElement, 'monaco-tl-contents', 'monaco-tl-row')) {
target = TreeMouseEventTarget.Element;
} else if (hasParentWithClass(event.browserEvent.target as HTMLElement, 'monaco-tree-type-filter', 'monaco-list')) {
target = TreeMouseEventTarget.Filter;
}
return {
@@ -1004,15 +1026,11 @@ function asTreeContextMenuEvent<T>(event: IListContextMenuEvent<ITreeNode<T, any
};
}
export interface IKeyboardNavigationEventFilter {
(e: StandardKeyboardEvent): boolean;
}
export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {
readonly multipleSelectionSupport?: boolean;
readonly automaticKeyboardNavigation?: boolean;
readonly simpleKeyboardNavigation?: boolean;
readonly filterOnType?: boolean;
readonly typeNavigationEnabled?: boolean;
readonly typeNavigationMode?: TypeNavigationMode;
readonly defaultFindMode?: TreeFindMode;
readonly smoothScrolling?: boolean;
readonly horizontalScrolling?: boolean;
readonly mouseWheelScrollSensitivity?: number;
@@ -1022,11 +1040,12 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {
}
export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTreeOptionsUpdate, IListOptions<T> {
readonly contextViewProvider?: IContextViewProvider;
readonly collapseByDefault?: boolean; // defaults to false
readonly filter?: ITreeFilter<T, TFilterData>;
readonly dnd?: ITreeDragAndDrop<T>;
readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter;
readonly additionalScrollHeight?: number;
readonly findWidgetEnabled?: boolean;
}
function dfs<T, TFilterData>(node: ITreeNode<T, TFilterData>, fn: (node: ITreeNode<T, TFilterData>) => void): void {
@@ -1158,7 +1177,9 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
}
protected override onViewPointer(e: IListMouseEvent<ITreeNode<T, TFilterData>>): void {
if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) {
if (isButton(e.browserEvent.target as HTMLElement) ||
isInputElement(e.browserEvent.target as HTMLElement) ||
isMonacoEditor(e.browserEvent.target as HTMLElement)) {
return;
}
@@ -1321,7 +1342,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
private selection: Trait<T>;
private anchor: Trait<T>;
private eventBufferer = new EventBufferer();
private typeFilterController?: TypeFilterController<T, TFilterData>;
private findController?: FindController<T, TFilterData>;
readonly onDidChangeFindOpenState: Event<boolean> = Event.None;
private focusNavigationFilter: ((node: ITreeNode<T, TFilterData>) => boolean) | undefined;
private styleElement: HTMLStyleElement;
protected readonly disposables = new DisposableStore();
@@ -1332,7 +1354,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
get onDidChangeSelection(): Event<ITreeEvent<T>> { return this.eventBufferer.wrapEvent(this.selection.onDidChange); }
get onMouseClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onMouseClick, asTreeMouseEvent); }
get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onMouseDblClick, asTreeMouseEvent); }
get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return Event.filter(Event.map(this.view.onMouseDblClick, asTreeMouseEvent), e => e.target !== TreeMouseEventTarget.Filter); }
get onContextMenu(): Event<ITreeContextMenuEvent<T>> { return Event.map(this.view.onContextMenu, asTreeContextMenuEvent); }
get onTap(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onTap, asTreeMouseEvent); }
get onPointer(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onPointer, asTreeMouseEvent); }
@@ -1351,8 +1373,11 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
private readonly _onWillRefilter = new Emitter<void>();
readonly onWillRefilter: Event<void> = this._onWillRefilter.event;
get filterOnType(): boolean { return !!this._options.filterOnType; }
get onDidChangeTypeFilterPattern(): Event<string> { return this.typeFilterController ? this.typeFilterController.onDidChangePattern : Event.None; }
get findMode(): TreeFindMode { return this.findController?.mode ?? TreeFindMode.Highlight; }
set findMode(findMode: TreeFindMode) { if (this.findController) { this.findController.mode = findMode; } }
readonly onDidChangeFindMode: Event<TreeFindMode>;
get onDidChangeFindPattern(): Event<string> { return this.findController ? this.findController.onDidChangePattern : Event.None; }
get expandOnDoubleClick(): boolean { return typeof this._options.expandOnDoubleClick === 'undefined' ? true : this._options.expandOnDoubleClick; }
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? true : this._options.expandOnlyOnTwistieClick; }
@@ -1373,16 +1398,16 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
const onDidChangeCollapseStateRelay = new Relay<ICollapseStateChangeEvent<T, TFilterData>>();
const onDidChangeActiveNodes = new Relay<ITreeNode<T, TFilterData>[]>();
const activeNodes = new EventCollection(onDidChangeActiveNodes.event);
const activeNodes = this.disposables.add(new EventCollection(onDidChangeActiveNodes.event));
this.renderers = renderers.map(r => new TreeRenderer<T, TFilterData, TRef, any>(r, () => this.model, onDidChangeCollapseStateRelay.event, activeNodes, _options));
for (let r of this.renderers) {
for (const r of this.renderers) {
this.disposables.add(r);
}
let filter: TypeFilter<T> | undefined;
let filter: FindFilter<T> | undefined;
if (_options.keyboardNavigationLabelProvider) {
filter = new TypeFilter(this, _options.keyboardNavigationLabelProvider, _options.filter as any as ITreeFilter<T, FuzzyScore>);
filter = new FindFilter(this, _options.keyboardNavigationLabelProvider, _options.filter as any as ITreeFilter<T, FuzzyScore>);
_options = { ..._options, filter: filter as ITreeFilter<T, TFilterData> }; // TODO need typescript help here
this.disposables.add(filter);
}
@@ -1400,7 +1425,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
this.focus.onDidModelSplice(e);
this.selection.onDidModelSplice(e);
});
});
}, this.disposables);
// Make sure the `forEach` always runs
onDidModelSplice(() => null, null, this.disposables);
@@ -1435,11 +1460,14 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
onKeyDown.filter(e => e.keyCode === KeyCode.Space).on(this.onSpace, this, this.disposables);
}
if (_options.keyboardNavigationLabelProvider) {
const delegate = _options.keyboardNavigationDelegate || DefaultKeyboardNavigationDelegate;
this.typeFilterController = new TypeFilterController(this, this.model, this.view, filter!, delegate);
this.focusNavigationFilter = node => this.typeFilterController!.shouldAllowFocus(node);
this.disposables.add(this.typeFilterController!);
if ((_options.findWidgetEnabled ?? true) && _options.keyboardNavigationLabelProvider && _options.contextViewProvider) {
this.findController = new FindController(this, this.model, this.view, filter!, _options.contextViewProvider);
this.focusNavigationFilter = node => this.findController!.shouldAllowFocus(node);
this.onDidChangeFindOpenState = this.findController.onDidChangeOpenState;
this.disposables.add(this.findController!);
this.onDidChangeFindMode = this.findController.onDidChangeMode;
} else {
this.onDidChangeFindMode = Event.None;
}
this.styleElement = createStyleSheet(this.view.getHTMLElement());
@@ -1453,15 +1481,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
renderer.updateOptions(optionsUpdate);
}
this.view.updateOptions({
...this._options,
enableKeyboardNavigation: this._options.simpleKeyboardNavigation,
});
if (this.typeFilterController) {
this.typeFilterController.updateOptions(this._options);
}
this.view.updateOptions(this._options);
this._onDidUpdateOptions.fire(this._options);
this.getHTMLElement().classList.toggle('always', this._options.renderIndentGuides === RenderIndentGuides.Always);
@@ -1488,21 +1508,11 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
}
get contentHeight(): number {
if (this.typeFilterController && this.typeFilterController.filterOnType && this.typeFilterController.empty) {
return 100;
}
return this.view.contentHeight;
}
get onDidChangeContentHeight(): Event<number> {
let result = this.view.onDidChangeContentHeight;
if (this.typeFilterController) {
result = Event.any(result, Event.map(this.typeFilterController.onDidChangeEmptyState, () => this.contentHeight));
}
return result;
return this.view.onDidChangeContentHeight;
}
get scrollTop(): number {
@@ -1564,6 +1574,10 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
layout(height?: number, width?: number): void {
this.view.layout(height, width);
if (isNumber(width)) {
this.findController?.layout(width);
}
}
style(styles: IListStyles): void {
@@ -1576,6 +1590,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
}
this.styleElement.textContent = content.join('\n');
this.findController?.style(styles);
this.view.style(styles);
}
@@ -1629,12 +1645,16 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
return this.model.isCollapsed(location);
}
toggleKeyboardNavigation(): void {
this.view.toggleKeyboardNavigation();
triggerTypeNavigation(): void {
this.view.triggerTypeNavigation();
}
if (this.typeFilterController) {
this.typeFilterController.toggle();
}
openFind(): void {
this.findController?.open();
}
closeFind(): void {
this.findController?.close();
}
refilter(): void {

View File

@@ -7,7 +7,7 @@ import { IDragAndDropData } from 'vs/base/browser/dnd';
import { IIdentityProvider, IListDragAndDrop, IListDragOverReaction, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
import { ComposedTreeDelegate, TreeFindMode as TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
import { ICompressedTreeElement, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel';
import { CompressibleObjectTree, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions, ICompressibleTreeRenderer, IObjectTreeOptions, IObjectTreeSetChildrenOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
@@ -119,9 +119,7 @@ class AsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements IT
}
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
this.renderer.disposeElement?.(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
@@ -196,9 +194,7 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
}
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
if (this.dnd.onDragStart) {
this.dnd.onDragStart(asAsyncDataTreeDragAndDropData(data), originalEvent);
}
this.dnd.onDragStart?.(asAsyncDataTreeDragAndDropData(data), originalEvent);
}
onDragOver(data: IDragAndDropData, targetNode: IAsyncDataTreeNode<TInput, T> | undefined, targetIndex: number | undefined, originalEvent: DragEvent, raw = true): boolean | IListDragOverReaction {
@@ -210,9 +206,7 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
}
onDragEnd(originalEvent: DragEvent): void {
if (this.dnd.onDragEnd) {
this.dnd.onDragEnd(originalEvent);
}
this.dnd.onDragEnd?.(originalEvent);
}
}
@@ -347,7 +341,12 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
get onDidUpdateOptions(): Event<IAsyncDataTreeOptionsUpdate> { return this.tree.onDidUpdateOptions; }
get filterOnType(): boolean { return this.tree.filterOnType; }
get onDidChangeFindOpenState(): Event<boolean> { return this.tree.onDidChangeFindOpenState; }
get findMode(): TreeFindMode { return this.tree.findMode; }
set findMode(mode: TreeFindMode) { this.tree.findMode = mode; }
readonly onDidChangeFindMode: Event<TreeFindMode>;
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) {
if (typeof this.tree.expandOnlyOnTwistieClick === 'boolean') {
return this.tree.expandOnlyOnTwistieClick;
@@ -373,6 +372,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
this.collapseByDefault = options.collapseByDefault;
this.tree = this.createTree(user, container, delegate, renderers, options);
this.onDidChangeFindMode = this.tree.onDidChangeFindMode;
this.root = createAsyncDataTreeNode({
element: undefined!,
@@ -622,8 +622,16 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
return this.tree.isCollapsed(this.getDataNode(element));
}
toggleKeyboardNavigation(): void {
this.tree.toggleKeyboardNavigation();
triggerTypeNavigation(): void {
this.tree.triggerTypeNavigation();
}
openFind(): void {
this.tree.openFind();
}
closeFind(): void {
this.tree.closeFind();
}
refilter(): void {
@@ -734,6 +742,16 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
return result;
}
if (node !== this.root) {
const treeNode = this.tree.getNode(node);
if (treeNode.collapsed) {
node.hasChildren = !!this.dataSource.hasChildren(node.element!);
node.stale = true;
return;
}
}
return this.doRefreshSubTree(node, recursive, viewStateContext);
}
@@ -1086,15 +1104,11 @@ class CompressibleAsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> i
}
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
this.renderer.disposeElement?.(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
}
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
if (this.renderer.disposeCompressedElements) {
this.renderer.disposeCompressedElements(this.compressibleNodeMapperProvider().map(node) as ITreeNode<ICompressedTreeNode<T>, TFilterData>, index, templateData.templateData, height);
}
this.renderer.disposeCompressedElements?.(this.compressibleNodeMapperProvider().map(node) as ITreeNode<ICompressedTreeNode<T>, TFilterData>, index, templateData.templateData, height);
}
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
@@ -1189,10 +1203,10 @@ export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends As
const expanded: string[] = [];
const root = this.tree.getCompressedTreeNode();
const queue = [root];
const stack = [root];
while (queue.length > 0) {
const node = queue.shift()!;
while (stack.length > 0) {
const node = stack.pop()!;
if (node !== root && node.collapsible && !node.collapsed) {
for (const asyncNode of node.element!.elements) {
@@ -1200,7 +1214,7 @@ export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends As
}
}
queue.push(...node.children);
stack.push(...node.children);
}
return { focus, selection, expanded, scrollTop: this.scrollTop };

View File

@@ -140,9 +140,7 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
insertedElements.add(id);
this.nodesByIdentity.set(id, node);
if (outerOnDidCreateNode) {
outerOnDidCreateNode(node);
}
outerOnDidCreateNode?.(node);
};
onDidDeleteNode = (node: ITreeNode<T, TFilterData>) => {

View File

@@ -554,9 +554,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
node.renderNodeCount = renderNodeCount;
}
if (onDidCreateNode) {
onDidCreateNode(node);
}
onDidCreateNode?.(node);
return node;
}

View File

@@ -67,3 +67,55 @@
/* Use steps to throttle FPS to reduce CPU usage */
animation: codicon-spin 1.25s steps(30) infinite;
}
.monaco-tree-type-filter {
position: absolute;
top: 0;
display: flex;
padding: 3px;
transition: top 0.3s;
max-width: 200px;
z-index: 100;
margin: 0 6px;
}
.monaco-tree-type-filter.disabled {
top: -40px;
}
.monaco-tree-type-filter-grab {
display: flex !important;
align-items: center;
justify-content: center;
cursor: grab;
margin-right: 2px;
}
.monaco-tree-type-filter-grab.grabbing {
cursor: grabbing;
}
.monaco-tree-type-filter-input {
flex: 1;
}
.monaco-tree-type-filter-input .monaco-inputbox {
height: 23px;
}
.monaco-tree-type-filter-input .monaco-inputbox > .ibwrapper > .input,
.monaco-tree-type-filter-input .monaco-inputbox > .ibwrapper > .mirror {
padding: 2px 4px;
}
.monaco-tree-type-filter-input .monaco-findInput > .controls {
top: 2px;
}
.monaco-tree-type-filter-actionbar {
margin-left: 4px;
}
.monaco-tree-type-filter-actionbar .monaco-action-bar .action-label {
padding: 2px;
}

View File

@@ -140,13 +140,9 @@ class CompressibleRenderer<T extends NonNullable<any>, TFilterData, TTemplateDat
disposeElement(node: ITreeNode<T, TFilterData>, index: number, templateData: CompressibleTemplateData<T, TFilterData, TTemplateData>, height: number | undefined): void {
if (templateData.compressedTreeNode) {
if (this.renderer.disposeCompressedElements) {
this.renderer.disposeCompressedElements(templateData.compressedTreeNode, index, templateData.data, height);
}
this.renderer.disposeCompressedElements?.(templateData.compressedTreeNode, index, templateData.data, height);
} else {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(node, index, templateData.data, height);
}
this.renderer.disposeElement?.(node, index, templateData.data, height);
}
}

View File

@@ -141,7 +141,8 @@ export interface ITreeEvent<T> {
export enum TreeMouseEventTarget {
Unknown,
Twistie,
Element
Element,
Filter
}
export interface ITreeMouseEvent<T> {

View File

@@ -23,8 +23,8 @@ export abstract class Widget extends Disposable {
this._register(dom.addDisposableListener(domNode, dom.EventType.MOUSE_OVER, (e: MouseEvent) => listener(new StandardMouseEvent(e))));
}
protected onnonbubblingmouseout(domNode: HTMLElement, listener: (e: IMouseEvent) => void): void {
this._register(dom.addDisposableNonBubblingMouseOutListener(domNode, (e: MouseEvent) => listener(new StandardMouseEvent(e))));
protected onmouseleave(domNode: HTMLElement, listener: (e: IMouseEvent) => void): void {
this._register(dom.addDisposableListener(domNode, dom.EventType.MOUSE_LEAVE, (e: MouseEvent) => listener(new StandardMouseEvent(e))));
}
protected onkeydown(domNode: HTMLElement, listener: (e: IKeyboardEvent) => void): void {

View File

@@ -14,8 +14,9 @@ export interface ITelemetryData {
}
export type WorkbenchActionExecutedClassification = {
id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
owner: 'bpasero';
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.' };
};
export type WorkbenchActionExecutedEvent = {

View File

@@ -26,7 +26,7 @@ export abstract class LoaderStats {
}
function diff(map: Map<string, number>, stat: LoaderEvent) {
let duration = map.get(stat.detail);
const duration = map.get(stat.detail);
if (!duration) {
// console.warn('BAD events, end WITHOUT start', stat);
// map.delete(stat.detail);
@@ -79,7 +79,7 @@ export abstract class LoaderStats {
nodeRequire.forEach(value => nodeRequireTotal += value);
function to2dArray(map: Map<string, number>): [string, number][] {
let res: [string, number][] = [];
const res: [string, number][] = [];
map.forEach((value, index) => res.push([index, value]));
return res;
}
@@ -96,7 +96,7 @@ export abstract class LoaderStats {
static toMarkdownTable(header: string[], rows: Array<Array<{ toString(): string } | undefined>>): string {
let result = '';
let lengths: number[] = [];
const lengths: number[] = [];
header.forEach((cell, ci) => {
lengths[ci] = cell.length;
});

View File

@@ -46,13 +46,55 @@ export function equals<T>(one: ReadonlyArray<T> | undefined, other: ReadonlyArra
return true;
}
/**
* Remove the element at `index` by replacing it with the last element. This is faster than `splice`
* but changes the order of the array
*/
export function removeFastWithoutKeepingOrder<T>(array: T[], index: number) {
const last = array.length - 1;
if (index < last) {
array[index] = array[last];
}
array.pop();
}
/**
* Performs a binary search algorithm over a sorted array.
*
* @param array The array being searched.
* @param key The value we search for.
* @param comparator A function that takes two array elements and returns zero
* if they are equal, a negative number if the first element precedes the
* second one in the sorting order, or a positive number if the second element
* precedes the first one.
* @return See {@link binarySearch2}
*/
export function binarySearch<T>(array: ReadonlyArray<T>, key: T, comparator: (op1: T, op2: T) => number): number {
return binarySearch2(array.length, i => comparator(array[i], key));
}
/**
* Performs a binary search algorithm over a sorted collection. Useful for cases
* when we need to perform a binary search over something that isn't actually an
* array, and converting data to an array would defeat the use of binary search
* in the first place.
*
* @param length The collection length.
* @param compareToKey A function that takes an index of an element in the
* collection and returns zero if the value at this index is equal to the
* search key, a negative number if the value precedes the search key in the
* sorting order, or a positive number if the search key precedes the value.
* @return A non-negative index of an element, if found. If not found, the
* result is -(n+1) (or ~n, using bitwise notation), where n is the index
* where the key should be inserted to maintain the sorting order.
*/
export function binarySearch2(length: number, compareToKey: (index: number) => number): number {
let low = 0,
high = array.length - 1;
high = length - 1;
while (low <= high) {
const mid = ((low + high) / 2) | 0;
const comp = comparator(array[mid], key);
const comp = compareToKey(mid);
if (comp < 0) {
low = mid + 1;
} else if (comp > 0) {
@@ -96,12 +138,12 @@ export function quickSelect<T>(nth: number, data: T[], compare: Compare<T>): T {
throw new TypeError('invalid index');
}
let pivotValue = data[Math.floor(data.length * Math.random())];
let lower: T[] = [];
let higher: T[] = [];
let pivots: T[] = [];
const pivotValue = data[Math.floor(data.length * Math.random())];
const lower: T[] = [];
const higher: T[] = [];
const pivots: T[] = [];
for (let value of data) {
for (const value of data) {
const val = compare(value, pivotValue);
if (val < 0) {
lower.push(value);
@@ -610,17 +652,55 @@ function getActualStartIndex<T>(array: T[], start: number): number {
return start < 0 ? Math.max(start + array.length, 0) : Math.min(start, array.length);
}
/**
* When comparing two values,
* a negative number indicates that the first value is less than the second,
* a positive number indicates that the first value is greater than the second,
* and zero indicates that neither is the case.
*/
export type CompareResult = number;
export namespace CompareResult {
export function isLessThan(result: CompareResult): boolean {
return result < 0;
}
export function isGreaterThan(result: CompareResult): boolean {
return result > 0;
}
export function isNeitherLessOrGreaterThan(result: CompareResult): boolean {
return result === 0;
}
export const greaterThan = 1;
export const lessThan = -1;
export const neitherLessOrGreaterThan = 0;
}
/**
* A comparator `c` defines a total order `<=` on `T` as following:
* `c(a, b) <= 0` iff `a` <= `b`.
* We also have `c(a, b) == 0` iff `c(b, a) == 0`.
*/
export type Comparator<T> = (a: T, b: T) => number;
export type Comparator<T> = (a: T, b: T) => CompareResult;
export function compareBy<TItem, TCompareBy>(selector: (item: TItem) => TCompareBy, comparator: Comparator<TCompareBy>): Comparator<TItem> {
return (a, b) => comparator(selector(a), selector(b));
}
export function tieBreakComparators<TItem>(...comparators: Comparator<TItem>[]): Comparator<TItem> {
return (item1, item2) => {
for (const comparator of comparators) {
const result = comparator(item1, item2);
if (!CompareResult.isNeitherLessOrGreaterThan(result)) {
return result;
}
}
return CompareResult.neitherLessOrGreaterThan;
};
}
/**
* The natural order on numbers.
*/
@@ -676,7 +756,7 @@ export class ArrayQueue<T> {
/**
* Constructs a queue that is backed by the given array. Runtime is O(1).
*/
constructor(private readonly items: T[]) { }
constructor(private readonly items: readonly T[]) { }
get length(): number {
return this.lastIdx - this.firstIdx + 1;
@@ -718,15 +798,31 @@ export class ArrayQueue<T> {
}
peek(): T | undefined {
if (this.length === 0) {
return undefined;
}
return this.items[this.firstIdx];
}
peekLast(): T | undefined {
if (this.length === 0) {
return undefined;
}
return this.items[this.lastIdx];
}
dequeue(): T | undefined {
const result = this.items[this.firstIdx];
this.firstIdx++;
return result;
}
removeLast(): T | undefined {
const result = this.items[this.lastIdx];
this.lastIdx--;
return result;
}
takeCount(count: number): T[] {
const result = this.items.slice(this.firstIdx, this.firstIdx + count);
this.firstIdx += count;

View File

@@ -354,9 +354,7 @@ export class Delayer<T> implements IDisposable {
this.cancelTimeout();
if (this.completionPromise) {
if (this.doReject) {
this.doReject(new CancellationError());
}
this.doReject?.(new CancellationError());
this.completionPromise = null;
}
}
@@ -885,9 +883,7 @@ export class RunOnceScheduler {
}
protected doRun(): void {
if (this.runner) {
this.runner();
}
this.runner?.();
}
}
@@ -960,9 +956,7 @@ export class ProcessTimeRunOnceScheduler {
// time elapsed
clearInterval(this.intervalToken);
this.intervalToken = -1;
if (this.runner) {
this.runner();
}
this.runner?.();
}
}
@@ -985,9 +979,7 @@ export class RunOnceWorker<T> extends RunOnceScheduler {
const units = this.units;
this.units = [];
if (this.runner) {
this.runner(units);
}
this.runner?.(units);
}
override dispose(): void {

View File

@@ -429,7 +429,8 @@ export class Codicon implements CSSIcon {
public static readonly debugBreakpointFunction = new Codicon('debug-breakpoint-function', { fontCharacter: '\\eb88' });
public static readonly debugBreakpointFunctionDisabled = new Codicon('debug-breakpoint-function-disabled', { fontCharacter: '\\eb88' });
public static readonly debugStackframeActive = new Codicon('debug-stackframe-active', { fontCharacter: '\\eb89' });
public static readonly debugStackframeDot = new Codicon('debug-stackframe-dot', { fontCharacter: '\\eb8a' });
public static readonly circleSmallFilled = new Codicon('circle-small-filled', { fontCharacter: '\\eb8a' });
public static readonly debugStackframeDot = new Codicon('debug-stackframe-dot', Codicon.circleSmallFilled.definition);
public static readonly debugStackframe = new Codicon('debug-stackframe', { fontCharacter: '\\eb8b' });
public static readonly debugStackframeFocused = new Codicon('debug-stackframe-focused', { fontCharacter: '\\eb8b' });
public static readonly debugBreakpointUnsupported = new Codicon('debug-breakpoint-unsupported', { fontCharacter: '\\eb8c' });
@@ -542,6 +543,9 @@ export class Codicon implements CSSIcon {
public static readonly layoutStatusbar = new Codicon('layout-statusbar', { fontCharacter: '\\ebf5' });
public static readonly layoutMenubar = new Codicon('layout-menubar', { fontCharacter: '\\ebf6' });
public static readonly layoutCentered = new Codicon('layout-centered', { fontCharacter: '\\ebf7' });
public static readonly layoutSidebarRightOff = new Codicon('layout-sidebar-right-off', { fontCharacter: '\\ec00' });
public static readonly layoutPanelOff = new Codicon('layout-panel-off', { fontCharacter: '\\ec01' });
public static readonly layoutSidebarLeftOff = new Codicon('layout-sidebar-left-off', { fontCharacter: '\\ec02' });
public static readonly target = new Codicon('target', { fontCharacter: '\\ebf8' });
public static readonly indent = new Codicon('indent', { fontCharacter: '\\ebf9' });
public static readonly recordSmall = new Codicon('record-small', { fontCharacter: '\\ebfa' });
@@ -550,6 +554,15 @@ export class Codicon implements CSSIcon {
public static readonly arrowCircleLeft = new Codicon('arrow-circle-left', { fontCharacter: '\\ebfd' });
public static readonly arrowCircleRight = new Codicon('arrow-circle-right', { fontCharacter: '\\ebfe' });
public static readonly arrowCircleUp = new Codicon('arrow-circle-up', { fontCharacter: '\\ebff' });
public static readonly heartFilled = new Codicon('heart-filled', { fontCharacter: '\\ec04' });
public static readonly map = new Codicon('map', { fontCharacter: '\\ec05' });
public static readonly mapFilled = new Codicon('map-filled', { fontCharacter: '\\ec06' });
public static readonly circleSmall = new Codicon('circle-small', { fontCharacter: '\\ec07' });
public static readonly bellSlash = new Codicon('bell-slash', { fontCharacter: '\\ec08' });
public static readonly bellSlashDot = new Codicon('bell-slash-dot', { fontCharacter: '\\ec09' });
public static readonly commentUnresolved = new Codicon('comment-unresolved', { fontCharacter: '\\ec0a' });
public static readonly gitPullRequestGoToChanges = new Codicon('git-pull-request-go-to-changes', { fontCharacter: '\\ec0b' });
public static readonly gitPullRequestNewChanges = new Codicon('git-pull-request-new-changes', { fontCharacter: '\\ec0c' });
// derived icons, that could become separate icons
@@ -612,7 +625,7 @@ export namespace CSSIcon {
if (!match) {
return asClassNameArray(Codicon.error);
}
let [, id, modifier] = match;
const [, id, modifier] = match;
// {{SQL CARBON EDIT}} Modifying method to not add 'codicon' in front of sql carbon icons.
let sqlCarbonIcons: string[] = [SqlIconId.activeConnectionsAction, SqlIconId.addServerAction, SqlIconId.addServerGroupAction, SqlIconId.serverPage];

View File

@@ -9,13 +9,13 @@
*/
export type IStringDictionary<V> = Record<string, V>;
/**
* An interface for a JavaScript object that
* acts a dictionary. The keys are numbers.
*/
export type INumberDictionary<V> = Record<number, V>;
// {{ SQL CARBON EDIT }} - BEGIN - Needed to retrive values from IStringDictionary's and INumberDictionary's
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
@@ -31,6 +31,9 @@ export function values<T>(from: IStringDictionary<T> | INumberDictionary<T>): T[
}
return result;
}
// {{SQL CARBON EDIT}} - END - Needed to retrive values from IStringDictionary's and INumberDictionary's
// {{ SQL CARBON EDIT }} - BEGIN - Adding forEach definition
/**
* Iterates over each entry in the provided dictionary. The iterator allows
@@ -51,6 +54,8 @@ export function forEach<T>(from: IStringDictionary<T> | INumberDictionary<T>, ca
}
}
// {{ SQL CARBON EDIT }} - END - Adding forEach definition
/**
* Groups the collection into a dictionary based on the provided
* group function.
@@ -68,25 +73,15 @@ export function groupBy<K extends string | number | symbol, V>(data: V[], groupF
return result;
}
export function fromMap<T>(original: Map<string, T>): IStringDictionary<T> {
const result: IStringDictionary<T> = Object.create(null);
if (original) {
original.forEach((value, key) => {
result[key] = value;
});
}
return result;
}
export function diffSets<T>(before: Set<T>, after: Set<T>): { removed: T[]; added: T[] } {
const removed: T[] = [];
const added: T[] = [];
for (let element of before) {
for (const element of before) {
if (!after.has(element)) {
removed.push(element);
}
}
for (let element of after) {
for (const element of after) {
if (!before.has(element)) {
added.push(element);
}
@@ -97,12 +92,12 @@ export function diffSets<T>(before: Set<T>, after: Set<T>): { removed: T[]; adde
export function diffMaps<K, V>(before: Map<K, V>, after: Map<K, V>): { removed: V[]; added: V[] } {
const removed: V[] = [];
const added: V[] = [];
for (let [index, value] of before) {
for (const [index, value] of before) {
if (!after.has(index)) {
removed.push(value);
}
}
for (let [index, value] of after) {
for (const [index, value] of after) {
if (!before.has(index)) {
added.push(value);
}

View File

@@ -219,7 +219,7 @@ function extractExtension(str?: string | null): string {
function compareAndDisambiguateByLength(collator: Intl.Collator, one: string, other: string) {
// Check for differences
let result = collator.compare(one, other);
const result = collator.compare(one, other);
if (result !== 0) {
return result;
}

View File

@@ -11,7 +11,7 @@ export interface IRemoteConsoleLog {
arguments: string;
}
interface IStackArgument {
export interface IStackArgument {
__$stack: string;
}

View File

@@ -0,0 +1,90 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
export interface IDataTransferFile {
readonly name: string;
readonly uri?: URI;
data(): Promise<Uint8Array>;
}
export interface IDataTransferItem {
asString(): Thenable<string>;
asFile(): IDataTransferFile | undefined;
value: any;
}
export function createStringDataTransferItem(stringOrPromise: string | Promise<string>): IDataTransferItem {
return {
asString: async () => stringOrPromise,
asFile: () => undefined,
value: typeof stringOrPromise === 'string' ? stringOrPromise : undefined,
};
}
export function createFileDataTransferItem(fileName: string, uri: URI | undefined, data: () => Promise<Uint8Array>): IDataTransferItem {
return {
asString: async () => '',
asFile: () => ({ name: fileName, uri, data }),
value: undefined,
};
}
export class VSDataTransfer {
private readonly _entries = new Map<string, IDataTransferItem[]>();
public get size(): number {
return this._entries.size;
}
public has(mimeType: string): boolean {
return this._entries.has(this.toKey(mimeType));
}
public get(mimeType: string): IDataTransferItem | undefined {
return this._entries.get(this.toKey(mimeType))?.[0];
}
public append(mimeType: string, value: IDataTransferItem): void {
const existing = this._entries.get(mimeType);
if (existing) {
existing.push(value);
} else {
this._entries.set(this.toKey(mimeType), [value]);
}
}
public replace(mimeType: string, value: IDataTransferItem): void {
this._entries.set(this.toKey(mimeType), [value]);
}
public delete(mimeType: string) {
this._entries.delete(this.toKey(mimeType));
}
public *entries(): Iterable<[string, IDataTransferItem]> {
for (const [mine, items] of this._entries.entries()) {
for (const item of items) {
yield [mine, item];
}
}
}
public values(): Iterable<IDataTransferItem> {
return Array.from(this._entries.values()).flat();
}
public forEach(f: (value: IDataTransferItem, key: string) => void) {
for (const [mime, item] of this.entries()) {
f(item, mime);
}
}
private toKey(mimeType: string): string {
return mimeType.toLowerCase();
}
}

View File

@@ -851,7 +851,7 @@ export class LcsDiff {
change.modifiedStart++;
}
let mergedChangeArr: Array<DiffChange | null> = [null];
const mergedChangeArr: Array<DiffChange | null> = [null];
if (i < changes.length - 1 && this.ChangesOverlap(changes[i], changes[i + 1], mergedChangeArr)) {
changes[i] = mergedChangeArr[0]!;
changes.splice(i + 1, 1);
@@ -1047,7 +1047,7 @@ export class LcsDiff {
* @returns The concatenated list
*/
private ConcatenateChanges(left: DiffChange[], right: DiffChange[]): DiffChange[] {
let mergedChangeArr: DiffChange[] = [];
const mergedChangeArr: DiffChange[] = [];
if (left.length === 0 || right.length === 0) {
return (right.length > 0) ? right : left;

View File

@@ -23,6 +23,10 @@ export class ErrorHandler {
this.unexpectedErrorHandler = function (e: any) {
setTimeout(() => {
if (e.stack) {
if (ErrorNoTelemetry.isErrorNoTelemetry(e)) {
throw new ErrorNoTelemetry(e.message + '\n\n' + e.stack);
}
throw new Error(e.message + '\n\n' + e.stack);
}
@@ -95,13 +99,14 @@ export interface SerializedError {
readonly name: string;
readonly message: string;
readonly stack: string;
readonly noTelemetry: boolean;
}
export function transformErrorForSerialization(error: Error): SerializedError;
export function transformErrorForSerialization(error: any): any;
export function transformErrorForSerialization(error: any): any {
if (error instanceof Error) {
let { name, message } = error;
const { name, message } = error;
let errorCode = (<any>error).errorCode; // {{SQL CARBON EDIT}} Add error code to retain more information
const stack: string = (<any>error).stacktrace || (<any>error).stack;
return {
@@ -236,24 +241,27 @@ export class ExpectedError extends Error {
* Error that when thrown won't be logged in telemetry as an unhandled error.
*/
export class ErrorNoTelemetry extends Error {
override readonly name: string;
public static fromError(err: any): ErrorNoTelemetry {
if (err && err instanceof ErrorNoTelemetry) {
constructor(msg?: string) {
super(msg);
this.name = 'ErrorNoTelemetry';
}
public static fromError(err: Error): ErrorNoTelemetry {
if (err instanceof ErrorNoTelemetry) {
return err;
}
if (err && err instanceof Error) {
const result = new ErrorNoTelemetry();
result.name = err.name;
result.message = err.message;
result.stack = err.stack;
return result;
}
return new ErrorNoTelemetry(err);
const result = new ErrorNoTelemetry();
result.message = err.message;
result.stack = err.stack;
return result;
}
readonly logTelemetry = false;
public static isErrorNoTelemetry(err: Error): err is ErrorNoTelemetry {
return err.name === 'ErrorNoTelemetry';
}
}
/**
@@ -262,8 +270,8 @@ export class ErrorNoTelemetry extends Error {
* Only catch this error to recover gracefully from bugs.
*/
export class BugIndicatingError extends Error {
constructor(message: string) {
super(message);
constructor(message?: string) {
super(message || 'An unexpected bug occurred.');
Object.setPrototypeOf(this, BugIndicatingError.prototype);
// Because we know for sure only buggy code throws this,

View File

@@ -8,13 +8,14 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { once as onceFn } from 'vs/base/common/functional';
import { combinedDisposable, Disposable, DisposableStore, IDisposable, SafeDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList';
import { IObservable, IObserver } from 'vs/base/common/observable';
import { StopWatch } from 'vs/base/common/stopwatch';
// -----------------------------------------------------------------------------------------------------------------------
// Uncomment the next line to print warnings whenever an emitter with listeners is disposed. That is a sign of code smell.
// -----------------------------------------------------------------------------------------------------------------------
let _enableDisposeWithListenerWarning = false;
const _enableDisposeWithListenerWarning = false;
// _enableDisposeWithListenerWarning = Boolean("TRUE"); // causes a linter warning so that it cannot be pushed
@@ -22,7 +23,7 @@ let _enableDisposeWithListenerWarning = false;
// Uncomment the next line to print warnings whenever a snapshotted event is used repeatedly without cleanup.
// See https://github.com/microsoft/vscode/issues/142851
// -----------------------------------------------------------------------------------------------------------------------
let _enableSnapshotPotentialLeakWarning = false;
const _enableSnapshotPotentialLeakWarning = false;
// _enableSnapshotPotentialLeakWarning = Boolean("TRUE"); // causes a linter warning so that it cannot be pushed
/**
@@ -60,7 +61,7 @@ export namespace Event {
return (listener, thisArgs = null, disposables?) => {
// we need this, in case the event fires during the listener call
let didFire = false;
let result: IDisposable;
let result: IDisposable | undefined = undefined;
result = event(e => {
if (didFire) {
return;
@@ -143,14 +144,14 @@ export namespace Event {
}
function snapshot<T>(event: Event<T>, disposable: DisposableStore | undefined): Event<T> {
let listener: IDisposable;
let listener: IDisposable | undefined;
const options: EmitterOptions | undefined = {
onFirstListenerAdd() {
listener = event(emitter.fire, emitter);
},
onLastListenerRemove() {
listener.dispose();
listener?.dispose();
}
};
@@ -160,9 +161,7 @@ export namespace Event {
const emitter = new Emitter<T>(options);
if (disposable) {
disposable.add(emitter);
}
disposable?.add(emitter);
return emitter.event;
}
@@ -223,9 +222,7 @@ export namespace Event {
const emitter = new Emitter<O>(options);
if (disposable) {
disposable.add(emitter);
}
disposable?.add(emitter);
return emitter.event;
}
@@ -276,9 +273,7 @@ export namespace Event {
});
const flush = () => {
if (buffer) {
buffer.forEach(e => emitter.fire(e));
}
buffer?.forEach(e => emitter.fire(e));
buffer = null;
};
@@ -310,7 +305,7 @@ export namespace Event {
return emitter.event;
}
export interface IChainableEvent<T> {
export interface IChainableEvent<T> extends IDisposable {
event: Event<T>;
map<O>(fn: (i: T) => O): IChainableEvent<O>;
@@ -327,34 +322,36 @@ export namespace Event {
class ChainableEvent<T> implements IChainableEvent<T> {
private readonly disposables = new DisposableStore();
constructor(readonly event: Event<T>) { }
map<O>(fn: (i: T) => O): IChainableEvent<O> {
return new ChainableEvent(map(this.event, fn));
return new ChainableEvent(map(this.event, fn, this.disposables));
}
forEach(fn: (i: T) => void): IChainableEvent<T> {
return new ChainableEvent(forEach(this.event, fn));
return new ChainableEvent(forEach(this.event, fn, this.disposables));
}
filter(fn: (e: T) => boolean): IChainableEvent<T>;
filter<R>(fn: (e: T | R) => e is R): IChainableEvent<R>;
filter(fn: (e: T) => boolean): IChainableEvent<T> {
return new ChainableEvent(filter(this.event, fn));
return new ChainableEvent(filter(this.event, fn, this.disposables));
}
reduce<R>(merge: (last: R | undefined, event: T) => R, initial?: R): IChainableEvent<R> {
return new ChainableEvent(reduce(this.event, merge, initial));
return new ChainableEvent(reduce(this.event, merge, initial, this.disposables));
}
latch(): IChainableEvent<T> {
return new ChainableEvent(latch(this.event));
return new ChainableEvent(latch(this.event, undefined, this.disposables));
}
debounce(merge: (last: T | undefined, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number): IChainableEvent<T>;
debounce<R>(merge: (last: R | undefined, event: T) => R, delay?: number, leading?: boolean, leakWarningThreshold?: number): IChainableEvent<R>;
debounce<R>(merge: (last: R | undefined, event: T) => R, delay: number = 100, leading = false, leakWarningThreshold?: number): IChainableEvent<R> {
return new ChainableEvent(debounce(this.event, merge, delay, leading, leakWarningThreshold));
return new ChainableEvent(debounce(this.event, merge, delay, leading, leakWarningThreshold, this.disposables));
}
on(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[] | DisposableStore) {
@@ -364,11 +361,12 @@ export namespace Event {
once(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[]) {
return once(this.event)(listener, thisArgs, disposables);
}
dispose() {
this.disposables.dispose();
}
}
/**
* @deprecated DO NOT use, this leaks memory
*/
export function chain<T>(event: Event<T>): IChainableEvent<T> {
return new ChainableEvent(event);
}
@@ -426,6 +424,55 @@ export namespace Event {
store?.dispose();
});
}
class EmitterObserver<T> implements IObserver {
readonly emitter: Emitter<T>;
private _counter = 0;
private _hasChanged = false;
constructor(readonly obs: IObservable<T, any>, store: DisposableStore | undefined) {
const options = {
onFirstListenerAdd: () => {
obs.addObserver(this);
},
onLastListenerRemove: () => {
obs.removeObserver(this);
}
};
if (!store) {
_addLeakageTraceLogic(options);
}
this.emitter = new Emitter<T>(options);
if (store) {
store.add(this.emitter);
}
}
beginUpdate<T>(_observable: IObservable<T, void>): void {
// console.assert(_observable === this.obs);
this._counter++;
}
handleChange<T, TChange>(_observable: IObservable<T, TChange>, _change: TChange): void {
this._hasChanged = true;
}
endUpdate<T>(_observable: IObservable<T, void>): void {
if (--this._counter === 0) {
if (this._hasChanged) {
this._hasChanged = false;
this.emitter.fire(this.obs.get());
}
}
}
}
export function fromObservable<T>(obs: IObservable<T, any>, store?: DisposableStore): Event<T> {
const observer = new EmitterObserver(obs, store);
return observer.emitter.event;
}
}
export interface EmitterOptions {
@@ -690,9 +737,7 @@ export class Emitter<T> {
}
const result = listener.subscription.set(() => {
if (removeMonitor) {
removeMonitor();
}
removeMonitor?.();
if (!this._disposed) {
removeListener();
if (this._options && this._options.onLastListenerRemove) {
@@ -730,7 +775,7 @@ export class Emitter<T> {
this._deliveryQueue = new PrivateEventDeliveryQueue();
}
for (let listener of this._listeners) {
for (const listener of this._listeners) {
this._deliveryQueue.push(this, listener, event);
}

View File

@@ -315,7 +315,7 @@ function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], pat
}
function doScoreFuzzy2Single(target: string, query: IPreparedQueryPiece, patternStart: number, wordStart: number): FuzzyScore2 {
const score = fuzzyScore(query.original, query.originalLowercase, patternStart, target, target.toLowerCase(), wordStart);
const score = fuzzyScore(query.original, query.originalLowercase, patternStart, target, target.toLowerCase(), wordStart, { firstMatchCanBeWeak: true, boostFullMatch: true });
if (!score) {
return NO_SCORE2;
}

View File

@@ -139,6 +139,10 @@ export function escapeMarkdownSyntaxTokens(text: string): string {
return text.replace(/[\\`*_{}[\]()#+\-!]/g, '\\$&');
}
export function escapeDoubleQuotes(input: string) {
return input.replace(/"/g, '&quot;');
}
export function removeMarkdownEscapes(text: string): string {
if (!text) {
return text;

View File

@@ -200,12 +200,12 @@ export interface JSONVisitor {
*/
export function createScanner(text: string, ignoreTrivia: boolean = false): JSONScanner {
let pos = 0,
len = text.length,
value: string = '',
tokenOffset = 0,
token: SyntaxKind = SyntaxKind.Unknown,
scanError: ScanError = ScanError.None;
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;
@@ -963,7 +963,7 @@ export function findNodeAtLocation(root: Node, path: JSONPath): Node | undefined
return undefined;
}
let node = root;
for (let segment of path) {
for (const segment of path) {
if (typeof segment === 'string') {
if (node.type !== 'object' || !Array.isArray(node.children)) {
return undefined;
@@ -1019,7 +1019,7 @@ export function getNodeValue(node: Node): any {
return node.children!.map(getNodeValue);
case 'object': {
const obj = Object.create(null);
for (let prop of node.children!) {
for (const prop of node.children!) {
const valueNode = prop.children![1];
if (valueNode) {
obj[prop.children![0].value] = getNodeValue(valueNode);
@@ -1315,11 +1315,11 @@ export function visit(text: string, visitor: JSONVisitor, options: ParseOptions
*/
export function stripComments(text: string, replaceCh?: string): string {
let _scanner = createScanner(text),
parts: string[] = [],
kind: SyntaxKind,
offset = 0,
pos: number;
const _scanner = createScanner(text);
const parts: string[] = [];
let kind: SyntaxKind;
let offset = 0;
let pos: number;
do {
pos = _scanner.getPosition();

View File

@@ -155,7 +155,7 @@ export function applyEdit(text: string, edit: Edit): string {
}
export function applyEdits(text: string, edits: Edit[]): string {
let sortedEdits = edits.slice(0).sort((a, b) => {
const sortedEdits = edits.slice(0).sort((a, b) => {
const diff = a.offset - b.offset;
if (diff === 0) {
return a.length - b.length;
@@ -164,7 +164,7 @@ export function applyEdits(text: string, edits: Edit[]): string {
});
let lastModifiedOffset = text.length;
for (let i = sortedEdits.length - 1; i >= 0; i--) {
let e = sortedEdits[i];
const e = sortedEdits[i];
if (e.offset + e.length <= lastModifiedOffset) {
text = applyEdit(text, e);
} else {

View File

@@ -46,6 +46,7 @@ export interface IJSONSchema {
const?: any;
contains?: IJSONSchema;
propertyNames?: IJSONSchema;
examples?: any[];
// schema draft 07
$comment?: string;
@@ -53,7 +54,27 @@ export interface IJSONSchema {
then?: IJSONSchema;
else?: IJSONSchema;
// VS Code extensions
// 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;

View File

@@ -722,8 +722,8 @@ for (let i = 0; i <= KeyCode.MAX_VALUE; i++) {
[0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_OEM_CLEAR', empty, empty],
];
let seenKeyCode: boolean[] = [];
let seenScanCode: boolean[] = [];
const seenKeyCode: boolean[] = [];
const seenScanCode: boolean[] = [];
for (const mapping of mappings) {
const [_keyCodeOrd, immutable, scanCode, scanCodeStr, keyCode, keyCodeStr, eventKeyCode, vkey, usUserSettingsLabel, generalUserSettingsLabel] = mapping;
if (!seenScanCode[scanCode]) {

View File

@@ -4,11 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import { firstOrDefault } from 'vs/base/common/arrays';
import { hasDriveLetter, isRootOrDriveLetter, toSlashes } from 'vs/base/common/extpath';
import { Schemas } from 'vs/base/common/network';
import { hasDriveLetter, toSlashes } from 'vs/base/common/extpath';
import { posix, sep, win32 } from 'vs/base/common/path';
import { isMacintosh, isWindows, OperatingSystem, OS } from 'vs/base/common/platform';
import { basename, extUri, extUriIgnorePathCase } from 'vs/base/common/resources';
import { extUri, extUriIgnorePathCase } from 'vs/base/common/resources';
import { rtrim, startsWithIgnoreCase } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
@@ -74,7 +73,7 @@ export function getPathLabel(resource: URI, formatting: IPathLabelFormatting): s
// macOS/Linux: tildify with provided user home directory
if (os !== OperatingSystem.Windows && tildifier?.userHome) {
let userHome = tildifier.userHome.fsPath;
const userHome = tildifier.userHome.fsPath;
// This is a bit of a hack, but in order to figure out if the
// resource is in the user home, we need to make sure to convert it
@@ -139,27 +138,6 @@ function getRelativePathLabel(resource: URI, relativePathProvider: IRelativePath
return relativePathLabel;
}
export function getBaseLabel(resource: URI | string): string;
export function getBaseLabel(resource: URI | string | undefined): string | undefined;
export function getBaseLabel(resource: URI | string | undefined): string | undefined {
if (!resource) {
return undefined;
}
if (typeof resource === 'string') {
resource = URI.file(resource);
}
const base = basename(resource) || (resource.scheme === Schemas.file ? resource.fsPath : resource.path) /* can be empty string if '/' is passed in */;
// convert c: => C:
if (isWindows && isRootOrDriveLetter(base)) {
return normalizeDriveLetter(base);
}
return base;
}
export function normalizeDriveLetter(path: string, isWindowsOS: boolean = isWindows): string {
if (hasDriveLetter(path, isWindowsOS)) {
return path.charAt(0).toUpperCase() + path.slice(1);
@@ -239,7 +217,7 @@ export function shorten(paths: string[], pathSeparator: string = sep): string[]
// for every path
let match = false;
for (let pathIndex = 0; pathIndex < paths.length; pathIndex++) {
let originalPath = paths[pathIndex];
const originalPath = paths[pathIndex];
if (originalPath === '') {
shortenedPaths[pathIndex] = `.${pathSeparator}`;

View File

@@ -131,7 +131,7 @@ export function dispose<T extends IDisposable>(disposables: Array<T>): Array<T>;
export function dispose<T extends IDisposable>(disposables: ReadonlyArray<T>): ReadonlyArray<T>;
export function dispose<T extends IDisposable>(arg: T | IterableIterator<T> | undefined): any {
if (Iterable.is(arg)) {
let errors: any[] = [];
const errors: any[] = [];
for (const d of arg) {
if (d) {

View File

@@ -371,13 +371,13 @@ export class TernarySearchTree<K, V> {
if (keys) {
const arr = keys.slice(0);
shuffle(arr);
for (let k of arr) {
for (const k of arr) {
this.set(k, (<V>values));
}
} else {
const arr = (<[K, V][]>values).slice(0);
shuffle(arr);
for (let entry of arr) {
for (const entry of arr) {
this.set(entry[0], entry[1]);
}
}
@@ -715,22 +715,28 @@ export class TernarySearchTree<K, V> {
yield* this._entries(this._root);
}
private *_entries(node: TernarySearchTreeNode<K, V> | undefined): IterableIterator<[K, V]> {
private _entries(node: TernarySearchTreeNode<K, V> | undefined): IterableIterator<[K, V]> {
const result: [K, V][] = [];
this._dfsEntries(node, result);
return result[Symbol.iterator]();
}
private _dfsEntries(node: TernarySearchTreeNode<K, V> | undefined, bucket: [K, V][]) {
// DFS
if (!node) {
return;
}
if (node.left) {
yield* this._entries(node.left);
this._dfsEntries(node.left, bucket);
}
if (node.value) {
yield [node.key!, node.value];
bucket.push([node.key!, node.value]);
}
if (node.mid) {
yield* this._entries(node.mid);
this._dfsEntries(node.mid, bucket);
}
if (node.right) {
yield* this._entries(node.right);
this._dfsEntries(node.right, bucket);
}
}
@@ -819,31 +825,31 @@ export class ResourceMap<T> implements Map<URI, T> {
if (typeof thisArg !== 'undefined') {
clb = clb.bind(thisArg);
}
for (let [_, entry] of this.map) {
for (const [_, entry] of this.map) {
clb(entry.value, entry.uri, <any>this);
}
}
*values(): IterableIterator<T> {
for (let entry of this.map.values()) {
for (const entry of this.map.values()) {
yield entry.value;
}
}
*keys(): IterableIterator<URI> {
for (let entry of this.map.values()) {
for (const entry of this.map.values()) {
yield entry.uri;
}
}
*entries(): IterableIterator<[URI, T]> {
for (let entry of this.map.values()) {
for (const entry of this.map.values()) {
yield [entry.uri, entry.value];
}
}
*[Symbol.iterator](): IterableIterator<[URI, T]> {
for (let [, entry] of this.map) {
for (const [, entry] of this.map) {
yield [entry.uri, entry.value];
}
}

View File

@@ -6,11 +6,11 @@
"git": {
"name": "marked",
"repositoryUrl": "https://github.com/markedjs/marked",
"commitHash": "d1b7d521c41bcf915f81f0218b0e5acd607c1b72"
"commitHash": "2002557d004139ca2208c910d9ca999829b65406"
}
},
"license": "MIT",
"version": "3.0.2"
"version": "4.0.16"
}
],
"version": 1

View File

@@ -23,6 +23,7 @@
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.marked = {}));
})(this, (function (exports) { 'use strict';
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
@@ -141,6 +142,10 @@
return html;
}
var unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;
/**
* @param {string} html
*/
function unescape(html) {
// explicitly match decimal, hex, and named HTML entities
return html.replace(unescapeTest, function (_, n) {
@@ -155,8 +160,13 @@
});
}
var caret = /(^|[^\[])\^/g;
/**
* @param {string | RegExp} regex
* @param {string} opt
*/
function edit(regex, opt) {
regex = regex.source || regex;
regex = typeof regex === 'string' ? regex : regex.source;
opt = opt || '';
var obj = {
replace: function replace(name, val) {
@@ -173,6 +183,12 @@
}
var nonWordAndColonTest = /[^\w:]/g;
var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
/**
* @param {boolean} sanitize
* @param {string} base
* @param {string} href
*/
function cleanUrl(sanitize, base, href) {
if (sanitize) {
var prot;
@@ -204,6 +220,11 @@
var justDomain = /^[^:]+:\/*[^/]*$/;
var protocol = /^([^:]+:)[\s\S]*$/;
var domain = /^([^:]+:\/*[^/]*)[\s\S]*$/;
/**
* @param {string} base
* @param {string} href
*/
function resolveUrl(base, href) {
if (!baseUrls[' ' + base]) {
// we can ignore everything in base after the last slash of its path component,
@@ -282,7 +303,7 @@
cells.shift();
}
if (!cells[cells.length - 1].trim()) {
if (cells.length > 0 && !cells[cells.length - 1].trim()) {
cells.pop();
}
@@ -300,9 +321,15 @@
}
return cells;
} // Remove trailing 'c's. Equivalent to str.replace(/c*$/, '').
// /c*$/ is vulnerable to REDOS.
// invert: Remove suffix of non-c chars instead. Default falsey.
}
/**
* Remove trailing 'c's. Equivalent to str.replace(/c*$/, '').
* /c*$/ is vulnerable to REDOS.
*
* @param {string} str
* @param {string} c
* @param {boolean} invert Remove suffix of non-c chars instead. Default falsey.
*/
function rtrim(str, c, invert) {
var l = str.length;
@@ -326,7 +353,7 @@
}
}
return str.substr(0, l - suffLen);
return str.slice(0, l - suffLen);
}
function findClosingBracket(str, b) {
if (str.indexOf(b[1]) === -1) {
@@ -359,6 +386,11 @@
}
} // copied from https://stackoverflow.com/a/5450113/806777
/**
* @param {string} pattern
* @param {number} count
*/
function repeatString(pattern, count) {
if (count < 1) {
return '';
@@ -395,15 +427,15 @@
};
lexer.state.inLink = false;
return token;
} else {
return {
type: 'image',
raw: raw,
href: href,
title: title,
text: escape(text)
};
}
return {
type: 'image',
raw: raw,
href: href,
title: title,
text: escape(text)
};
}
function indentCodeCompensation(raw, text) {
@@ -446,11 +478,11 @@
var cap = this.rules.block.newline.exec(src);
if (cap && cap[0].length > 0) {
return {
type: 'space',
raw: cap[0]
};
}
return {
type: 'space',
raw: cap[0]
};
}
};
_proto.code = function code(src) {
@@ -526,7 +558,7 @@
var cap = this.rules.block.blockquote.exec(src);
if (cap) {
var text = cap[0].replace(/^ *> ?/gm, '');
var text = cap[0].replace(/^ *>[ \t]?/gm, '');
return {
type: 'blockquote',
raw: cap[0],
@@ -558,7 +590,7 @@
} // Get next list item
var itemRegex = new RegExp("^( {0,3}" + bull + ")((?: [^\\n]*)?(?:\\n|$))"); // Check if current bullet point can start a new List Item
var itemRegex = new RegExp("^( {0,3}" + bull + ")((?:[\t ][^\\n]*)?(?:\\n|$))"); // Check if current bullet point can start a new List Item
while (src) {
endEarly = false;
@@ -599,31 +631,37 @@
}
if (!endEarly) {
var nextBulletRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}(?:[*+-]|\\d{1,9}[.)])"); // Check if following lines should be included in List Item
var nextBulletRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}(?:[*+-]|\\d{1,9}[.)])((?: [^\\n]*)?(?:\\n|$))");
var hrRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)"); // Check if following lines should be included in List Item
while (src) {
rawLine = src.split('\n', 1)[0];
line = rawLine; // Re-align to follow commonmark nesting rules
if (this.options.pedantic) {
line = line.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' ');
} // End list item if found start of new bullet
if (this.options.pedantic) {
line = line.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' ');
} // End list item if found start of new bullet
if (nextBulletRegex.test(line)) {
break;
if (nextBulletRegex.test(line)) {
break;
} // Horizontal rule found
if (hrRegex.test(src)) {
break;
}
if (line.search(/[^ ]/) >= indent || !line.trim()) {
if (line.search(/[^ ]/) >= indent || !line.trim()) {
// Dedent if possible
itemContents += '\n' + line.slice(indent);
itemContents += '\n' + line.slice(indent);
} else if (!blankLine) {
// Until blank line, item doesn't need indentation
itemContents += '\n' + line;
} else {
} else {
// Otherwise, improper indentation ends this item
break;
}
break;
}
if (!blankLine && !line.trim()) {
// Check if current line is blank
@@ -757,7 +795,7 @@
};
}),
align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
rows: cap[3] ? cap[3].replace(/\n[ \t]*$/, '').split('\n') : []
rows: cap[3] && cap[3].trim() ? cap[3].replace(/\n[ \t]*$/, '').split('\n') : []
};
if (item.header.length === item.align.length) {
@@ -793,7 +831,7 @@
for (j = 0; j < l; j++) {
item.header[j].tokens = [];
this.lexer.inlineTokens(item.header[j].text, item.header[j].tokens);
this.lexer.inline(item.header[j].text, item.header[j].tokens);
} // cell child tokens
@@ -804,7 +842,7 @@
for (k = 0; k < row.length; k++) {
row[k].tokens = [];
this.lexer.inlineTokens(row[k].text, row[k].tokens);
this.lexer.inline(row[k].text, row[k].tokens);
}
}
@@ -1195,10 +1233,10 @@
newline: /^(?: *(?:\n|$))+/,
code: /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,
fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?=\n|$)|$)/,
hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,
hr: /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,
heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,
blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,
list: /^( {0,3}bull)( [^\n]+?)?(?:\n|$)/,
list: /^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/,
html: '^ {0,3}(?:' // optional indentation
+ '<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)' // (1)
+ '|comment[^\\n]*(\\n+|$)' // (2)
@@ -1288,9 +1326,9 @@
emStrong: {
lDelim: /^(?:\*+(?:([punct_])|[^\s*]))|^_+(?:([punct*])|([^\s_]))/,
// (1) and (2) can only be a Right Delimiter. (3) and (4) can only be Left. (5) and (6) can be either Left or Right.
// () Skip orphan delim inside strong (1) #*** (2) a***#, a*** (3) #***a, ***a (4) ***# (5) #***# (6) a***a
rDelimAst: /^[^_*]*?\_\_[^_*]*?\*[^_*]*?(?=\_\_)|[punct_](\*+)(?=[\s]|$)|[^punct*_\s](\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|[^punct*_\s](\*+)(?=[^punct*_\s])/,
rDelimUnd: /^[^_*]*?\*\*[^_*]*?\_[^_*]*?(?=\*\*)|[punct*](\_+)(?=[\s]|$)|[^punct*_\s](\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/ // ^- Not allowed for _
// () Skip orphan inside strong () Consume to delim (1) #*** (2) a***#, a*** (3) #***a, ***a (4) ***# (5) #***# (6) a***a
rDelimAst: /^[^_*]*?\_\_[^_*]*?\*[^_*]*?(?=\_\_)|[^*]+(?=[^*])|[punct_](\*+)(?=[\s]|$)|[^punct*_\s](\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|[^punct*_\s](\*+)(?=[^punct*_\s])/,
rDelimUnd: /^[^_*]*?\*\*[^_*]*?\_[^_*]*?(?=\*\*)|[^_]+(?=[^_])|[punct*](\_+)(?=[\s]|$)|[^punct*_\s](\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/ // ^- Not allowed for _
},
code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,
@@ -1372,6 +1410,7 @@
/**
* smartypants text replacement
* @param {string} text
*/
function smartypants(text) {
@@ -1386,6 +1425,7 @@
}
/**
* mangle email addresses
* @param {string} text
*/
@@ -1476,7 +1516,7 @@
var _proto = Lexer.prototype;
_proto.lex = function lex(src) {
src = src.replace(/\r\n|\r/g, '\n').replace(/\t/g, ' ');
src = src.replace(/\r\n|\r/g, '\n');
this.blockTokens(src, this.tokens);
var next;
@@ -1499,7 +1539,11 @@
}
if (this.options.pedantic) {
src = src.replace(/^ +$/gm, '');
src = src.replace(/\t/g, ' ').replace(/^ +$/gm, '');
} else {
src = src.replace(/^( *)(\t+)/gm, function (_, leading, tabs) {
return leading + ' '.repeat(tabs.length);
});
}
var token, lastToken, cutSrc, lastParagraphClipped;
@@ -1959,23 +2003,35 @@
}
return '<pre><code class="' + this.options.langPrefix + escape(lang, true) + '">' + (escaped ? _code : escape(_code, true)) + '</code></pre>\n';
};
}
/**
* @param {string} quote
*/
;
_proto.blockquote = function blockquote(quote) {
return '<blockquote>\n' + quote + '</blockquote>\n';
return "<blockquote>\n" + quote + "</blockquote>\n";
};
_proto.html = function html(_html) {
return _html;
};
}
/**
* @param {string} text
* @param {string} level
* @param {string} raw
* @param {any} slugger
*/
;
_proto.heading = function heading(text, level, raw, slugger) {
if (this.options.headerIds) {
return '<h' + level + ' id="' + this.options.headerPrefix + slugger.slug(raw) + '">' + text + '</h' + level + '>\n';
var id = this.options.headerPrefix + slugger.slug(raw);
return "<h" + level + " id=\"" + id + "\">" + text + "</h" + level + ">\n";
} // ignore IDs
return '<h' + level + '>' + text + '</h' + level + '>\n';
return "<h" + level + ">" + text + "</h" + level + ">\n";
};
_proto.hr = function hr() {
@@ -1986,55 +2042,94 @@
var type = ordered ? 'ol' : 'ul',
startatt = ordered && start !== 1 ? ' start="' + start + '"' : '';
return '<' + type + startatt + '>\n' + body + '</' + type + '>\n';
};
}
/**
* @param {string} text
*/
;
_proto.listitem = function listitem(text) {
return '<li>' + text + '</li>\n';
return "<li>" + text + "</li>\n";
};
_proto.checkbox = function checkbox(checked) {
return '<input ' + (checked ? 'checked="" ' : '') + 'disabled="" type="checkbox"' + (this.options.xhtml ? ' /' : '') + '> ';
};
}
/**
* @param {string} text
*/
;
_proto.paragraph = function paragraph(text) {
return '<p>' + text + '</p>\n';
};
return "<p>" + text + "</p>\n";
}
/**
* @param {string} header
* @param {string} body
*/
;
_proto.table = function table(header, body) {
if (body) body = '<tbody>' + body + '</tbody>';
if (body) body = "<tbody>" + body + "</tbody>";
return '<table>\n' + '<thead>\n' + header + '</thead>\n' + body + '</table>\n';
};
}
/**
* @param {string} content
*/
;
_proto.tablerow = function tablerow(content) {
return '<tr>\n' + content + '</tr>\n';
return "<tr>\n" + content + "</tr>\n";
};
_proto.tablecell = function tablecell(content, flags) {
var type = flags.header ? 'th' : 'td';
var tag = flags.align ? '<' + type + ' align="' + flags.align + '">' : '<' + type + '>';
return tag + content + '</' + type + '>\n';
} // span level renderer
var tag = flags.align ? "<" + type + " align=\"" + flags.align + "\">" : "<" + type + ">";
return tag + content + ("</" + type + ">\n");
}
/**
* span level renderer
* @param {string} text
*/
;
_proto.strong = function strong(text) {
return '<strong>' + text + '</strong>';
};
return "<strong>" + text + "</strong>";
}
/**
* @param {string} text
*/
;
_proto.em = function em(text) {
return '<em>' + text + '</em>';
};
return "<em>" + text + "</em>";
}
/**
* @param {string} text
*/
;
_proto.codespan = function codespan(text) {
return '<code>' + text + '</code>';
return "<code>" + text + "</code>";
};
_proto.br = function br() {
return this.options.xhtml ? '<br/>' : '<br>';
};
}
/**
* @param {string} text
*/
;
_proto.del = function del(text) {
return '<del>' + text + '</del>';
};
return "<del>" + text + "</del>";
}
/**
* @param {string} href
* @param {string} title
* @param {string} text
*/
;
_proto.link = function link(href, title, text) {
href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
@@ -2051,7 +2146,13 @@
out += '>' + text + '</a>';
return out;
};
}
/**
* @param {string} href
* @param {string} title
* @param {string} text
*/
;
_proto.image = function image(href, title, text) {
href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
@@ -2060,10 +2161,10 @@
return text;
}
var out = '<img src="' + href + '" alt="' + text + '"';
var out = "<img src=\"" + href + "\" alt=\"" + text + "\"";
if (title) {
out += ' title="' + title + '"';
out += " title=\"" + title + "\"";
}
out += this.options.xhtml ? '/>' : '>';
@@ -2133,6 +2234,10 @@
function Slugger() {
this.seen = {};
}
/**
* @param {string} value
*/
var _proto = Slugger.prototype;
@@ -2143,6 +2248,8 @@
}
/**
* Finds the next safe (unique) slug to use
* @param {string} originalSlug
* @param {boolean} isDryRun
*/
;
@@ -2168,8 +2275,9 @@
}
/**
* Convert string to unique id
* @param {object} options
* @param {boolean} options.dryrun Generates the next unique slug without updating the internal accumulator.
* @param {object} [options]
* @param {boolean} [options.dryrun] Generates the next unique slug without
* updating the internal accumulator.
*/
;
@@ -2866,6 +2974,7 @@
};
/**
* Parse Inline
* @param {string} src
*/
@@ -2941,6 +3050,7 @@
exports.walkTokens = walkTokens;
Object.defineProperty(exports, '__esModule', { value: true });
}));
// ESM-uncomment-begin

View File

@@ -5,14 +5,14 @@
import { extname } from 'vs/base/common/path';
export namespace Mimes {
export const text = 'text/plain';
export const binary = 'application/octet-stream';
export const unknown = 'application/unknown';
export const markdown = 'text/markdown';
export const latex = 'text/latex';
export const uriList = 'text/uri-list';
}
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;

View File

@@ -103,6 +103,11 @@ export namespace Schemas {
* 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';
}
export const connectionTokenCookieName = 'vscode-tkn';
@@ -116,6 +121,7 @@ class RemoteAuthoritiesImpl {
private readonly _connectionTokens: { [authority: string]: string | undefined } = Object.create(null);
private _preferredWebSchema: 'http' | 'https' = 'http';
private _delegate: ((uri: URI) => URI) | null = null;
private _remoteResourcesPath: string = `/${Schemas.vscodeRemoteResource}`;
setPreferredWebSchema(schema: 'http' | 'https') {
this._preferredWebSchema = schema;
@@ -125,6 +131,10 @@ class RemoteAuthoritiesImpl {
this._delegate = delegate;
}
setServerRootPath(serverRootPath: string): void {
this._remoteResourcesPath = `${serverRootPath}/${Schemas.vscodeRemoteResource}`;
}
set(authority: string, host: string, port: number): void {
this._hosts[authority] = host;
this._ports[authority] = port;
@@ -156,7 +166,7 @@ class RemoteAuthoritiesImpl {
return URI.from({
scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource,
authority: platform.isWeb && port === this._defaultWebPort ? `${host}` : `${host}:${port}`, // {{SQL CARBON EDIT}} addresses same-origin-policy violation in web mode when port number is in authority, but not in URI.
path: `/vscode-remote-resource`,
path: this._remoteResourcesPath,
query
});
}

View File

@@ -75,7 +75,7 @@ function _cloneAndChange(obj: any, changer: (orig: any) => any, seen: Set<any>):
}
seen.add(obj);
const r2 = {};
for (let i2 in obj) {
for (const i2 in obj) {
if (_hasOwnProperty.call(obj, i2)) {
(r2 as any)[i2] = _cloneAndChange(obj[i2], changer, seen);
}
@@ -186,6 +186,7 @@ export function safeStringify(obj: any): string {
});
}
// {{SQL CARBON EDIT}} - define getOrDefault
export function getOrDefault<T, R>(obj: T, fn: (obj: T) => R | undefined, defaultValue: R): R {
const result = fn(obj);
return typeof result === 'undefined' ? defaultValue : result;

View File

@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export {
IObservable,
IObserver,
IReader,
ISettable,
ISettableObservable,
ITransaction,
observableValue,
transaction,
} from 'vs/base/common/observableImpl/base';
export { derived } from 'vs/base/common/observableImpl/derived';
export {
autorun,
autorunDelta,
autorunHandleChanges,
autorunWithStore,
} from 'vs/base/common/observableImpl/autorun';
export * from 'vs/base/common/observableImpl/utils';
import { ConsoleObservableLogger, setLogger } from 'vs/base/common/observableImpl/logging';
const enableLogging = false;
if (enableLogging) {
setLogger(new ConsoleObservableLogger());
}

View File

@@ -0,0 +1,167 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IReader, IObservable, IObserver } from 'vs/base/common/observableImpl/base';
import { getLogger } from 'vs/base/common/observableImpl/logging';
export function autorun(debugName: string, fn: (reader: IReader) => void): IDisposable {
return new AutorunObserver(debugName, fn, undefined);
}
interface IChangeContext {
readonly changedObservable: IObservable<any, any>;
readonly change: unknown;
didChange<T, TChange>(observable: IObservable<T, TChange>): this is { change: TChange };
}
export function autorunHandleChanges(
debugName: string,
options: {
/**
* Returns if this change should cause a re-run of the autorun.
*/
handleChange: (context: IChangeContext) => boolean;
},
fn: (reader: IReader) => void
): IDisposable {
return new AutorunObserver(debugName, fn, options.handleChange);
}
export function autorunWithStore(
fn: (reader: IReader, store: DisposableStore) => void,
debugName: string
): IDisposable {
const store = new DisposableStore();
const disposable = autorun(
debugName,
reader => {
store.clear();
fn(reader, store);
}
);
return toDisposable(() => {
disposable.dispose();
store.dispose();
});
}
export class AutorunObserver implements IObserver, IReader, IDisposable {
public needsToRun = true;
private updateCount = 0;
private disposed = false;
/**
* The actual dependencies.
*/
private _dependencies = new Set<IObservable<any>>();
public get dependencies() {
return this._dependencies;
}
/**
* Dependencies that have to be removed when {@link runFn} ran through.
*/
private staleDependencies = new Set<IObservable<any>>();
constructor(
public readonly debugName: string,
private readonly runFn: (reader: IReader) => void,
private readonly _handleChange: ((context: IChangeContext) => boolean) | undefined
) {
getLogger()?.handleAutorunCreated(this);
this.runIfNeeded();
}
public subscribeTo<T>(observable: IObservable<T>) {
// In case the run action disposes the autorun
if (this.disposed) {
return;
}
this._dependencies.add(observable);
if (!this.staleDependencies.delete(observable)) {
observable.addObserver(this);
}
}
public handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void {
const shouldReact = this._handleChange ? this._handleChange({
changedObservable: observable,
change,
didChange: o => o === observable as any,
}) : true;
this.needsToRun = this.needsToRun || shouldReact;
if (this.updateCount === 0) {
this.runIfNeeded();
}
}
public beginUpdate(): void {
this.updateCount++;
}
public endUpdate(): void {
this.updateCount--;
if (this.updateCount === 0) {
this.runIfNeeded();
}
}
private runIfNeeded(): void {
if (!this.needsToRun) {
return;
}
// Assert: this.staleDependencies is an empty set.
const emptySet = this.staleDependencies;
this.staleDependencies = this._dependencies;
this._dependencies = emptySet;
this.needsToRun = false;
getLogger()?.handleAutorunTriggered(this);
try {
this.runFn(this);
} finally {
// We don't want our observed observables to think that they are (not even temporarily) not being observed.
// Thus, we only unsubscribe from observables that are definitely not read anymore.
for (const o of this.staleDependencies) {
o.removeObserver(this);
}
this.staleDependencies.clear();
}
}
public dispose(): void {
this.disposed = true;
for (const o of this._dependencies) {
o.removeObserver(this);
}
this._dependencies.clear();
}
public toString(): string {
return `Autorun<${this.debugName}>`;
}
}
export namespace autorun {
export const Observer = AutorunObserver;
}
export function autorunDelta<T>(
name: string,
observable: IObservable<T>,
handler: (args: { lastValue: T | undefined; newValue: T }) => void
): IDisposable {
let _lastValue: T | undefined;
return autorun(name, (reader) => {
const newValue = observable.read(reader);
const lastValue = _lastValue;
_lastValue = newValue;
handler({ lastValue, newValue });
});
}

View File

@@ -0,0 +1,244 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type { derived } from 'vs/base/common/observableImpl/derived';
import { getLogger } from 'vs/base/common/observableImpl/logging';
export interface IObservable<T, TChange = void> {
readonly TChange: TChange;
/**
* Reads the current value.
*
* Must not be called from {@link IObserver.handleChange}.
*/
get(): T;
/**
* Adds an observer.
*/
addObserver(observer: IObserver): void;
removeObserver(observer: IObserver): void;
/**
* Subscribes the reader to this observable and returns the current value of this observable.
*/
read(reader: IReader): T;
map<TNew>(fn: (value: T) => TNew): IObservable<TNew>;
readonly debugName: string;
}
export interface IReader {
/**
* Reports an observable that was read.
*
* Is called by {@link IObservable.read}.
*/
subscribeTo<T>(observable: IObservable<T, any>): void;
}
export interface IObserver {
/**
* Indicates that an update operation is about to begin.
*
* During an update, invariants might not hold for subscribed observables and
* change events might be delayed.
* However, all changes must be reported before all update operations are over.
*/
beginUpdate<T>(observable: IObservable<T>): void;
/**
* Is called by a subscribed observable immediately after it notices a change.
*
* When {@link IObservable.get} returns and no change has been reported,
* there has been no change for that observable.
*
* Implementations must not call into other observables!
* The change should be processed when {@link IObserver.endUpdate} is called.
*/
handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void;
/**
* Indicates that an update operation has completed.
*/
endUpdate<T>(observable: IObservable<T>): void;
}
export interface ISettable<T, TChange = void> {
set(value: T, transaction: ITransaction | undefined, change: TChange): void;
}
export interface ITransaction {
/**
* Calls `Observer.beginUpdate` immediately
* and `Observer.endUpdate` when the transaction is complete.
*/
updateObserver(
observer: IObserver,
observable: IObservable<any, any>
): void;
}
let _derived: typeof derived;
/**
* @internal
* This is to allow splitting files.
*/
export function _setDerived(derived: typeof _derived) {
_derived = derived;
}
export abstract class ConvenientObservable<T, TChange> implements IObservable<T, TChange> {
get TChange(): TChange { return null!; }
public abstract get(): T;
public abstract addObserver(observer: IObserver): void;
public abstract removeObserver(observer: IObserver): void;
/** @sealed */
public read(reader: IReader): T {
reader.subscribeTo(this);
return this.get();
}
/** @sealed */
public map<TNew>(fn: (value: T) => TNew): IObservable<TNew> {
return _derived(
() => {
const name = getFunctionName(fn);
return name !== undefined ? name : `${this.debugName} (mapped)`;
},
(reader) => fn(this.read(reader))
);
}
public abstract get debugName(): string;
}
export abstract class BaseObservable<T, TChange = void> extends ConvenientObservable<T, TChange> {
protected readonly observers = new Set<IObserver>();
/** @sealed */
public addObserver(observer: IObserver): void {
const len = this.observers.size;
this.observers.add(observer);
if (len === 0) {
this.onFirstObserverAdded();
}
}
/** @sealed */
public removeObserver(observer: IObserver): void {
const deleted = this.observers.delete(observer);
if (deleted && this.observers.size === 0) {
this.onLastObserverRemoved();
}
}
protected onFirstObserverAdded(): void { }
protected onLastObserverRemoved(): void { }
}
export function transaction(fn: (tx: ITransaction) => void, getDebugName?: () => string): void {
const tx = new TransactionImpl(fn, getDebugName);
try {
getLogger()?.handleBeginTransaction(tx);
fn(tx);
} finally {
tx.finish();
getLogger()?.handleEndTransaction();
}
}
export function getFunctionName(fn: Function): string | undefined {
const fnSrc = fn.toString();
// Pattern: /** @description ... */
const regexp = /\/\*\*\s*@description\s*([^*]*)\*\//;
const match = regexp.exec(fnSrc);
const result = match ? match[1] : undefined;
return result?.trim();
}
export class TransactionImpl implements ITransaction {
private updatingObservers: { observer: IObserver; observable: IObservable<any> }[] | null = [];
constructor(private readonly fn: Function, private readonly _getDebugName?: () => string) { }
public getDebugName(): string | undefined {
if (this._getDebugName) {
return this._getDebugName();
}
return getFunctionName(this.fn);
}
public updateObserver(
observer: IObserver,
observable: IObservable<any>
): void {
this.updatingObservers!.push({ observer, observable });
observer.beginUpdate(observable);
}
public finish(): void {
const updatingObservers = this.updatingObservers!;
// Prevent anyone from updating observers from now on.
this.updatingObservers = null;
for (const { observer, observable } of updatingObservers) {
observer.endUpdate(observable);
}
}
}
export interface ISettableObservable<T, TChange = void> extends IObservable<T, TChange>, ISettable<T, TChange> {
}
export function observableValue<T, TChange = void>(name: string, initialValue: T): ISettableObservable<T, TChange> {
return new ObservableValue(name, initialValue);
}
export class ObservableValue<T, TChange = void>
extends BaseObservable<T, TChange>
implements ISettableObservable<T, TChange>
{
private value: T;
constructor(public readonly debugName: string, initialValue: T) {
super();
this.value = initialValue;
}
public get(): T {
return this.value;
}
public set(value: T, tx: ITransaction | undefined, change: TChange): void {
if (this.value === value) {
return;
}
if (!tx) {
transaction((tx) => {
this.set(value, tx, change);
}, () => `Setting ${this.debugName}`);
return;
}
const oldValue = this.value;
this.value = value;
getLogger()?.handleObservableChanged(this, { oldValue, newValue: value, change, didChange: true });
for (const observer of this.observers) {
tx.updateObserver(observer, this);
observer.handleChange(this, change);
}
}
override toString(): string {
return `${this.debugName}: ${this.value}`;
}
}

View File

@@ -0,0 +1,167 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IReader, IObservable, BaseObservable, IObserver, _setDerived } from 'vs/base/common/observableImpl/base';
import { getLogger } from 'vs/base/common/observableImpl/logging';
export function derived<T>(debugName: string | (() => string), computeFn: (reader: IReader) => T): IObservable<T> {
return new Derived(debugName, computeFn);
}
_setDerived(derived);
export class Derived<T> extends BaseObservable<T, void> implements IReader, IObserver {
private hadValue = false;
private hasValue = false;
private value: T | undefined = undefined;
private updateCount = 0;
private _dependencies = new Set<IObservable<any>>();
public get dependencies(): ReadonlySet<IObservable<any>> {
return this._dependencies;
}
/**
* Dependencies that have to be removed when {@link runFn} ran through.
*/
private staleDependencies = new Set<IObservable<any>>();
public override get debugName(): string {
return typeof this._debugName === 'function' ? this._debugName() : this._debugName;
}
constructor(
private readonly _debugName: string | (() => string),
private readonly computeFn: (reader: IReader) => T
) {
super();
getLogger()?.handleDerivedCreated(this);
}
protected override onLastObserverRemoved(): void {
/**
* We are not tracking changes anymore, thus we have to assume
* that our cache is invalid.
*/
this.hasValue = false;
this.hadValue = false;
this.value = undefined;
for (const d of this._dependencies) {
d.removeObserver(this);
}
this._dependencies.clear();
}
public get(): T {
if (this.observers.size === 0) {
// Cache is not valid and don't refresh the cache.
// Observables should not be read in non-reactive contexts.
const result = this.computeFn(this);
// Clear new dependencies
this.onLastObserverRemoved();
return result;
}
if (this.updateCount > 0 && this.hasValue) {
// Refresh dependencies
for (const d of this._dependencies) {
// Maybe `.get()` triggers `handleChange`?
d.get();
if (!this.hasValue) {
// The other dependencies will refresh on demand
break;
}
}
}
if (!this.hasValue) {
const emptySet = this.staleDependencies;
this.staleDependencies = this._dependencies;
this._dependencies = emptySet;
const oldValue = this.value;
try {
this.value = this.computeFn(this);
} finally {
// We don't want our observed observables to think that they are (not even temporarily) not being observed.
// Thus, we only unsubscribe from observables that are definitely not read anymore.
for (const o of this.staleDependencies) {
o.removeObserver(this);
}
this.staleDependencies.clear();
}
this.hasValue = true;
const didChange = this.hadValue && oldValue !== this.value;
getLogger()?.handleDerivedRecomputed(this, {
oldValue,
newValue: this.value,
change: undefined,
didChange
});
if (didChange) {
for (const r of this.observers) {
r.handleChange(this, undefined);
}
}
}
return this.value!;
}
// IObserver Implementation
public beginUpdate(): void {
if (this.updateCount === 0) {
for (const r of this.observers) {
r.beginUpdate(this);
}
}
this.updateCount++;
}
public handleChange<T, TChange>(
_observable: IObservable<T, TChange>,
_change: TChange
): void {
if (this.hasValue) {
this.hadValue = true;
this.hasValue = false;
}
// Not in transaction: Recompute & inform observers immediately
if (this.updateCount === 0 && this.observers.size > 0) {
this.get();
}
// Otherwise, recompute in `endUpdate` or on demand.
}
public endUpdate(): void {
this.updateCount--;
if (this.updateCount === 0) {
if (this.observers.size > 0) {
// Propagate invalidation
this.get();
}
for (const r of this.observers) {
r.endUpdate(this);
}
}
}
// IReader Implementation
public subscribeTo<T>(observable: IObservable<T>) {
this._dependencies.add(observable);
// We are already added as observer for stale dependencies.
if (!this.staleDependencies.delete(observable)) {
observable.addObserver(this);
}
}
override toString(): string {
return `LazyDerived<${this.debugName}>`;
}
}

View File

@@ -0,0 +1,312 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AutorunObserver } from 'vs/base/common/observableImpl/autorun';
import { IObservable, ObservableValue, TransactionImpl } from 'vs/base/common/observableImpl/base';
import { Derived } from 'vs/base/common/observableImpl/derived';
import { FromEventObservable } from 'vs/base/common/observableImpl/utils';
let globalObservableLogger: IObservableLogger | undefined;
export function setLogger(logger: IObservableLogger): void {
globalObservableLogger = logger;
}
export function getLogger(): IObservableLogger | undefined {
return globalObservableLogger;
}
interface IChangeInformation {
oldValue: unknown;
newValue: unknown;
change: unknown;
didChange: boolean;
}
export interface IObservableLogger {
handleObservableChanged(observable: ObservableValue<unknown, unknown>, info: IChangeInformation): void;
handleFromEventObservableTriggered(observable: FromEventObservable<any, any>, info: IChangeInformation): void;
handleAutorunCreated(autorun: AutorunObserver): void;
handleAutorunTriggered(autorun: AutorunObserver): void;
handleDerivedCreated(observable: Derived<unknown>): void;
handleDerivedRecomputed(observable: Derived<unknown>, info: IChangeInformation): void;
handleBeginTransaction(transaction: TransactionImpl): void;
handleEndTransaction(): void;
}
export class ConsoleObservableLogger implements IObservableLogger {
private indentation = 0;
private textToConsoleArgs(text: ConsoleText): unknown[] {
return consoleTextToArgs([
normalText(repeat('| ', this.indentation)),
text,
]);
}
private formatInfo(info: IChangeInformation): ConsoleText[] {
return info.didChange
? [
normalText(` `),
styled(formatValue(info.oldValue, 70), {
color: 'red',
strikeThrough: true,
}),
normalText(` `),
styled(formatValue(info.newValue, 60), {
color: 'green',
}),
]
: [normalText(` (unchanged)`)];
}
handleObservableChanged(observable: IObservable<unknown, unknown>, info: IChangeInformation): void {
console.log(...this.textToConsoleArgs([
formatKind('observable value changed'),
styled(observable.debugName, { color: 'BlueViolet' }),
...this.formatInfo(info),
]));
}
private readonly changedObservablesSets = new WeakMap<object, Set<IObservable<any, any>>>();
formatChanges(changes: Set<IObservable<any, any>>): ConsoleText | undefined {
if (changes.size === 0) {
return undefined;
}
return styled(
' (changed deps: ' +
[...changes].map((o) => o.debugName).join(', ') +
')',
{ color: 'gray' }
);
}
handleDerivedCreated(derived: Derived<unknown>): void {
const existingHandleChange = derived.handleChange;
this.changedObservablesSets.set(derived, new Set());
derived.handleChange = (observable, change) => {
this.changedObservablesSets.get(derived)!.add(observable);
return existingHandleChange.apply(derived, [observable, change]);
};
}
handleDerivedRecomputed(derived: Derived<unknown>, info: IChangeInformation): void {
const changedObservables = this.changedObservablesSets.get(derived)!;
console.log(...this.textToConsoleArgs([
formatKind('derived recomputed'),
styled(derived.debugName, { color: 'BlueViolet' }),
...this.formatInfo(info),
this.formatChanges(changedObservables)
]));
changedObservables.clear();
}
handleFromEventObservableTriggered(observable: FromEventObservable<any, any>, info: IChangeInformation): void {
console.log(...this.textToConsoleArgs([
formatKind('observable from event triggered'),
styled(observable.debugName, { color: 'BlueViolet' }),
...this.formatInfo(info),
]));
}
handleAutorunCreated(autorun: AutorunObserver): void {
const existingHandleChange = autorun.handleChange;
this.changedObservablesSets.set(autorun, new Set());
autorun.handleChange = (observable, change) => {
this.changedObservablesSets.get(autorun)!.add(observable);
return existingHandleChange.apply(autorun, [observable, change]);
};
}
handleAutorunTriggered(autorun: AutorunObserver): void {
const changedObservables = this.changedObservablesSets.get(autorun)!;
console.log(...this.textToConsoleArgs([
formatKind('autorun'),
styled(autorun.debugName, { color: 'BlueViolet' }),
this.formatChanges(changedObservables)
]));
changedObservables.clear();
}
handleBeginTransaction(transaction: TransactionImpl): void {
let transactionName = transaction.getDebugName();
if (transactionName === undefined) {
transactionName = '';
}
console.log(...this.textToConsoleArgs([
formatKind('transaction'),
styled(transactionName, { color: 'BlueViolet' }),
]));
this.indentation++;
}
handleEndTransaction(): void {
this.indentation--;
}
}
type ConsoleText =
| (ConsoleText | undefined)[]
| { text: string; style: string; data?: Record<string, unknown> }
| { data: Record<string, unknown> };
function consoleTextToArgs(text: ConsoleText): unknown[] {
const styles = new Array<any>();
const initial = {};
const data = initial;
let firstArg = '';
function process(t: ConsoleText): void {
if ('length' in t) {
for (const item of t) {
if (item) {
process(item);
}
}
} else if ('text' in t) {
firstArg += `%c${t.text}`;
styles.push(t.style);
if (t.data) {
Object.assign(data, t.data);
}
} else if ('data' in t) {
Object.assign(data, t.data);
}
}
process(text);
const result = [firstArg, ...styles];
if (Object.keys(data).length > 0) {
result.push(data);
}
return result;
}
function normalText(text: string): ConsoleText {
return styled(text, { color: 'black' });
}
function formatKind(kind: string): ConsoleText {
return styled(padStr(`${kind}: `, 10), { color: 'black', bold: true });
}
function styled(
text: string,
options: { color: string; strikeThrough?: boolean; bold?: boolean } = {
color: 'black',
}
): ConsoleText {
function objToCss(styleObj: Record<string, string>): string {
return Object.entries(styleObj).reduce(
(styleString, [propName, propValue]) => {
return `${styleString}${propName}:${propValue};`;
},
''
);
}
const style: Record<string, string> = {
color: options.color,
};
if (options.strikeThrough) {
style['text-decoration'] = 'line-through';
}
if (options.bold) {
style['font-weight'] = 'bold';
}
return {
text,
style: objToCss(style),
};
}
function formatValue(value: unknown, availableLen: number): string {
switch (typeof value) {
case 'number':
return '' + value;
case 'string':
if (value.length + 2 <= availableLen) {
return `"${value}"`;
}
return `"${value.substr(0, availableLen - 7)}"+...`;
case 'boolean':
return value ? 'true' : 'false';
case 'undefined':
return 'undefined';
case 'object':
if (value === null) {
return 'null';
}
if (Array.isArray(value)) {
return formatArray(value, availableLen);
}
return formatObject(value, availableLen);
case 'symbol':
return value.toString();
case 'function':
return `[[Function${value.name ? ' ' + value.name : ''}]]`;
default:
return '' + value;
}
}
function formatArray(value: unknown[], availableLen: number): string {
let result = '[ ';
let first = true;
for (const val of value) {
if (!first) {
result += ', ';
}
if (result.length - 5 > availableLen) {
result += '...';
break;
}
first = false;
result += `${formatValue(val, availableLen - result.length)}`;
}
result += ' ]';
return result;
}
function formatObject(value: object, availableLen: number): string {
let result = '{ ';
let first = true;
for (const [key, val] of Object.entries(value)) {
if (!first) {
result += ', ';
}
if (result.length - 5 > availableLen) {
result += '...';
break;
}
first = false;
result += `${key}: ${formatValue(val, availableLen - result.length)}`;
}
result += ' }';
return result;
}
function repeat(str: string, count: number): string {
let result = '';
for (let i = 1; i <= count; i++) {
result += str;
}
return result;
}
function padStr(str: string, length: number): string {
while (str.length < length) {
str += ' ';
}
return str;
}

View File

@@ -0,0 +1,281 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { autorun } from 'vs/base/common/observableImpl/autorun';
import { IObservable, BaseObservable, transaction, IReader, ITransaction, ConvenientObservable, IObserver, observableValue, getFunctionName } from 'vs/base/common/observableImpl/base';
import { derived } from 'vs/base/common/observableImpl/derived';
import { Event } from 'vs/base/common/event';
import { getLogger } from 'vs/base/common/observableImpl/logging';
export function constObservable<T>(value: T): IObservable<T> {
return new ConstObservable(value);
}
class ConstObservable<T> extends ConvenientObservable<T, void> {
constructor(private readonly value: T) {
super();
}
public override get debugName(): string {
return this.toString();
}
public get(): T {
return this.value;
}
public addObserver(observer: IObserver): void {
// NO OP
}
public removeObserver(observer: IObserver): void {
// NO OP
}
override toString(): string {
return `Const: ${this.value}`;
}
}
export function observableFromPromise<T>(promise: Promise<T>): IObservable<{ value?: T }> {
const observable = observableValue<{ value?: T }>('promiseValue', {});
promise.then((value) => {
observable.set({ value }, undefined);
});
return observable;
}
export function waitForState<T, TState extends T>(observable: IObservable<T>, predicate: (state: T) => state is TState): Promise<TState>;
export function waitForState<T>(observable: IObservable<T>, predicate: (state: T) => boolean): Promise<T>;
export function waitForState<T>(observable: IObservable<T>, predicate: (state: T) => boolean): Promise<T> {
return new Promise(resolve => {
const d = autorun('waitForState', reader => {
const currentState = observable.read(reader);
if (predicate(currentState)) {
d.dispose();
resolve(currentState);
}
});
});
}
export function observableFromEvent<T, TArgs = unknown>(
event: Event<TArgs>,
getValue: (args: TArgs | undefined) => T
): IObservable<T> {
return new FromEventObservable(event, getValue);
}
export class FromEventObservable<TArgs, T> extends BaseObservable<T> {
private value: T | undefined;
private hasValue = false;
private subscription: IDisposable | undefined;
constructor(
private readonly event: Event<TArgs>,
private readonly getValue: (args: TArgs | undefined) => T
) {
super();
}
private getDebugName(): string | undefined {
return getFunctionName(this.getValue);
}
public get debugName(): string {
const name = this.getDebugName();
return 'From Event' + (name ? `: ${name}` : '');
}
protected override onFirstObserverAdded(): void {
this.subscription = this.event(this.handleEvent);
}
private readonly handleEvent = (args: TArgs | undefined) => {
const newValue = this.getValue(args);
const didChange = this.value !== newValue;
getLogger()?.handleFromEventObservableTriggered(this, { oldValue: this.value, newValue, change: undefined, didChange });
if (didChange) {
this.value = newValue;
if (this.hasValue) {
transaction(
(tx) => {
for (const o of this.observers) {
tx.updateObserver(o, this);
o.handleChange(this, undefined);
}
},
() => {
const name = this.getDebugName();
return 'Event fired' + (name ? `: ${name}` : '');
}
);
}
this.hasValue = true;
}
};
protected override onLastObserverRemoved(): void {
this.subscription!.dispose();
this.subscription = undefined;
this.hasValue = false;
this.value = undefined;
}
public get(): T {
if (this.subscription) {
if (!this.hasValue) {
this.handleEvent(undefined);
}
return this.value!;
} else {
// no cache, as there are no subscribers to keep it updated
return this.getValue(undefined);
}
}
}
export namespace observableFromEvent {
export const Observer = FromEventObservable;
}
export function observableSignalFromEvent(
debugName: string,
event: Event<any>
): IObservable<void> {
return new FromEventObservableSignal(debugName, event);
}
class FromEventObservableSignal extends BaseObservable<void> {
private subscription: IDisposable | undefined;
constructor(
public readonly debugName: string,
private readonly event: Event<any>,
) {
super();
}
protected override onFirstObserverAdded(): void {
this.subscription = this.event(this.handleEvent);
}
private readonly handleEvent = () => {
transaction(
(tx) => {
for (const o of this.observers) {
tx.updateObserver(o, this);
o.handleChange(this, undefined);
}
},
() => this.debugName
);
};
protected override onLastObserverRemoved(): void {
this.subscription!.dispose();
this.subscription = undefined;
}
public override get(): void {
// NO OP
}
}
export function debouncedObservable<T>(observable: IObservable<T>, debounceMs: number, disposableStore: DisposableStore): IObservable<T | undefined> {
const debouncedObservable = observableValue<T | undefined>('debounced', undefined);
let timeout: any = undefined;
disposableStore.add(autorun('debounce', reader => {
const value = observable.read(reader);
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
transaction(tx => {
debouncedObservable.set(value, tx);
});
}, debounceMs);
}));
return debouncedObservable;
}
export function wasEventTriggeredRecently(event: Event<any>, timeoutMs: number, disposableStore: DisposableStore): IObservable<boolean> {
const observable = observableValue('triggeredRecently', false);
let timeout: any = undefined;
disposableStore.add(event(() => {
observable.set(true, undefined);
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
observable.set(false, undefined);
}, timeoutMs);
}));
return observable;
}
/**
* This ensures the observable is kept up-to-date.
* This is useful when the observables `get` method is used.
*/
export function keepAlive(observable: IObservable<any>): IDisposable {
const o = new KeepAliveObserver();
observable.addObserver(o);
return toDisposable(() => {
observable.removeObserver(o);
});
}
class KeepAliveObserver implements IObserver {
beginUpdate<T>(observable: IObservable<T, void>): void {
// NO OP
}
handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void {
// NO OP
}
endUpdate<T>(observable: IObservable<T, void>): void {
// NO OP
}
}
export function derivedObservableWithCache<T>(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable<T> {
let lastValue: T | undefined = undefined;
const observable = derived(name, reader => {
lastValue = computeFn(reader, lastValue);
return lastValue;
});
return observable;
}
export function derivedObservableWithWritableCache<T>(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable<T> & { clearCache(transaction: ITransaction): void } {
let lastValue: T | undefined = undefined;
const counter = observableValue('derivedObservableWithWritableCache.counter', 0);
const observable = derived(name, reader => {
counter.read(reader);
lastValue = computeFn(reader, lastValue);
return lastValue;
});
return Object.assign(observable, {
clearCache: (transaction: ITransaction) => {
lastValue = undefined;
counter.set(counter.get() + 1, transaction);
},
});
}

View File

@@ -5,17 +5,28 @@
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
//@ts-ignore
import type { IObservable } from 'vs/base/common/observable';
/**
* @deprecated Use {@link IObservable} instead.
*/
export interface IObservableValue<T> {
onDidChange: Event<T>;
readonly value: T;
}
/**
* @deprecated Use {@link IObservable} instead.
*/
export const staticObservableValue = <T>(value: T): IObservableValue<T> => ({
onDidChange: Event.None,
value,
});
/**
* @deprecated Use {@link IObservable} instead.
*/
export class MutableObservableValue<T> extends Disposable implements IObservableValue<T> {
private readonly changeEmitter = this._register(new Emitter<T>());

View File

@@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
const LANGUAGE_DEFAULT = 'en';
@@ -67,7 +68,6 @@ const isElectronRenderer = isElectronProcess && nodeProcess?.type === 'renderer'
interface INavigator {
userAgent: string;
language: string;
maxTouchPoints?: number;
}
declare const navigator: INavigator;
@@ -80,7 +80,17 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
_isIOS = (_userAgent.indexOf('Macintosh') >= 0 || _userAgent.indexOf('iPad') >= 0 || _userAgent.indexOf('iPhone') >= 0) && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0;
_isLinux = _userAgent.indexOf('Linux') >= 0;
_isWeb = true;
_locale = navigator.language;
const configuredLocale = nls.getConfiguredDefaultLocale(
// This call _must_ be done in the file that calls `nls.getConfiguredDefaultLocale`
// to ensure that the NLS AMD Loader plugin has been loaded and configured.
// This is because the loader plugin decides what the default locale is based on
// how it's able to resolve the strings.
nls.localize({ key: 'ensureLoaderPluginIsLoaded', comment: ['{Locked}'] }, '_')
);
_locale = configuredLocale || LANGUAGE_DEFAULT;
_language = _locale;
}
@@ -195,6 +205,8 @@ export const locale = _locale;
*/
export const translationsConfigFile = _translationsConfigFile;
export const setTimeout0IsFaster = (typeof globals.postMessage === 'function' && !globals.importScripts);
/**
* See https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#:~:text=than%204%2C%20then-,set%20timeout%20to%204,-.
*
@@ -202,12 +214,12 @@ export const translationsConfigFile = _translationsConfigFile;
* that browsers set when the nesting level is > 5.
*/
export const setTimeout0 = (() => {
if (typeof globals.postMessage === 'function' && !globals.importScripts) {
if (setTimeout0IsFaster) {
interface IQueueElement {
id: number;
callback: () => void;
}
let pending: IQueueElement[] = [];
const pending: IQueueElement[] = [];
globals.addEventListener('message', (e: MessageEvent) => {
if (e.data && e.data.vscodeScheduleAsyncWork) {
for (let i = 0, len = pending.length; i < len; i++) {

View File

@@ -71,9 +71,11 @@ export interface IProductConfiguration {
readonly extensionsGallery?: {
readonly serviceUrl: string;
readonly itemUrl: string;
readonly publisherUrl: string;
readonly resourceUrlTemplate: string;
readonly controlUrl: string;
readonly recommendationsUrl: string;
readonly nlsBaseUrl: string;
};
readonly extensionTips?: { [id: string]: string };
@@ -158,6 +160,8 @@ export interface IProductConfiguration {
readonly 'configurationSync.store'?: ConfigurationSyncStore;
readonly 'editSessions.store'?: Omit<ConfigurationSyncStore, 'insidersUrl' | 'stableUrl'>;
readonly darwinUniversalAssetId?: string;
}

View File

@@ -240,7 +240,8 @@ export class ExtUri implements IExtUri {
const relativePath = paths.relative(originalFSPath(from), originalFSPath(to));
return isWindows ? extpath.toSlashes(relativePath) : relativePath;
}
let fromPath = from.path || '/', toPath = to.path || '/';
let fromPath = from.path || '/';
const toPath = to.path || '/';
if (this._ignorePathCasing(from)) {
// make casing of fromPath match toPath
let i = 0;

View File

@@ -268,9 +268,7 @@ export class Scrollable extends Disposable {
this._setState(newState, Boolean(this._smoothScrolling));
// Validate outstanding animated scroll position target
if (this._smoothScrolling) {
this._smoothScrolling.acceptScrollDimensions(this._state);
}
this._smoothScrolling?.acceptScrollDimensions(this._state);
}
/**

Some files were not shown because too many files have changed in this diff Show More