mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-07 09:35:41 -05:00
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:
69
src/vs/base/browser/broadcast.ts
Normal file
69
src/vs/base/browser/broadcast.ts
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
108
src/vs/base/browser/deviceAccess.ts
Normal file
108
src/vs/base/browser/deviceAccess.ts
Normal 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
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>;
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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' : ''));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 */;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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>) => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,8 @@ export interface ITreeEvent<T> {
|
||||
export enum TreeMouseEventTarget {
|
||||
Unknown,
|
||||
Twistie,
|
||||
Element
|
||||
Element,
|
||||
Filter
|
||||
}
|
||||
|
||||
export interface ITreeMouseEvent<T> {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface IRemoteConsoleLog {
|
||||
arguments: string;
|
||||
}
|
||||
|
||||
interface IStackArgument {
|
||||
export interface IStackArgument {
|
||||
__$stack: string;
|
||||
}
|
||||
|
||||
|
||||
90
src/vs/base/common/dataTransfer.ts
Normal file
90
src/vs/base/common/dataTransfer.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -139,6 +139,10 @@ export function escapeMarkdownSyntaxTokens(text: string): string {
|
||||
return text.replace(/[\\`*_{}[\]()#+\-!]/g, '\\$&');
|
||||
}
|
||||
|
||||
export function escapeDoubleQuotes(input: string) {
|
||||
return input.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
export function removeMarkdownEscapes(text: string): string {
|
||||
if (!text) {
|
||||
return text;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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}`;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
30
src/vs/base/common/observable.ts
Normal file
30
src/vs/base/common/observable.ts
Normal 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());
|
||||
}
|
||||
167
src/vs/base/common/observableImpl/autorun.ts
Normal file
167
src/vs/base/common/observableImpl/autorun.ts
Normal 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 });
|
||||
});
|
||||
}
|
||||
244
src/vs/base/common/observableImpl/base.ts
Normal file
244
src/vs/base/common/observableImpl/base.ts
Normal 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}`;
|
||||
}
|
||||
}
|
||||
|
||||
167
src/vs/base/common/observableImpl/derived.ts
Normal file
167
src/vs/base/common/observableImpl/derived.ts
Normal 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}>`;
|
||||
}
|
||||
}
|
||||
312
src/vs/base/common/observableImpl/logging.ts
Normal file
312
src/vs/base/common/observableImpl/logging.ts
Normal 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;
|
||||
}
|
||||
281
src/vs/base/common/observableImpl/utils.ts
Normal file
281
src/vs/base/common/observableImpl/utils.ts
Normal 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);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -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>());
|
||||
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user