Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c (#8525)
* Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c * remove files we don't want * fix hygiene * update distro * update distro * fix hygiene * fix strict nulls * distro * distro * fix tests * fix tests * add another edit * fix viewlet icon * fix azure dialog * fix some padding * fix more padding issues
@@ -123,20 +123,3 @@ export const isWebkitWebView = (!isChrome && !isSafari && isWebKit);
|
||||
export const isIPad = (userAgent.indexOf('iPad') >= 0);
|
||||
export const isEdgeWebView = isEdge && (userAgent.indexOf('WebView/') >= 0);
|
||||
export const isStandalone = (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches);
|
||||
|
||||
export function hasClipboardSupport() {
|
||||
if (isIE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isEdge) {
|
||||
let index = userAgent.indexOf('Edge/');
|
||||
let version = parseInt(userAgent.substring(index + 5, userAgent.indexOf('.', index)), 10);
|
||||
|
||||
if (!version || (version >= 12 && version <= 16)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
60
src/vs/base/browser/canIUse.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
|
||||
export const enum KeyboardSupport {
|
||||
Always,
|
||||
FullScreen,
|
||||
None
|
||||
}
|
||||
|
||||
/**
|
||||
* Browser feature we can support in current platform, browser and environment.
|
||||
*/
|
||||
export const BrowserFeatures = {
|
||||
clipboard: {
|
||||
writeText: (
|
||||
platform.isNative
|
||||
|| (document.queryCommandSupported && document.queryCommandSupported('copy'))
|
||||
|| !!(navigator && navigator.clipboard && navigator.clipboard.writeText)
|
||||
),
|
||||
readText: (
|
||||
platform.isNative
|
||||
|| !!(navigator && navigator.clipboard && navigator.clipboard.readText)
|
||||
),
|
||||
richText: (() => {
|
||||
if (browser.isIE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (browser.isEdge) {
|
||||
let index = navigator.userAgent.indexOf('Edge/');
|
||||
let version = parseInt(navigator.userAgent.substring(index + 5, navigator.userAgent.indexOf('.', index)), 10);
|
||||
|
||||
if (!version || (version >= 12 && version <= 16)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
})()
|
||||
},
|
||||
keyboard: (() => {
|
||||
if (platform.isNative || browser.isStandalone) {
|
||||
return KeyboardSupport.Always;
|
||||
}
|
||||
|
||||
if ((<any>navigator).keyboard || browser.isSafari) {
|
||||
return KeyboardSupport.FullScreen;
|
||||
}
|
||||
|
||||
return KeyboardSupport.None;
|
||||
})(),
|
||||
|
||||
touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0,
|
||||
pointerEvents: window.PointerEvent && ('ontouchstart' in window || window.navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0)
|
||||
};
|
||||
@@ -10,10 +10,10 @@ import { SubmenuAction } from 'vs/base/browser/ui/menu/menu';
|
||||
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
|
||||
export interface IContextMenuEvent {
|
||||
shiftKey?: boolean;
|
||||
ctrlKey?: boolean;
|
||||
altKey?: boolean;
|
||||
metaKey?: boolean;
|
||||
readonly shiftKey?: boolean;
|
||||
readonly ctrlKey?: boolean;
|
||||
readonly altKey?: boolean;
|
||||
readonly metaKey?: boolean;
|
||||
}
|
||||
|
||||
export class ContextSubMenu extends SubmenuAction {
|
||||
|
||||
@@ -16,6 +16,7 @@ import * as platform from 'vs/base/common/platform';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas, RemoteAuthorities } from 'vs/base/common/network';
|
||||
import { BrowserFeatures } from 'vs/base/browser/canIUse';
|
||||
|
||||
export function clearNode(node: HTMLElement): void {
|
||||
while (node.firstChild) {
|
||||
@@ -266,6 +267,23 @@ export let addStandardDisposableListener: IAddStandardDisposableListenerSignatur
|
||||
return addDisposableListener(node, type, wrapHandler, useCapture);
|
||||
};
|
||||
|
||||
export let addStandardDisposableGenericMouseDownListner = function addStandardDisposableListener(node: HTMLElement, handler: (event: any) => void, useCapture?: boolean): IDisposable {
|
||||
let wrapHandler = _wrapAsStandardMouseEvent(handler);
|
||||
|
||||
return addDisposableGenericMouseDownListner(node, wrapHandler, useCapture);
|
||||
};
|
||||
|
||||
export function addDisposableGenericMouseDownListner(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
|
||||
return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_DOWN : EventType.MOUSE_DOWN, handler, useCapture);
|
||||
}
|
||||
|
||||
export function addDisposableGenericMouseMoveListner(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
|
||||
return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_MOVE : EventType.MOUSE_MOVE, handler, useCapture);
|
||||
}
|
||||
|
||||
export function addDisposableGenericMouseUpListner(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
|
||||
@@ -281,6 +299,21 @@ export function addDisposableNonBubblingMouseOutListener(node: Element, handler:
|
||||
});
|
||||
}
|
||||
|
||||
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 || e.target);
|
||||
while (toElement && toElement !== node) {
|
||||
toElement = toElement.parentNode;
|
||||
}
|
||||
if (toElement === node) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler(e);
|
||||
});
|
||||
}
|
||||
|
||||
interface IRequestAnimationFrame {
|
||||
(callback: (time: number) => void): number;
|
||||
}
|
||||
@@ -428,7 +461,7 @@ export interface DOMEvent {
|
||||
}
|
||||
|
||||
const MINIMUM_TIME_MS = 16;
|
||||
const DEFAULT_EVENT_MERGER: IEventMerger<DOMEvent, DOMEvent> = function (lastEvent: DOMEvent, currentEvent: DOMEvent) {
|
||||
const DEFAULT_EVENT_MERGER: IEventMerger<DOMEvent, DOMEvent> = function (lastEvent: DOMEvent | null, currentEvent: DOMEvent) {
|
||||
return currentEvent;
|
||||
};
|
||||
|
||||
@@ -477,6 +510,20 @@ export function getClientArea(element: HTMLElement): Dimension {
|
||||
return new Dimension(element.clientWidth, element.clientHeight);
|
||||
}
|
||||
|
||||
// If visual view port exits and it's on mobile, it should be used instead of window innerWidth / innerHeight, or document.body.clientWidth / document.body.clientHeight
|
||||
if (platform.isIOS && (<any>window).visualViewport) {
|
||||
const width = (<any>window).visualViewport.width;
|
||||
const height = (<any>window).visualViewport.height - (
|
||||
browser.isStandalone
|
||||
// in PWA mode, the visual viewport always includes the safe-area-inset-bottom (which is for the home indicator)
|
||||
// even when you are using the onscreen monitor, the visual viewport will include the area between system statusbar and the onscreen keyboard
|
||||
// plus the area between onscreen keyboard and the bottom bezel, which is 20px on iOS.
|
||||
? (20 + 4) // + 4px for body margin
|
||||
: 0
|
||||
);
|
||||
return new Dimension(width, height);
|
||||
}
|
||||
|
||||
// Try innerWidth / innerHeight
|
||||
if (window.innerWidth && window.innerHeight) {
|
||||
return new Dimension(window.innerWidth, window.innerHeight);
|
||||
@@ -852,6 +899,9 @@ export const EventType = {
|
||||
MOUSE_OUT: 'mouseout',
|
||||
MOUSE_ENTER: 'mouseenter',
|
||||
MOUSE_LEAVE: 'mouseleave',
|
||||
POINTER_UP: 'pointerup',
|
||||
POINTER_DOWN: 'pointerdown',
|
||||
POINTER_MOVE: 'pointermove',
|
||||
CONTEXT_MENU: 'contextmenu',
|
||||
WHEEL: 'wheel',
|
||||
// Keyboard
|
||||
@@ -1029,6 +1079,11 @@ function _$<T extends Element>(namespace: Namespace, description: string, attrs?
|
||||
|
||||
Object.keys(attrs).forEach(name => {
|
||||
const value = attrs![name];
|
||||
|
||||
if (typeof value === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (/^on\w+$/.test(name)) {
|
||||
(<any>result)[name] = value;
|
||||
} else if (name === 'selected') {
|
||||
@@ -1212,3 +1267,18 @@ export function asCSSUrl(uri: URI): string {
|
||||
}
|
||||
return `url('${asDomUri(uri).toString(true).replace(/'/g, '%27')}')`;
|
||||
}
|
||||
|
||||
export function triggerDownload(uri: URI, name: string): void {
|
||||
// In order to download from the browser, the only way seems
|
||||
// to be creating a <a> element with download attribute that
|
||||
// points to the file to download.
|
||||
// See also https://developers.google.com/web/updates/2011/08/Downloading-resources-in-HTML5-a-download
|
||||
const anchor = document.createElement('a');
|
||||
document.body.appendChild(anchor);
|
||||
anchor.download = name;
|
||||
anchor.href = uri.toString(true);
|
||||
anchor.click();
|
||||
|
||||
// Ensure to remove the element from DOM eventually
|
||||
setTimeout(() => document.body.removeChild(anchor));
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ export class FastDomNode<T extends HTMLElement> {
|
||||
private _position: string;
|
||||
private _visibility: string;
|
||||
private _layerHint: boolean;
|
||||
private _contain: 'none' | 'strict' | 'content' | 'size' | 'layout' | 'style' | 'paint';
|
||||
|
||||
constructor(domNode: T) {
|
||||
this.domNode = domNode;
|
||||
@@ -47,6 +48,7 @@ export class FastDomNode<T extends HTMLElement> {
|
||||
this._position = '';
|
||||
this._visibility = '';
|
||||
this._layerHint = false;
|
||||
this._contain = 'none';
|
||||
}
|
||||
|
||||
public setMaxWidth(maxWidth: number): void {
|
||||
@@ -203,7 +205,15 @@ export class FastDomNode<T extends HTMLElement> {
|
||||
return;
|
||||
}
|
||||
this._layerHint = layerHint;
|
||||
(<any>this.domNode.style).willChange = this._layerHint ? 'transform' : 'auto';
|
||||
this.domNode.style.transform = this._layerHint ? 'translate3d(0px, 0px, 0px)' : '';
|
||||
}
|
||||
|
||||
public setContain(contain: 'none' | 'strict' | 'content' | 'size' | 'layout' | 'style' | 'paint'): void {
|
||||
if (this._contain === contain) {
|
||||
return;
|
||||
}
|
||||
this._contain = contain;
|
||||
(<any>this.domNode.style).contain = this._contain;
|
||||
}
|
||||
|
||||
public setAttribute(name: string, value: string): void {
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { IframeUtils } from 'vs/base/browser/iframe';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { BrowserFeatures } from 'vs/base/browser/canIUse';
|
||||
|
||||
export interface IStandardMouseMoveEventData {
|
||||
leftButton: boolean;
|
||||
@@ -15,7 +17,7 @@ export interface IStandardMouseMoveEventData {
|
||||
}
|
||||
|
||||
export interface IEventMerger<R> {
|
||||
(lastEvent: R, currentEvent: MouseEvent): R;
|
||||
(lastEvent: R | null, currentEvent: MouseEvent): R;
|
||||
}
|
||||
|
||||
export interface IMouseMoveCallback<R> {
|
||||
@@ -26,7 +28,7 @@ export interface IOnStopCallback {
|
||||
(): void;
|
||||
}
|
||||
|
||||
export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData, currentEvent: MouseEvent): IStandardMouseMoveEventData {
|
||||
export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData | null, currentEvent: MouseEvent): IStandardMouseMoveEventData {
|
||||
let ev = new StandardMouseEvent(currentEvent);
|
||||
ev.preventDefault();
|
||||
return {
|
||||
@@ -38,10 +40,10 @@ export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData,
|
||||
|
||||
export class GlobalMouseMoveMonitor<R> implements IDisposable {
|
||||
|
||||
private readonly hooks = new DisposableStore();
|
||||
private mouseMoveEventMerger: IEventMerger<R> | null = null;
|
||||
private mouseMoveCallback: IMouseMoveCallback<R> | null = null;
|
||||
private onStopCallback: IOnStopCallback | null = null;
|
||||
protected readonly hooks = new DisposableStore();
|
||||
protected mouseMoveEventMerger: IEventMerger<R> | null = null;
|
||||
protected mouseMoveCallback: IMouseMoveCallback<R> | null = null;
|
||||
protected onStopCallback: IOnStopCallback | null = null;
|
||||
|
||||
public dispose(): void {
|
||||
this.stopMonitoring(false);
|
||||
@@ -84,12 +86,14 @@ export class GlobalMouseMoveMonitor<R> implements IDisposable {
|
||||
this.onStopCallback = onStopCallback;
|
||||
|
||||
let windowChain = IframeUtils.getSameOriginWindowChain();
|
||||
const mouseMove = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointermove' : 'mousemove';
|
||||
const mouseUp = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointerup' : 'mouseup';
|
||||
for (const element of windowChain) {
|
||||
this.hooks.add(dom.addDisposableThrottledListener(element.window.document, 'mousemove',
|
||||
this.hooks.add(dom.addDisposableThrottledListener(element.window.document, mouseMove,
|
||||
(data: R) => this.mouseMoveCallback!(data),
|
||||
(lastEvent: R, currentEvent) => this.mouseMoveEventMerger!(lastEvent, currentEvent as MouseEvent)
|
||||
(lastEvent: R | null, currentEvent) => this.mouseMoveEventMerger!(lastEvent, currentEvent as MouseEvent)
|
||||
));
|
||||
this.hooks.add(dom.addDisposableListener(element.window.document, 'mouseup', (e: MouseEvent) => this.stopMonitoring(true)));
|
||||
this.hooks.add(dom.addDisposableListener(element.window.document, mouseUp, (e: MouseEvent) => this.stopMonitoring(true)));
|
||||
}
|
||||
|
||||
if (IframeUtils.hasDifferentOriginAncestor()) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { cloneAndChange } from 'vs/base/common/objects';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
|
||||
|
||||
export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
|
||||
codeBlockRenderer?: (modeId: string, value: string) => Promise<string>;
|
||||
@@ -50,28 +51,32 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
const _href = function (href: string, isDomUri: boolean): string {
|
||||
const data = markdown.uris && markdown.uris[href];
|
||||
if (!data) {
|
||||
return href;
|
||||
return href; // no uri exists
|
||||
}
|
||||
let uri = URI.revive(data);
|
||||
if (URI.parse(href).toString() === uri.toString()) {
|
||||
return href; // no tranformation performed
|
||||
}
|
||||
if (isDomUri) {
|
||||
uri = DOM.asDomUri(uri);
|
||||
}
|
||||
if (uri.query) {
|
||||
uri = uri.with({ query: _uriMassage(uri.query) });
|
||||
}
|
||||
if (data) {
|
||||
href = uri.toString(true);
|
||||
}
|
||||
return href;
|
||||
return uri.toString(true);
|
||||
};
|
||||
|
||||
// signal to code-block render that the
|
||||
// element has been created
|
||||
let signalInnerHTML: () => void;
|
||||
const withInnerHTML = new Promise(c => signalInnerHTML = c);
|
||||
const withInnerHTML = new Promise<void>(c => signalInnerHTML = c);
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.image = (href: string, title: string, text: string) => {
|
||||
if (href && href.indexOf('vscode-icon://codicon/') === 0) {
|
||||
return renderCodicons(`$(${URI.parse(href).path.substr(1)})`);
|
||||
}
|
||||
|
||||
let dimensions: string[] = [];
|
||||
let attributes: string[] = [];
|
||||
if (href) {
|
||||
@@ -187,7 +192,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
renderer
|
||||
};
|
||||
|
||||
const allowedSchemes = [Schemas.http, Schemas.https, Schemas.mailto, Schemas.data, Schemas.file, Schemas.vscodeRemote];
|
||||
const allowedSchemes = [Schemas.http, Schemas.https, Schemas.mailto, Schemas.data, Schemas.file, Schemas.vscodeRemote, Schemas.vscodeRemoteResource];
|
||||
if (markdown.isTrusted) {
|
||||
allowedSchemes.push(Schemas.command);
|
||||
}
|
||||
@@ -199,7 +204,8 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
'a': ['href', 'name', 'target', 'data-href'],
|
||||
'iframe': ['allowfullscreen', 'frameborder', 'src'],
|
||||
'img': ['src', 'title', 'alt', 'width', 'height'],
|
||||
'div': ['class', 'data-code']
|
||||
'div': ['class', 'data-code'],
|
||||
'span': ['class']
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface GestureEvent extends MouseEvent {
|
||||
translationY: number;
|
||||
pageX: number;
|
||||
pageY: number;
|
||||
tapCount: number;
|
||||
}
|
||||
|
||||
interface Touch {
|
||||
@@ -71,16 +72,24 @@ export class Gesture extends Disposable {
|
||||
|
||||
private dispatched = false;
|
||||
private targets: HTMLElement[];
|
||||
private ignoreTargets: HTMLElement[];
|
||||
private handle: IDisposable | null;
|
||||
|
||||
private activeTouches: { [id: number]: TouchData; };
|
||||
|
||||
private _lastSetTapCountTime: number;
|
||||
|
||||
private static readonly CLEAR_TAP_COUNT_TIME = 400; // ms
|
||||
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
|
||||
this.activeTouches = {};
|
||||
this.handle = null;
|
||||
this.targets = [];
|
||||
this.ignoreTargets = [];
|
||||
this._lastSetTapCountTime = 0;
|
||||
this._register(DomUtils.addDisposableListener(document, 'touchstart', (e: TouchEvent) => this.onTouchStart(e)));
|
||||
this._register(DomUtils.addDisposableListener(document, 'touchend', (e: TouchEvent) => this.onTouchEnd(e)));
|
||||
this._register(DomUtils.addDisposableListener(document, 'touchmove', (e: TouchEvent) => this.onTouchMove(e)));
|
||||
@@ -103,6 +112,23 @@ export class Gesture extends Disposable {
|
||||
};
|
||||
}
|
||||
|
||||
public static ignoreTarget(element: HTMLElement): IDisposable {
|
||||
if (!Gesture.isTouchDevice()) {
|
||||
return Disposable.None;
|
||||
}
|
||||
if (!Gesture.INSTANCE) {
|
||||
Gesture.INSTANCE = new Gesture();
|
||||
}
|
||||
|
||||
Gesture.INSTANCE.ignoreTargets.push(element);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
Gesture.INSTANCE.ignoreTargets = Gesture.INSTANCE.ignoreTargets.filter(t => t !== element);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@memoize
|
||||
private static isTouchDevice(): boolean {
|
||||
return 'ontouchstart' in window as any || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0;
|
||||
@@ -224,10 +250,33 @@ export class Gesture extends Disposable {
|
||||
let event = <GestureEvent>(<any>document.createEvent('CustomEvent'));
|
||||
event.initEvent(type, false, true);
|
||||
event.initialTarget = initialTarget;
|
||||
event.tapCount = 0;
|
||||
return event;
|
||||
}
|
||||
|
||||
private dispatchEvent(event: GestureEvent): void {
|
||||
if (event.type === EventType.Tap) {
|
||||
const currentTime = (new Date()).getTime();
|
||||
let setTapCount = 0;
|
||||
if (currentTime - this._lastSetTapCountTime > Gesture.CLEAR_TAP_COUNT_TIME) {
|
||||
setTapCount = 1;
|
||||
} else {
|
||||
setTapCount = 2;
|
||||
}
|
||||
|
||||
this._lastSetTapCountTime = currentTime;
|
||||
event.tapCount = setTapCount;
|
||||
} else if (event.type === EventType.Change || event.type === EventType.Contextmenu) {
|
||||
// tap is canceled by scrolling or context menu
|
||||
this._lastSetTapCountTime = 0;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.ignoreTargets.length; i++) {
|
||||
if (event.initialTarget instanceof Node && this.ignoreTargets[i].contains(event.initialTarget)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.targets.forEach(target => {
|
||||
if (event.initialTarget instanceof Node && target.contains(event.initialTarget)) {
|
||||
target.dispatchEvent(event);
|
||||
|
||||
@@ -90,4 +90,5 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { DataTransfers } from 'vs/base/browser/dnd';
|
||||
import { isFirefox } from 'vs/base/browser/browser';
|
||||
|
||||
export interface IActionViewItem extends IDisposable {
|
||||
actionRunner: IActionRunner;
|
||||
@@ -113,6 +115,11 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem {
|
||||
const enableDragging = this.options && this.options.draggable;
|
||||
if (enableDragging) {
|
||||
container.draggable = true;
|
||||
|
||||
if (isFirefox) {
|
||||
// Firefox: requires to set a text data transfer to get going
|
||||
this._register(DOM.addDisposableListener(container, DOM.EventType.DRAG_START, e => e.dataTransfer?.setData(DataTransfers.TEXT, this._action.label)));
|
||||
}
|
||||
}
|
||||
|
||||
this._register(DOM.addDisposableListener(element, EventType.Tap, e => this.onClick(e)));
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
.monaco-breadcrumbs {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
@@ -23,6 +25,10 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.monaco-breadcrumbs .monaco-breadcrumb-item .codicon-chevron-right {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.monaco-breadcrumbs .monaco-breadcrumb-item:first-of-type::before {
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-text-button {
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
@@ -21,4 +20,4 @@
|
||||
.monaco-button.disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ function toSplitViewView(view: IView, getHeight: () => number): ISplitViewView {
|
||||
get maximumSize() { return view.maximumWidth; },
|
||||
get minimumSize() { return view.minimumWidth; },
|
||||
onDidChange: Event.map(view.onDidChange, e => e && e.width),
|
||||
layout: size => view.layout(size, getHeight(), Orientation.HORIZONTAL)
|
||||
layout: (size, offset) => view.layout(size, getHeight(), 0, offset)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export class CenteredViewLayout implements IDisposable {
|
||||
this.resizeMargins();
|
||||
}
|
||||
} else {
|
||||
this.view.layout(width, height, Orientation.HORIZONTAL);
|
||||
this.view.layout(width, height, 0, 0);
|
||||
}
|
||||
this.didLayout = true;
|
||||
}
|
||||
|
||||
@@ -13,19 +13,10 @@
|
||||
height: 20px;
|
||||
border: 1px solid transparent;
|
||||
padding: 1px;
|
||||
|
||||
-webkit-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-o-user-select: none;
|
||||
-ms-user-select: none;
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.monaco-custom-checkbox:hover,
|
||||
|
||||
@@ -113,6 +113,8 @@ export class Checkbox extends Widget {
|
||||
ev.preventDefault();
|
||||
});
|
||||
|
||||
this.ignoreGesture(this.domNode);
|
||||
|
||||
this.onkeydown(this.domNode, (keyboardEvent) => {
|
||||
if (keyboardEvent.keyCode === KeyCode.Space || keyboardEvent.keyCode === KeyCode.Enter) {
|
||||
this.checked = !this._checked;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
@font-face {
|
||||
font-family: "codicon";
|
||||
src: url("./codicon.ttf?3a05fcfc657285cdb4cd3eba790b7462") format("truetype");
|
||||
src: url("./codicon.ttf?c4e66586cd3ad4acc55fc456c0760dec") format("truetype");
|
||||
}
|
||||
|
||||
.codicon[class*='codicon-'] {
|
||||
@@ -16,10 +16,9 @@
|
||||
text-align: center;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +69,10 @@
|
||||
.codicon-eye-watch:before { content: "\ea70" }
|
||||
.codicon-circle-filled:before { content: "\ea71" }
|
||||
.codicon-primitive-dot:before { content: "\ea71" }
|
||||
.codicon-close-dirty:before { content: "\ea71" }
|
||||
.codicon-debug-breakpoint:before { content: "\ea71" }
|
||||
.codicon-debug-breakpoint-disabled:before { content: "\ea71" }
|
||||
.codicon-debug-hint:before { content: "\ea71" }
|
||||
.codicon-primitive-square:before { content: "\ea72" }
|
||||
.codicon-edit:before { content: "\ea73" }
|
||||
.codicon-pencil:before { content: "\ea73" }
|
||||
@@ -93,6 +96,7 @@
|
||||
.codicon-file:before { content: "\ea7b" }
|
||||
.codicon-file-text:before { content: "\ea7b" }
|
||||
.codicon-more:before { content: "\ea7c" }
|
||||
.codicon-ellipsis:before { content: "\ea7c" }
|
||||
.codicon-kebab-horizontal:before { content: "\ea7c" }
|
||||
.codicon-mail-reply:before { content: "\ea7d" }
|
||||
.codicon-reply:before { content: "\ea7d" }
|
||||
@@ -139,7 +143,6 @@
|
||||
.codicon-symbol-parameter:before { content: "\ea92" }
|
||||
.codicon-symbol-type-parameter:before { content: "\ea92" }
|
||||
.codicon-symbol-key:before { content: "\ea93" }
|
||||
.codicon-symbol-string:before { content: "\ea93" }
|
||||
.codicon-symbol-text:before { content: "\ea93" }
|
||||
.codicon-symbol-reference:before { content: "\ea94" }
|
||||
.codicon-go-to-file:before { content: "\ea94" }
|
||||
@@ -147,237 +150,259 @@
|
||||
.codicon-symbol-value:before { content: "\ea95" }
|
||||
.codicon-symbol-ruler:before { content: "\ea96" }
|
||||
.codicon-symbol-unit:before { content: "\ea96" }
|
||||
.codicon-activate-breakpoints:before { content: "\f101" }
|
||||
.codicon-archive:before { content: "\f102" }
|
||||
.codicon-arrow-both:before { content: "\f103" }
|
||||
.codicon-arrow-down:before { content: "\f104" }
|
||||
.codicon-arrow-left:before { content: "\f105" }
|
||||
.codicon-arrow-right:before { content: "\f106" }
|
||||
.codicon-arrow-small-down:before { content: "\f107" }
|
||||
.codicon-arrow-small-left:before { content: "\f108" }
|
||||
.codicon-arrow-small-right:before { content: "\f109" }
|
||||
.codicon-arrow-small-up:before { content: "\f10a" }
|
||||
.codicon-arrow-up:before { content: "\f10b" }
|
||||
.codicon-bell:before { content: "\f10c" }
|
||||
.codicon-bold:before { content: "\f10d" }
|
||||
.codicon-book:before { content: "\f10e" }
|
||||
.codicon-bookmark:before { content: "\f10f" }
|
||||
.codicon-breakpoint-conditional-unverified:before { content: "\f110" }
|
||||
.codicon-breakpoint-conditional:before { content: "\f111" }
|
||||
.codicon-breakpoint-data-unverified:before { content: "\f112" }
|
||||
.codicon-breakpoint-data:before { content: "\f113" }
|
||||
.codicon-breakpoint-log-unverified:before { content: "\f114" }
|
||||
.codicon-breakpoint-log:before { content: "\f115" }
|
||||
.codicon-briefcase:before { content: "\f116" }
|
||||
.codicon-broadcast:before { content: "\f117" }
|
||||
.codicon-browser:before { content: "\f118" }
|
||||
.codicon-bug:before { content: "\f119" }
|
||||
.codicon-calendar:before { content: "\f11a" }
|
||||
.codicon-case-sensitive:before { content: "\f11b" }
|
||||
.codicon-check:before { content: "\f11c" }
|
||||
.codicon-checklist:before { content: "\f11d" }
|
||||
.codicon-chevron-down:before { content: "\f11e" }
|
||||
.codicon-chevron-left:before { content: "\f11f" }
|
||||
.codicon-chevron-right:before { content: "\f120" }
|
||||
.codicon-chevron-up:before { content: "\f121" }
|
||||
.codicon-chrome-close:before { content: "\f122" }
|
||||
.codicon-chrome-maximize:before { content: "\f123" }
|
||||
.codicon-chrome-minimize:before { content: "\f124" }
|
||||
.codicon-chrome-restore:before { content: "\f125" }
|
||||
.codicon-circle-outline:before { content: "\f126" }
|
||||
.codicon-circle-slash:before { content: "\f127" }
|
||||
.codicon-circuit-board:before { content: "\f128" }
|
||||
.codicon-clear-all:before { content: "\f129" }
|
||||
.codicon-clippy:before { content: "\f12a" }
|
||||
.codicon-close-all:before { content: "\f12b" }
|
||||
.codicon-cloud-download:before { content: "\f12c" }
|
||||
.codicon-cloud-upload:before { content: "\f12d" }
|
||||
.codicon-code:before { content: "\f12e" }
|
||||
.codicon-collapse-all:before { content: "\f12f" }
|
||||
.codicon-color-mode:before { content: "\f130" }
|
||||
.codicon-comment-discussion:before { content: "\f131" }
|
||||
.codicon-compare-changes:before { content: "\f132" }
|
||||
.codicon-continue:before { content: "\f133" }
|
||||
.codicon-credit-card:before { content: "\f134" }
|
||||
.codicon-current-and-breakpoint:before { content: "\f135" }
|
||||
.codicon-current:before { content: "\f136" }
|
||||
.codicon-dash:before { content: "\f137" }
|
||||
.codicon-dashboard:before { content: "\f138" }
|
||||
.codicon-database:before { content: "\f139" }
|
||||
.codicon-debug-disconnect:before { content: "\f13a" }
|
||||
.codicon-debug-pause:before { content: "\f13b" }
|
||||
.codicon-debug-restart:before { content: "\f13c" }
|
||||
.codicon-debug-start:before { content: "\f13d" }
|
||||
.codicon-debug-step-into:before { content: "\f13e" }
|
||||
.codicon-debug-step-out:before { content: "\f13f" }
|
||||
.codicon-debug-step-over:before { content: "\f140" }
|
||||
.codicon-debug-stop:before { content: "\f141" }
|
||||
.codicon-debug:before { content: "\f142" }
|
||||
.codicon-device-camera-video:before { content: "\f143" }
|
||||
.codicon-device-camera:before { content: "\f144" }
|
||||
.codicon-device-mobile:before { content: "\f145" }
|
||||
.codicon-diff-added:before { content: "\f146" }
|
||||
.codicon-diff-ignored:before { content: "\f147" }
|
||||
.codicon-diff-modified:before { content: "\f148" }
|
||||
.codicon-diff-removed:before { content: "\f149" }
|
||||
.codicon-diff-renamed:before { content: "\f14a" }
|
||||
.codicon-diff:before { content: "\f14b" }
|
||||
.codicon-discard:before { content: "\f14c" }
|
||||
.codicon-editor-layout:before { content: "\f14d" }
|
||||
.codicon-ellipsis:before { content: "\f14e" }
|
||||
.codicon-empty-window:before { content: "\f14f" }
|
||||
.codicon-exclude:before { content: "\f150" }
|
||||
.codicon-extensions:before { content: "\f151" }
|
||||
.codicon-eye-closed:before { content: "\f152" }
|
||||
.codicon-file-binary:before { content: "\f153" }
|
||||
.codicon-file-code:before { content: "\f154" }
|
||||
.codicon-file-media:before { content: "\f155" }
|
||||
.codicon-file-pdf:before { content: "\f156" }
|
||||
.codicon-file-submodule:before { content: "\f157" }
|
||||
.codicon-file-symlink-directory:before { content: "\f158" }
|
||||
.codicon-file-symlink-file:before { content: "\f159" }
|
||||
.codicon-file-zip:before { content: "\f15a" }
|
||||
.codicon-files:before { content: "\f15b" }
|
||||
.codicon-filter:before { content: "\f15c" }
|
||||
.codicon-flame:before { content: "\f15d" }
|
||||
.codicon-fold-down:before { content: "\f15e" }
|
||||
.codicon-fold-up:before { content: "\f15f" }
|
||||
.codicon-fold:before { content: "\f160" }
|
||||
.codicon-folder-active:before { content: "\f161" }
|
||||
.codicon-folder-opened:before { content: "\f162" }
|
||||
.codicon-gear:before { content: "\f163" }
|
||||
.codicon-gift:before { content: "\f164" }
|
||||
.codicon-gist-secret:before { content: "\f165" }
|
||||
.codicon-gist:before { content: "\f166" }
|
||||
.codicon-git-commit:before { content: "\f167" }
|
||||
.codicon-git-compare:before { content: "\f168" }
|
||||
.codicon-git-merge:before { content: "\f169" }
|
||||
.codicon-github-action:before { content: "\f16a" }
|
||||
.codicon-github-alt:before { content: "\f16b" }
|
||||
.codicon-globe:before { content: "\f16c" }
|
||||
.codicon-grabber:before { content: "\f16d" }
|
||||
.codicon-graph:before { content: "\f16e" }
|
||||
.codicon-gripper:before { content: "\f16f" }
|
||||
.codicon-heart:before { content: "\f170" }
|
||||
.codicon-home:before { content: "\f171" }
|
||||
.codicon-horizontal-rule:before { content: "\f172" }
|
||||
.codicon-hubot:before { content: "\f173" }
|
||||
.codicon-inbox:before { content: "\f174" }
|
||||
.codicon-issue-closed:before { content: "\f175" }
|
||||
.codicon-issue-reopened:before { content: "\f176" }
|
||||
.codicon-issues:before { content: "\f177" }
|
||||
.codicon-italic:before { content: "\f178" }
|
||||
.codicon-jersey:before { content: "\f179" }
|
||||
.codicon-json:before { content: "\f17a" }
|
||||
.codicon-kebab-vertical:before { content: "\f17b" }
|
||||
.codicon-law:before { content: "\f17c" }
|
||||
.codicon-lightbulb-autofix:before { content: "\f17d" }
|
||||
.codicon-link-external:before { content: "\f17e" }
|
||||
.codicon-link:before { content: "\f17f" }
|
||||
.codicon-list-ordered:before { content: "\f180" }
|
||||
.codicon-list-unordered:before { content: "\f181" }
|
||||
.codicon-live-share:before { content: "\f182" }
|
||||
.codicon-loading:before { content: "\f183" }
|
||||
.codicon-location:before { content: "\f184" }
|
||||
.codicon-mail-read:before { content: "\f185" }
|
||||
.codicon-mail:before { content: "\f186" }
|
||||
.codicon-markdown:before { content: "\f187" }
|
||||
.codicon-megaphone:before { content: "\f188" }
|
||||
.codicon-mention:before { content: "\f189" }
|
||||
.codicon-milestone:before { content: "\f18a" }
|
||||
.codicon-mortar-board:before { content: "\f18b" }
|
||||
.codicon-move:before { content: "\f18c" }
|
||||
.codicon-multiple-windows:before { content: "\f18d" }
|
||||
.codicon-mute:before { content: "\f18e" }
|
||||
.codicon-no-newline:before { content: "\f18f" }
|
||||
.codicon-note:before { content: "\f190" }
|
||||
.codicon-octoface:before { content: "\f191" }
|
||||
.codicon-open-preview:before { content: "\f192" }
|
||||
.codicon-package:before { content: "\f193" }
|
||||
.codicon-paintcan:before { content: "\f194" }
|
||||
.codicon-pin:before { content: "\f195" }
|
||||
.codicon-play:before { content: "\f196" }
|
||||
.codicon-plug:before { content: "\f197" }
|
||||
.codicon-preserve-case:before { content: "\f198" }
|
||||
.codicon-preview:before { content: "\f199" }
|
||||
.codicon-project:before { content: "\f19a" }
|
||||
.codicon-pulse:before { content: "\f19b" }
|
||||
.codicon-question:before { content: "\f19c" }
|
||||
.codicon-quote:before { content: "\f19d" }
|
||||
.codicon-radio-tower:before { content: "\f19e" }
|
||||
.codicon-reactions:before { content: "\f19f" }
|
||||
.codicon-references:before { content: "\f1a0" }
|
||||
.codicon-refresh:before { content: "\f1a1" }
|
||||
.codicon-regex:before { content: "\f1a2" }
|
||||
.codicon-remote:before { content: "\f1a3" }
|
||||
.codicon-remove:before { content: "\f1a4" }
|
||||
.codicon-replace-all:before { content: "\f1a5" }
|
||||
.codicon-replace:before { content: "\f1a6" }
|
||||
.codicon-repo-clone:before { content: "\f1a7" }
|
||||
.codicon-repo-force-push:before { content: "\f1a8" }
|
||||
.codicon-repo-pull:before { content: "\f1a9" }
|
||||
.codicon-repo-push:before { content: "\f1aa" }
|
||||
.codicon-report:before { content: "\f1ab" }
|
||||
.codicon-request-changes:before { content: "\f1ac" }
|
||||
.codicon-rocket:before { content: "\f1ad" }
|
||||
.codicon-root-folder-opened:before { content: "\f1ae" }
|
||||
.codicon-root-folder:before { content: "\f1af" }
|
||||
.codicon-rss:before { content: "\f1b0" }
|
||||
.codicon-ruby:before { content: "\f1b1" }
|
||||
.codicon-save-all:before { content: "\f1b2" }
|
||||
.codicon-save-as:before { content: "\f1b3" }
|
||||
.codicon-save:before { content: "\f1b4" }
|
||||
.codicon-screen-full:before { content: "\f1b5" }
|
||||
.codicon-screen-normal:before { content: "\f1b6" }
|
||||
.codicon-search-stop:before { content: "\f1b7" }
|
||||
.codicon-selection:before { content: "\f1b8" }
|
||||
.codicon-server:before { content: "\f1b9" }
|
||||
.codicon-settings:before { content: "\f1ba" }
|
||||
.codicon-shield:before { content: "\f1bb" }
|
||||
.codicon-smiley:before { content: "\f1bc" }
|
||||
.codicon-sort-precedence:before { content: "\f1bd" }
|
||||
.codicon-split-horizontal:before { content: "\f1be" }
|
||||
.codicon-split-vertical:before { content: "\f1bf" }
|
||||
.codicon-squirrel:before { content: "\f1c0" }
|
||||
.codicon-star-full:before { content: "\f1c1" }
|
||||
.codicon-star-half:before { content: "\f1c2" }
|
||||
.codicon-symbol-class:before { content: "\f1c3" }
|
||||
.codicon-symbol-color:before { content: "\f1c4" }
|
||||
.codicon-symbol-constant:before { content: "\f1c5" }
|
||||
.codicon-symbol-enum-member:before { content: "\f1c6" }
|
||||
.codicon-symbol-field:before { content: "\f1c7" }
|
||||
.codicon-symbol-file:before { content: "\f1c8" }
|
||||
.codicon-symbol-interface:before { content: "\f1c9" }
|
||||
.codicon-symbol-keyword:before { content: "\f1ca" }
|
||||
.codicon-symbol-misc:before { content: "\f1cb" }
|
||||
.codicon-symbol-operator:before { content: "\f1cc" }
|
||||
.codicon-symbol-property:before { content: "\f1cd" }
|
||||
.codicon-symbol-snippet:before { content: "\f1ce" }
|
||||
.codicon-tasklist:before { content: "\f1cf" }
|
||||
.codicon-telescope:before { content: "\f1d0" }
|
||||
.codicon-text-size:before { content: "\f1d1" }
|
||||
.codicon-three-bars:before { content: "\f1d2" }
|
||||
.codicon-thumbsdown:before { content: "\f1d3" }
|
||||
.codicon-thumbsup:before { content: "\f1d4" }
|
||||
.codicon-tools:before { content: "\f1d5" }
|
||||
.codicon-triangle-down:before { content: "\f1d6" }
|
||||
.codicon-triangle-left:before { content: "\f1d7" }
|
||||
.codicon-triangle-right:before { content: "\f1d8" }
|
||||
.codicon-triangle-up:before { content: "\f1d9" }
|
||||
.codicon-twitter:before { content: "\f1da" }
|
||||
.codicon-unfold:before { content: "\f1db" }
|
||||
.codicon-unlock:before { content: "\f1dc" }
|
||||
.codicon-unmute:before { content: "\f1dd" }
|
||||
.codicon-unverified:before { content: "\f1de" }
|
||||
.codicon-verified:before { content: "\f1df" }
|
||||
.codicon-versions:before { content: "\f1e0" }
|
||||
.codicon-vm-active:before { content: "\f1e1" }
|
||||
.codicon-vm-outline:before { content: "\f1e2" }
|
||||
.codicon-vm-running:before { content: "\f1e3" }
|
||||
.codicon-watch:before { content: "\f1e4" }
|
||||
.codicon-whitespace:before { content: "\f1e5" }
|
||||
.codicon-whole-word:before { content: "\f1e6" }
|
||||
.codicon-window:before { content: "\f1e7" }
|
||||
.codicon-word-wrap:before { content: "\f1e8" }
|
||||
.codicon-zoom-in:before { content: "\f1e9" }
|
||||
.codicon-zoom-out:before { content: "\f1ea" }
|
||||
.codicon-activate-breakpoints:before { content: "\ea97" }
|
||||
.codicon-archive:before { content: "\ea98" }
|
||||
.codicon-arrow-both:before { content: "\ea99" }
|
||||
.codicon-arrow-down:before { content: "\ea9a" }
|
||||
.codicon-arrow-left:before { content: "\ea9b" }
|
||||
.codicon-arrow-right:before { content: "\ea9c" }
|
||||
.codicon-arrow-small-down:before { content: "\ea9d" }
|
||||
.codicon-arrow-small-left:before { content: "\ea9e" }
|
||||
.codicon-arrow-small-right:before { content: "\ea9f" }
|
||||
.codicon-arrow-small-up:before { content: "\eaa0" }
|
||||
.codicon-arrow-up:before { content: "\eaa1" }
|
||||
.codicon-bell:before { content: "\eaa2" }
|
||||
.codicon-bold:before { content: "\eaa3" }
|
||||
.codicon-book:before { content: "\eaa4" }
|
||||
.codicon-bookmark:before { content: "\eaa5" }
|
||||
.codicon-debug-breakpoint-conditional-unverified:before { content: "\eaa6" }
|
||||
.codicon-debug-breakpoint-conditional:before { content: "\eaa7" }
|
||||
.codicon-debug-breakpoint-conditional-disabled:before { content: "\eaa7" }
|
||||
.codicon-debug-breakpoint-data-unverified:before { content: "\eaa8" }
|
||||
.codicon-debug-breakpoint-data:before { content: "\eaa9" }
|
||||
.codicon-debug-breakpoint-data-disabled:before { content: "\eaa9" }
|
||||
.codicon-debug-breakpoint-log-unverified:before { content: "\eaaa" }
|
||||
.codicon-debug-breakpoint-log:before { content: "\eaab" }
|
||||
.codicon-debug-breakpoint-log-disabled:before { content: "\eaab" }
|
||||
.codicon-briefcase:before { content: "\eaac" }
|
||||
.codicon-broadcast:before { content: "\eaad" }
|
||||
.codicon-browser:before { content: "\eaae" }
|
||||
.codicon-bug:before { content: "\eaaf" }
|
||||
.codicon-calendar:before { content: "\eab0" }
|
||||
.codicon-case-sensitive:before { content: "\eab1" }
|
||||
.codicon-check:before { content: "\eab2" }
|
||||
.codicon-checklist:before { content: "\eab3" }
|
||||
.codicon-chevron-down:before { content: "\eab4" }
|
||||
.codicon-chevron-left:before { content: "\eab5" }
|
||||
.codicon-chevron-right:before { content: "\eab6" }
|
||||
.codicon-chevron-up:before { content: "\eab7" }
|
||||
.codicon-chrome-close:before { content: "\eab8" }
|
||||
.codicon-chrome-maximize:before { content: "\eab9" }
|
||||
.codicon-chrome-minimize:before { content: "\eaba" }
|
||||
.codicon-chrome-restore:before { content: "\eabb" }
|
||||
.codicon-circle-outline:before { content: "\eabc" }
|
||||
.codicon-debug-breakpoint-unverified:before { content: "\eabc" }
|
||||
.codicon-circle-slash:before { content: "\eabd" }
|
||||
.codicon-circuit-board:before { content: "\eabe" }
|
||||
.codicon-clear-all:before { content: "\eabf" }
|
||||
.codicon-clippy:before { content: "\eac0" }
|
||||
.codicon-close-all:before { content: "\eac1" }
|
||||
.codicon-cloud-download:before { content: "\eac2" }
|
||||
.codicon-cloud-upload:before { content: "\eac3" }
|
||||
.codicon-code:before { content: "\eac4" }
|
||||
.codicon-collapse-all:before { content: "\eac5" }
|
||||
.codicon-color-mode:before { content: "\eac6" }
|
||||
.codicon-comment-discussion:before { content: "\eac7" }
|
||||
.codicon-compare-changes:before { content: "\eac8" }
|
||||
.codicon-credit-card:before { content: "\eac9" }
|
||||
.codicon-dash:before { content: "\eacc" }
|
||||
.codicon-dashboard:before { content: "\eacd" }
|
||||
.codicon-database:before { content: "\eace" }
|
||||
.codicon-debug-continue:before { content: "\eacf" }
|
||||
.codicon-debug-disconnect:before { content: "\ead0" }
|
||||
.codicon-debug-pause:before { content: "\ead1" }
|
||||
.codicon-debug-restart:before { content: "\ead2" }
|
||||
.codicon-debug-start:before { content: "\ead3" }
|
||||
.codicon-debug-step-into:before { content: "\ead4" }
|
||||
.codicon-debug-step-out:before { content: "\ead5" }
|
||||
.codicon-debug-step-over:before { content: "\ead6" }
|
||||
.codicon-debug-stop:before { content: "\ead7" }
|
||||
.codicon-debug:before { content: "\ead8" }
|
||||
.codicon-device-camera-video:before { content: "\ead9" }
|
||||
.codicon-device-camera:before { content: "\eada" }
|
||||
.codicon-device-mobile:before { content: "\eadb" }
|
||||
.codicon-diff-added:before { content: "\eadc" }
|
||||
.codicon-diff-ignored:before { content: "\eadd" }
|
||||
.codicon-diff-modified:before { content: "\eade" }
|
||||
.codicon-diff-removed:before { content: "\eadf" }
|
||||
.codicon-diff-renamed:before { content: "\eae0" }
|
||||
.codicon-diff:before { content: "\eae1" }
|
||||
.codicon-discard:before { content: "\eae2" }
|
||||
.codicon-editor-layout:before { content: "\eae3" }
|
||||
.codicon-empty-window:before { content: "\eae4" }
|
||||
.codicon-exclude:before { content: "\eae5" }
|
||||
.codicon-extensions:before { content: "\eae6" }
|
||||
.codicon-eye-closed:before { content: "\eae7" }
|
||||
.codicon-file-binary:before { content: "\eae8" }
|
||||
.codicon-file-code:before { content: "\eae9" }
|
||||
.codicon-file-media:before { content: "\eaea" }
|
||||
.codicon-file-pdf:before { content: "\eaeb" }
|
||||
.codicon-file-submodule:before { content: "\eaec" }
|
||||
.codicon-file-symlink-directory:before { content: "\eaed" }
|
||||
.codicon-file-symlink-file:before { content: "\eaee" }
|
||||
.codicon-file-zip:before { content: "\eaef" }
|
||||
.codicon-files:before { content: "\eaf0" }
|
||||
.codicon-filter:before { content: "\eaf1" }
|
||||
.codicon-flame:before { content: "\eaf2" }
|
||||
.codicon-fold-down:before { content: "\eaf3" }
|
||||
.codicon-fold-up:before { content: "\eaf4" }
|
||||
.codicon-fold:before { content: "\eaf5" }
|
||||
.codicon-folder-active:before { content: "\eaf6" }
|
||||
.codicon-folder-opened:before { content: "\eaf7" }
|
||||
.codicon-gear:before { content: "\eaf8" }
|
||||
.codicon-gift:before { content: "\eaf9" }
|
||||
.codicon-gist-secret:before { content: "\eafa" }
|
||||
.codicon-gist:before { content: "\eafb" }
|
||||
.codicon-git-commit:before { content: "\eafc" }
|
||||
.codicon-git-compare:before { content: "\eafd" }
|
||||
.codicon-git-merge:before { content: "\eafe" }
|
||||
.codicon-github-action:before { content: "\eaff" }
|
||||
.codicon-github-alt:before { content: "\eb00" }
|
||||
.codicon-globe:before { content: "\eb01" }
|
||||
.codicon-grabber:before { content: "\eb02" }
|
||||
.codicon-graph:before { content: "\eb03" }
|
||||
.codicon-gripper:before { content: "\eb04" }
|
||||
.codicon-heart:before { content: "\eb05" }
|
||||
.codicon-home:before { content: "\eb06" }
|
||||
.codicon-horizontal-rule:before { content: "\eb07" }
|
||||
.codicon-hubot:before { content: "\eb08" }
|
||||
.codicon-inbox:before { content: "\eb09" }
|
||||
.codicon-issue-closed:before { content: "\eb0a" }
|
||||
.codicon-issue-reopened:before { content: "\eb0b" }
|
||||
.codicon-issues:before { content: "\eb0c" }
|
||||
.codicon-italic:before { content: "\eb0d" }
|
||||
.codicon-jersey:before { content: "\eb0e" }
|
||||
.codicon-json:before { content: "\eb0f" }
|
||||
.codicon-kebab-vertical:before { content: "\eb10" }
|
||||
.codicon-key:before { content: "\eb11" }
|
||||
.codicon-law:before { content: "\eb12" }
|
||||
.codicon-lightbulb-autofix:before { content: "\eb13" }
|
||||
.codicon-link-external:before { content: "\eb14" }
|
||||
.codicon-link:before { content: "\eb15" }
|
||||
.codicon-list-ordered:before { content: "\eb16" }
|
||||
.codicon-list-unordered:before { content: "\eb17" }
|
||||
.codicon-live-share:before { content: "\eb18" }
|
||||
.codicon-loading:before { content: "\eb19" }
|
||||
.codicon-location:before { content: "\eb1a" }
|
||||
.codicon-mail-read:before { content: "\eb1b" }
|
||||
.codicon-mail:before { content: "\eb1c" }
|
||||
.codicon-markdown:before { content: "\eb1d" }
|
||||
.codicon-megaphone:before { content: "\eb1e" }
|
||||
.codicon-mention:before { content: "\eb1f" }
|
||||
.codicon-milestone:before { content: "\eb20" }
|
||||
.codicon-mortar-board:before { content: "\eb21" }
|
||||
.codicon-move:before { content: "\eb22" }
|
||||
.codicon-multiple-windows:before { content: "\eb23" }
|
||||
.codicon-mute:before { content: "\eb24" }
|
||||
.codicon-no-newline:before { content: "\eb25" }
|
||||
.codicon-note:before { content: "\eb26" }
|
||||
.codicon-octoface:before { content: "\eb27" }
|
||||
.codicon-open-preview:before { content: "\eb28" }
|
||||
.codicon-package:before { content: "\eb29" }
|
||||
.codicon-paintcan:before { content: "\eb2a" }
|
||||
.codicon-pin:before { content: "\eb2b" }
|
||||
.codicon-play:before { content: "\eb2c" }
|
||||
.codicon-plug:before { content: "\eb2d" }
|
||||
.codicon-preserve-case:before { content: "\eb2e" }
|
||||
.codicon-preview:before { content: "\eb2f" }
|
||||
.codicon-project:before { content: "\eb30" }
|
||||
.codicon-pulse:before { content: "\eb31" }
|
||||
.codicon-question:before { content: "\eb32" }
|
||||
.codicon-quote:before { content: "\eb33" }
|
||||
.codicon-radio-tower:before { content: "\eb34" }
|
||||
.codicon-reactions:before { content: "\eb35" }
|
||||
.codicon-references:before { content: "\eb36" }
|
||||
.codicon-refresh:before { content: "\eb37" }
|
||||
.codicon-regex:before { content: "\eb38" }
|
||||
.codicon-remote-explorer:before { content: "\eb39" }
|
||||
.codicon-remote:before { content: "\eb3a" }
|
||||
.codicon-remove:before { content: "\eb3b" }
|
||||
.codicon-replace-all:before { content: "\eb3c" }
|
||||
.codicon-replace:before { content: "\eb3d" }
|
||||
.codicon-repo-clone:before { content: "\eb3e" }
|
||||
.codicon-repo-force-push:before { content: "\eb3f" }
|
||||
.codicon-repo-pull:before { content: "\eb40" }
|
||||
.codicon-repo-push:before { content: "\eb41" }
|
||||
.codicon-report:before { content: "\eb42" }
|
||||
.codicon-request-changes:before { content: "\eb43" }
|
||||
.codicon-rocket:before { content: "\eb44" }
|
||||
.codicon-root-folder-opened:before { content: "\eb45" }
|
||||
.codicon-root-folder:before { content: "\eb46" }
|
||||
.codicon-rss:before { content: "\eb47" }
|
||||
.codicon-ruby:before { content: "\eb48" }
|
||||
.codicon-save-all:before { content: "\eb49" }
|
||||
.codicon-save-as:before { content: "\eb4a" }
|
||||
.codicon-save:before { content: "\eb4b" }
|
||||
.codicon-screen-full:before { content: "\eb4c" }
|
||||
.codicon-screen-normal:before { content: "\eb4d" }
|
||||
.codicon-search-stop:before { content: "\eb4e" }
|
||||
.codicon-server:before { content: "\eb50" }
|
||||
.codicon-settings-gear:before { content: "\eb51" }
|
||||
.codicon-settings:before { content: "\eb52" }
|
||||
.codicon-shield:before { content: "\eb53" }
|
||||
.codicon-smiley:before { content: "\eb54" }
|
||||
.codicon-sort-precedence:before { content: "\eb55" }
|
||||
.codicon-split-horizontal:before { content: "\eb56" }
|
||||
.codicon-split-vertical:before { content: "\eb57" }
|
||||
.codicon-squirrel:before { content: "\eb58" }
|
||||
.codicon-star-full:before { content: "\eb59" }
|
||||
.codicon-star-half:before { content: "\eb5a" }
|
||||
.codicon-symbol-class:before { content: "\eb5b" }
|
||||
.codicon-symbol-color:before { content: "\eb5c" }
|
||||
.codicon-symbol-constant:before { content: "\eb5d" }
|
||||
.codicon-symbol-enum-member:before { content: "\eb5e" }
|
||||
.codicon-symbol-field:before { content: "\eb5f" }
|
||||
.codicon-symbol-file:before { content: "\eb60" }
|
||||
.codicon-symbol-interface:before { content: "\eb61" }
|
||||
.codicon-symbol-keyword:before { content: "\eb62" }
|
||||
.codicon-symbol-misc:before { content: "\eb63" }
|
||||
.codicon-symbol-operator:before { content: "\eb64" }
|
||||
.codicon-symbol-property:before { content: "\eb65" }
|
||||
.codicon-symbol-snippet:before { content: "\eb66" }
|
||||
.codicon-tasklist:before { content: "\eb67" }
|
||||
.codicon-telescope:before { content: "\eb68" }
|
||||
.codicon-text-size:before { content: "\eb69" }
|
||||
.codicon-three-bars:before { content: "\eb6a" }
|
||||
.codicon-thumbsdown:before { content: "\eb6b" }
|
||||
.codicon-thumbsup:before { content: "\eb6c" }
|
||||
.codicon-tools:before { content: "\eb6d" }
|
||||
.codicon-triangle-down:before { content: "\eb6e" }
|
||||
.codicon-triangle-left:before { content: "\eb6f" }
|
||||
.codicon-triangle-right:before { content: "\eb70" }
|
||||
.codicon-triangle-up:before { content: "\eb71" }
|
||||
.codicon-twitter:before { content: "\eb72" }
|
||||
.codicon-unfold:before { content: "\eb73" }
|
||||
.codicon-unlock:before { content: "\eb74" }
|
||||
.codicon-unmute:before { content: "\eb75" }
|
||||
.codicon-unverified:before { content: "\eb76" }
|
||||
.codicon-verified:before { content: "\eb77" }
|
||||
.codicon-versions:before { content: "\eb78" }
|
||||
.codicon-vm-active:before { content: "\eb79" }
|
||||
.codicon-vm-outline:before { content: "\eb7a" }
|
||||
.codicon-vm-running:before { content: "\eb7b" }
|
||||
.codicon-watch:before { content: "\eb7c" }
|
||||
.codicon-whitespace:before { content: "\eb7d" }
|
||||
.codicon-whole-word:before { content: "\eb7e" }
|
||||
.codicon-window:before { content: "\eb7f" }
|
||||
.codicon-word-wrap:before { content: "\eb80" }
|
||||
.codicon-zoom-in:before { content: "\eb81" }
|
||||
.codicon-zoom-out:before { content: "\eb82" }
|
||||
.codicon-list-filter:before { content: "\eb83" }
|
||||
.codicon-list-flat:before { content: "\eb84" }
|
||||
.codicon-list-selection:before { content: "\eb85" }
|
||||
.codicon-selection:before { content: "\eb85" }
|
||||
.codicon-list-tree:before { content: "\eb86" }
|
||||
.codicon-debug-breakpoint-function-unverified:before { content: "\eb87" }
|
||||
.codicon-debug-breakpoint-function:before { content: "\eb88" }
|
||||
.codicon-debug-breakpoint-function-disabled:before { content: "\eb88" }
|
||||
.codicon-debug-breakpoint-stackframe-active:before { content: "\eb89" }
|
||||
.codicon-debug-breakpoint-stackframe-dot:before { content: "\eb8a" }
|
||||
.codicon-debug-breakpoint-stackframe:before { content: "\eb8b" }
|
||||
.codicon-debug-breakpoint-stackframe-focused:before { content: "\eb8b" }
|
||||
.codicon-debug-breakpoint-unsupported:before { content: "\eb8c" }
|
||||
.codicon-symbol-string:before { content: "\eb8d" }
|
||||
.codicon-debug-reverse-continue:before { content: "\eb8e" }
|
||||
.codicon-debug-step-back:before { content: "\eb8f" }
|
||||
.codicon-debug-restart-frame:before { content: "\eb90" }
|
||||
.codicon-debug-alternate:before { content: "\eb91" }
|
||||
.codicon-debug-alt:before { content: "\f101" }
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
|
||||
import 'vs/css!./contextview';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Range } from 'vs/base/common/range';
|
||||
import { BrowserFeatures } from 'vs/base/browser/canIUse';
|
||||
|
||||
export interface IAnchor {
|
||||
x: number;
|
||||
@@ -178,7 +180,7 @@ export class ContextView extends Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.delegate!.canRelayout === false) {
|
||||
if (this.delegate!.canRelayout === false && !(platform.isIOS && BrowserFeatures.pointerEvents)) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { $, append } from 'vs/base/browser/dom';
|
||||
import { format } from 'vs/base/common/strings';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { IThemable } from 'vs/base/common/styler';
|
||||
|
||||
export interface ICountBadgeOptions extends ICountBadgetyles {
|
||||
count?: number;
|
||||
@@ -26,7 +27,7 @@ const defaultOpts = {
|
||||
badgeForeground: Color.fromHex('#FFFFFF')
|
||||
};
|
||||
|
||||
export class CountBadge {
|
||||
export class CountBadge implements IThemable {
|
||||
|
||||
private element: HTMLElement;
|
||||
private count: number = 0;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.00001 8.70714L11.6465 12.3536L12.3536 11.6465L8.70711 8.00004L12.3536 4.35359L11.6465 3.64648L8.00001 7.29293L4.35356 3.64648L3.64645 4.35359L7.2929 8.00004L3.64645 11.6465L4.35356 12.3536L8.00001 8.70714Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 379 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.00001 8.70714L11.6465 12.3536L12.3536 11.6465L8.70711 8.00004L12.3536 4.35359L11.6465 3.64648L8.00001 7.29293L4.35356 3.64648L3.64645 4.35359L7.2929 8.00004L3.64645 11.6465L4.35356 12.3536L8.00001 8.70714Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 379 B |
@@ -27,12 +27,12 @@
|
||||
min-width: 500px;
|
||||
max-width: 90%;
|
||||
min-height: 75px;
|
||||
padding: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/** Dialog: Title Actions Row */
|
||||
.monaco-workbench .dialog-box .dialog-toolbar-row {
|
||||
padding-right: 1px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.monaco-workbench .dialog-box .action-label {
|
||||
@@ -46,73 +46,19 @@
|
||||
}
|
||||
|
||||
|
||||
.monaco-workbench .dialog-box .dialog-close-action {
|
||||
background: url('close-light.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .dialog-box .dialog-close-action,
|
||||
.hc-black .monaco-workbench .dialog-box .dialog-close-action {
|
||||
background: url('close-dark.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
/** Dialog: Message Row */
|
||||
.monaco-workbench .dialog-box .dialog-message-row {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
padding: 10px 15px 20px;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.monaco-workbench .dialog-box .dialog-message-row .dialog-icon {
|
||||
flex: 0 0 40px;
|
||||
height: 40px;
|
||||
.monaco-workbench .dialog-box .dialog-message-row > .codicon {
|
||||
flex: 0 0 48px;
|
||||
height: 48px;
|
||||
align-self: baseline;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 40px;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-pending {
|
||||
background-image: url('pending.svg');
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-info {
|
||||
background-image: url('info-light.svg');
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-warning {
|
||||
background-image: url('warning-light.svg');
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-error {
|
||||
background-image: url('error-light.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-pending {
|
||||
background-image: url('pending-dark.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-info,
|
||||
.hc-black .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-info {
|
||||
background-image: url('info-dark.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-warning,
|
||||
.hc-black .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-warning {
|
||||
background-image: url('warning-dark.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-error,
|
||||
.hc-black .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-error {
|
||||
background-image: url('error-dark.svg');
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-pending {
|
||||
background-image: url('pending-hc.svg');
|
||||
}
|
||||
|
||||
.monaco-workbench .dialog-box .dialog-message-row .dialog-icon.icon-pending {
|
||||
background-size: 30px;
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
/** Dialog: Message Container */
|
||||
@@ -121,8 +67,10 @@
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-left: 20px;
|
||||
padding-left: 24px;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
-ms-user-select: text;
|
||||
word-wrap: break-word; /* never overflow long words, but break to next line */
|
||||
white-space: normal;
|
||||
}
|
||||
@@ -134,7 +82,10 @@
|
||||
flex: 1; /* let the message always grow */
|
||||
white-space: normal;
|
||||
word-wrap: break-word; /* never overflow long words, but break to next line */
|
||||
padding-bottom: 10px;
|
||||
min-height: 48px; /* matches icon height */
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/** Dialog: Details */
|
||||
@@ -154,6 +105,13 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.monaco-workbench .dialog-box .dialog-message-row .dialog-message-container .dialog-checkbox-row .dialog-checkbox-message {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
/** Dialog: Buttons Row */
|
||||
.monaco-workbench .dialog-box > .dialog-buttons-row {
|
||||
display: flex;
|
||||
@@ -166,6 +124,7 @@
|
||||
.monaco-workbench .dialog-box > .dialog-buttons-row {
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
padding: 20px 10px 10px;
|
||||
}
|
||||
|
||||
/** Dialog: Buttons */
|
||||
@@ -175,7 +134,8 @@
|
||||
}
|
||||
|
||||
.monaco-workbench .dialog-box > .dialog-buttons-row > .dialog-buttons > .monaco-button {
|
||||
max-width: fit-content;
|
||||
width: fit-content;
|
||||
width: -moz-fit-content;
|
||||
padding: 5px 10px;
|
||||
margin: 4px 5px; /* allows button focus outline to be visible */
|
||||
overflow: hidden;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import 'vs/css!./dialog';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { $, hide, show, EventHelper, clearNode, removeClasses, addClass, removeNode, isAncestor } from 'vs/base/browser/dom';
|
||||
import { $, hide, show, EventHelper, clearNode, removeClasses, addClass, addClasses, removeNode, isAncestor, addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
@@ -75,7 +75,8 @@ export class Dialog extends Disposable {
|
||||
|
||||
if (this.options.detail) {
|
||||
const messageElement = messageContainer.appendChild($('.dialog-message'));
|
||||
messageElement.innerText = this.message;
|
||||
const messageTextElement = messageElement.appendChild($('.dialog-message-text'));
|
||||
messageTextElement.innerText = this.message;
|
||||
}
|
||||
|
||||
this.messageDetailElement = messageContainer.appendChild($('.dialog-message-detail'));
|
||||
@@ -84,12 +85,13 @@ export class Dialog extends Disposable {
|
||||
if (this.options.checkboxLabel) {
|
||||
const checkboxRowElement = messageContainer.appendChild($('.dialog-checkbox-row'));
|
||||
|
||||
this.checkbox = this._register(new SimpleCheckbox(this.options.checkboxLabel, !!this.options.checkboxChecked));
|
||||
const checkbox = this.checkbox = this._register(new SimpleCheckbox(this.options.checkboxLabel, !!this.options.checkboxChecked));
|
||||
|
||||
checkboxRowElement.appendChild(this.checkbox.domNode);
|
||||
checkboxRowElement.appendChild(checkbox.domNode);
|
||||
|
||||
const checkboxMessageElement = checkboxRowElement.appendChild($('.dialog-checkbox-message'));
|
||||
checkboxMessageElement.innerText = this.options.checkboxLabel;
|
||||
this._register(addDisposableListener(checkboxMessageElement, EventType.CLICK, () => checkbox.checked = !checkbox.checked));
|
||||
}
|
||||
|
||||
const toolbarRowElement = this.element.appendChild($('.dialog-toolbar-row'));
|
||||
@@ -198,29 +200,30 @@ export class Dialog extends Disposable {
|
||||
}
|
||||
}));
|
||||
|
||||
removeClasses(this.iconElement, 'icon-error', 'icon-warning', 'icon-info');
|
||||
addClass(this.iconElement, 'codicon');
|
||||
removeClasses(this.iconElement, 'codicon-alert', 'codicon-warning', 'codicon-info');
|
||||
|
||||
switch (this.options.type) {
|
||||
case 'error':
|
||||
addClass(this.iconElement, 'icon-error');
|
||||
addClass(this.iconElement, 'codicon-error');
|
||||
break;
|
||||
case 'warning':
|
||||
addClass(this.iconElement, 'icon-warning');
|
||||
addClass(this.iconElement, 'codicon-warning');
|
||||
break;
|
||||
case 'pending':
|
||||
addClass(this.iconElement, 'icon-pending');
|
||||
addClasses(this.iconElement, 'codicon-loading', 'codicon-animation-spin');
|
||||
break;
|
||||
case 'none':
|
||||
case 'info':
|
||||
case 'question':
|
||||
default:
|
||||
addClass(this.iconElement, 'icon-info');
|
||||
addClass(this.iconElement, 'codicon-info');
|
||||
break;
|
||||
}
|
||||
|
||||
const actionBar = new ActionBar(this.toolbarContainer, {});
|
||||
|
||||
const action = new Action('dialog.close', nls.localize('dialogClose', "Close Dialog"), 'dialog-close-action', true, () => {
|
||||
const action = new Action('dialog.close', nls.localize('dialogClose', "Close Dialog"), 'codicon codicon-close', true, () => {
|
||||
resolve({ button: this.options.cancelId || 0, checkboxChecked: this.checkbox ? this.checkbox.checked : undefined });
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.58318 2.02842C9.96435 2.16331 11.2561 2.77279 12.2383 3.75307C13.3643 4.87923 13.9978 6.40584 14 7.99829C14.0004 9.38617 13.5196 10.7313 12.6396 11.8045C11.7595 12.8778 10.5345 13.6127 9.17333 13.8841C7.81215 14.1556 6.39895 13.9467 5.1745 13.2931C3.95005 12.6394 2.99008 11.5815 2.45814 10.2995C1.92619 9.0175 1.85517 7.59072 2.25717 6.26222C2.65917 4.93373 3.50933 3.7857 4.66282 3.0137C5.8163 2.24171 7.20177 1.89351 8.58318 2.02842ZM8.68038 1.03316C10.292 1.19055 11.7993 1.90184 12.9453 3.04585C14.2587 4.35938 14.9976 6.14013 15 7.99764C15.0005 9.61695 14.4396 11.1864 13.4129 12.4385C12.3861 13.6907 10.9569 14.5482 9.36889 14.8648C7.78084 15.1815 6.13211 14.9378 4.70359 14.1752C3.27506 13.4127 2.1551 12.1784 1.53449 10.6828C0.913887 9.18708 0.831027 7.52251 1.30003 5.97259C1.76903 4.42268 2.76089 3.08331 4.10662 2.18265C5.45236 1.28199 7.06873 0.875761 8.68038 1.03316ZM5.52498 5L8.00004 7.47506L10.4751 5L11.1822 5.70711L8.70714 8.18217L11.1818 10.6569L10.4747 11.364L8.00004 8.88927L5.52535 11.364L4.81824 10.6569L7.29293 8.18217L4.81787 5.70711L5.52498 5Z" fill="#F48771"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.58318 2.02842C9.96435 2.16331 11.2561 2.77279 12.2383 3.75307C13.3643 4.87923 13.9978 6.40584 14 7.99829C14.0004 9.38617 13.5196 10.7313 12.6396 11.8045C11.7595 12.8778 10.5345 13.6127 9.17333 13.8841C7.81215 14.1556 6.39895 13.9467 5.1745 13.2931C3.95005 12.6394 2.99008 11.5815 2.45814 10.2995C1.92619 9.0175 1.85517 7.59072 2.25717 6.26222C2.65917 4.93373 3.50933 3.7857 4.66282 3.0137C5.8163 2.24171 7.20177 1.89351 8.58318 2.02842ZM8.68038 1.03316C10.292 1.19055 11.7993 1.90184 12.9453 3.04585C14.2587 4.35938 14.9976 6.14013 15 7.99764C15.0005 9.61695 14.4396 11.1864 13.4129 12.4385C12.3861 13.6907 10.9569 14.5482 9.36889 14.8648C7.78084 15.1815 6.13211 14.9378 4.70359 14.1752C3.27506 13.4127 2.1551 12.1784 1.53449 10.6828C0.913887 9.18708 0.831027 7.52251 1.30003 5.97259C1.76903 4.42268 2.76089 3.08331 4.10662 2.18265C5.45236 1.28199 7.06873 0.875761 8.68038 1.03316ZM5.52498 5L8.00004 7.47506L10.4751 5L11.1822 5.70711L8.70714 8.18217L11.1818 10.6569L10.4747 11.364L8.00004 8.88927L5.52535 11.364L4.81824 10.6569L7.29293 8.18217L4.81787 5.70711L5.52498 5Z" fill="#A1260D"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 7.5C3 4.46243 5.46243 2 8.5 2C11.5376 2 14 4.46243 14 7.5C14 10.5376 11.5376 13 8.5 13C5.46243 13 3 10.5376 3 7.5ZM2 7.5C2 3.91015 4.91015 1 8.5 1C12.0899 1 15 3.91015 15 7.5C15 11.0899 12.0899 14 8.5 14C4.91015 14 2 11.0899 2 7.5ZM8 4V5H9V4H8ZM8 6L8 10H9L9 6H8Z" fill="#75BEFF"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 436 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 7.5C3 4.46243 5.46243 2 8.5 2C11.5376 2 14 4.46243 14 7.5C14 10.5376 11.5376 13 8.5 13C5.46243 13 3 10.5376 3 7.5ZM2 7.5C2 3.91015 4.91015 1 8.5 1C12.0899 1 15 3.91015 15 7.5C15 11.0899 12.0899 14 8.5 14C4.91015 14 2 11.0899 2 7.5ZM8 4V5H9V4H8ZM8 6L8 10H9L9 6H8Z" fill="#007ACC"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 7.5C3 4.46243 5.46243 2 8.5 2C11.5376 2 14 4.46243 14 7.5C14 10.5376 11.5376 13 8.5 13C5.46243 13 3 10.5376 3 7.5ZM2 7.5C2 3.91015 4.91015 1 8.5 1C12.0899 1 15 3.91015 15 7.5C15 11.0899 12.0899 14 8.5 14C4.91015 14 2 11.0899 2 7.5ZM8 4V5H9V4H8ZM8 6L8 10H9L9 6H8Z" fill="#007ACC"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 769 B |
@@ -1,31 +0,0 @@
|
||||
<?xml version='1.0' standalone='no' ?>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
|
||||
<style>
|
||||
circle {
|
||||
animation: ball 1.04s steps(8) infinite;
|
||||
}
|
||||
|
||||
circle:nth-child(2) { animation-delay: 0.13s; }
|
||||
circle:nth-child(3) { animation-delay: 0.26s; }
|
||||
circle:nth-child(4) { animation-delay: 0.39s; }
|
||||
circle:nth-child(5) { animation-delay: 0.52s; }
|
||||
circle:nth-child(6) { animation-delay: 0.65s; }
|
||||
circle:nth-child(7) { animation-delay: 0.78s; }
|
||||
circle:nth-child(8) { animation-delay: 0.91s; }
|
||||
|
||||
@keyframes ball {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0.3; }
|
||||
}
|
||||
</style>
|
||||
<g style="fill:grey;">
|
||||
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,31 +0,0 @@
|
||||
<?xml version='1.0' standalone='no' ?>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
|
||||
<style>
|
||||
circle {
|
||||
animation: ball 1.04s steps(8) infinite;
|
||||
}
|
||||
|
||||
circle:nth-child(2) { animation-delay: 0.13s; }
|
||||
circle:nth-child(3) { animation-delay: 0.26s; }
|
||||
circle:nth-child(4) { animation-delay: 0.39s; }
|
||||
circle:nth-child(5) { animation-delay: 0.52s; }
|
||||
circle:nth-child(6) { animation-delay: 0.65s; }
|
||||
circle:nth-child(7) { animation-delay: 0.78s; }
|
||||
circle:nth-child(8) { animation-delay: 0.91s; }
|
||||
|
||||
@keyframes ball {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0.3; }
|
||||
}
|
||||
</style>
|
||||
<g style="fill:white;">
|
||||
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,31 +0,0 @@
|
||||
<?xml version='1.0' standalone='no' ?>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
|
||||
<style>
|
||||
circle {
|
||||
animation: ball 1.04s steps(8) infinite;
|
||||
}
|
||||
|
||||
circle:nth-child(2) { animation-delay: 0.13s; }
|
||||
circle:nth-child(3) { animation-delay: 0.26s; }
|
||||
circle:nth-child(4) { animation-delay: 0.39s; }
|
||||
circle:nth-child(5) { animation-delay: 0.52s; }
|
||||
circle:nth-child(6) { animation-delay: 0.65s; }
|
||||
circle:nth-child(7) { animation-delay: 0.78s; }
|
||||
circle:nth-child(8) { animation-delay: 0.91s; }
|
||||
|
||||
@keyframes ball {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0.3; }
|
||||
}
|
||||
</style>
|
||||
<g>
|
||||
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.12 13.9725L15 12.5L9.37927 2H7.61924L1.9985 12.5L2.87852 13.9725H14.12ZM2.87852 12.9725L8.49925 2.47249L14.12 12.9725H2.87852ZM7.98952 6H8.98802V10H7.98952V6ZM7.98952 11H8.98802V12H7.98952V11Z" fill="#FFCC00"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 367 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.12 13.9725L15 12.5L9.37927 2H7.61924L1.9985 12.5L2.87852 13.9725H14.12ZM2.87852 12.9725L8.49925 2.47249L14.12 12.9725H2.87852ZM7.98952 6H8.98802V10H7.98952V6ZM7.98952 11H8.98802V12H7.98952V11Z" fill="#FFCC00"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.12 13.9725L15 12.5L9.37927 2H7.61924L1.9985 12.5L2.87852 13.9725H14.12ZM2.87852 12.9725L8.49925 2.47249L14.12 12.9725H2.87852ZM7.98952 6H8.98802V10H7.98952V6ZM7.98952 11H8.98802V12H7.98952V11Z" fill="#DDB100"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 631 B |
@@ -5,12 +5,10 @@
|
||||
|
||||
.monaco-dropdown {
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.monaco-dropdown > .dropdown-label {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -396,30 +396,18 @@ export class FindInput extends Widget {
|
||||
}
|
||||
|
||||
public validate(): void {
|
||||
if (this.inputBox) {
|
||||
this.inputBox.validate();
|
||||
}
|
||||
this.inputBox.validate();
|
||||
}
|
||||
|
||||
public showMessage(message: InputBoxMessage): void {
|
||||
if (this.inputBox) {
|
||||
this.inputBox.showMessage(message);
|
||||
}
|
||||
this.inputBox.showMessage(message);
|
||||
}
|
||||
|
||||
public clearMessage(): void {
|
||||
if (this.inputBox) {
|
||||
this.inputBox.hideMessage();
|
||||
}
|
||||
this.inputBox.hideMessage();
|
||||
}
|
||||
|
||||
private clearValidation(): void {
|
||||
if (this.inputBox) {
|
||||
this.inputBox.hideMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this.inputBox.hideMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,7 +605,7 @@ export type GridNodeDescriptor = { size?: number, groups?: GridNodeDescriptor[]
|
||||
export type GridDescriptor = { orientation: Orientation, groups?: GridNodeDescriptor[] };
|
||||
|
||||
export function sanitizeGridNodeDescriptor(nodeDescriptor: GridNodeDescriptor): void {
|
||||
if (nodeDescriptor.groups && nodeDescriptor.groups.length === 0) {
|
||||
if (nodeDescriptor.groups && nodeDescriptor.groups.length <= 1) {
|
||||
nodeDescriptor.groups = undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export interface IView {
|
||||
readonly onDidChange: Event<IViewSize | undefined>;
|
||||
readonly priority?: LayoutPriority;
|
||||
readonly snap?: boolean;
|
||||
layout(width: number, height: number, orientation: Orientation): void;
|
||||
layout(width: number, height: number, top: number, left: number): void;
|
||||
setVisible?(visible: boolean): void;
|
||||
}
|
||||
|
||||
@@ -69,10 +69,10 @@ export function orthogonal(orientation: Orientation): Orientation {
|
||||
}
|
||||
|
||||
export interface Box {
|
||||
top: number;
|
||||
left: number;
|
||||
width: number;
|
||||
height: number;
|
||||
readonly top: number;
|
||||
readonly left: number;
|
||||
readonly width: number;
|
||||
readonly height: number;
|
||||
}
|
||||
|
||||
export interface GridLeafNode {
|
||||
@@ -117,11 +117,19 @@ export interface IGridViewOptions {
|
||||
readonly layoutController?: ILayoutController;
|
||||
}
|
||||
|
||||
class BranchNode implements ISplitView, IDisposable {
|
||||
interface ILayoutContext {
|
||||
readonly orthogonalSize: number;
|
||||
readonly absoluteOffset: number;
|
||||
readonly absoluteOrthogonalOffset: number;
|
||||
readonly absoluteSize: number;
|
||||
readonly absoluteOrthogonalSize: number;
|
||||
}
|
||||
|
||||
class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
|
||||
readonly element: HTMLElement;
|
||||
readonly children: Node[] = [];
|
||||
private splitview: SplitView;
|
||||
private splitview: SplitView<ILayoutContext>;
|
||||
|
||||
private _size: number;
|
||||
get size(): number { return this._size; }
|
||||
@@ -129,6 +137,9 @@ class BranchNode implements ISplitView, IDisposable {
|
||||
private _orthogonalSize: number;
|
||||
get orthogonalSize(): number { return this._orthogonalSize; }
|
||||
|
||||
private absoluteOffset: number = 0;
|
||||
private absoluteOrthogonalOffset: number = 0;
|
||||
|
||||
private _styles: IGridViewStyles;
|
||||
get styles(): IGridViewStyles { return this._styles; }
|
||||
|
||||
@@ -140,6 +151,14 @@ class BranchNode implements ISplitView, IDisposable {
|
||||
return this.orientation === Orientation.HORIZONTAL ? this.orthogonalSize : this.size;
|
||||
}
|
||||
|
||||
get top(): number {
|
||||
return this.orientation === Orientation.HORIZONTAL ? this.absoluteOffset : this.absoluteOrthogonalOffset;
|
||||
}
|
||||
|
||||
get left(): number {
|
||||
return this.orientation === Orientation.HORIZONTAL ? this.absoluteOrthogonalOffset : this.absoluteOffset;
|
||||
}
|
||||
|
||||
get minimumSize(): number {
|
||||
return this.children.length === 0 ? 0 : Math.max(...this.children.map(c => c.minimumOrthogonalSize));
|
||||
}
|
||||
@@ -221,7 +240,7 @@ class BranchNode implements ISplitView, IDisposable {
|
||||
if (!childDescriptors) {
|
||||
// Normal behavior, we have no children yet, just set up the splitview
|
||||
this.splitview = new SplitView(this.element, { orientation, styles, proportionalLayout });
|
||||
this.splitview.layout(size, orthogonalSize);
|
||||
this.splitview.layout(size, { orthogonalSize, absoluteOffset: 0, absoluteOrthogonalOffset: 0, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize });
|
||||
} else {
|
||||
// Reconstruction behavior, we want to reconstruct a splitview
|
||||
const descriptor = {
|
||||
@@ -268,20 +287,32 @@ class BranchNode implements ISplitView, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
layout(size: number, orthogonalSize: number | undefined): void {
|
||||
layout(size: number, offset: number, ctx: ILayoutContext | undefined): void {
|
||||
if (!this.layoutController.isLayoutEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof orthogonalSize !== 'number') {
|
||||
if (typeof ctx === 'undefined') {
|
||||
throw new Error('Invalid state');
|
||||
}
|
||||
|
||||
// branch nodes should flip the normal/orthogonal directions
|
||||
this._size = orthogonalSize;
|
||||
this._size = ctx.orthogonalSize;
|
||||
this._orthogonalSize = size;
|
||||
this.absoluteOffset = ctx.absoluteOffset + offset;
|
||||
this.absoluteOrthogonalOffset = ctx.absoluteOrthogonalOffset;
|
||||
|
||||
this.splitview.layout(orthogonalSize, size);
|
||||
this.splitview.layout(ctx.orthogonalSize, {
|
||||
orthogonalSize: size,
|
||||
absoluteOffset: this.absoluteOrthogonalOffset,
|
||||
absoluteOrthogonalOffset: this.absoluteOffset,
|
||||
absoluteSize: ctx.absoluteOrthogonalSize,
|
||||
absoluteOrthogonalSize: ctx.absoluteSize
|
||||
});
|
||||
|
||||
// Disable snapping on views which sit on the edges of the grid
|
||||
this.splitview.startSnappingEnabled = this.absoluteOrthogonalOffset > 0;
|
||||
this.splitview.endSnappingEnabled = this.absoluteOrthogonalOffset + ctx.orthogonalSize < ctx.absoluteOrthogonalSize;
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): void {
|
||||
@@ -511,7 +542,7 @@ class BranchNode implements ISplitView, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
class LeafNode implements ISplitView, IDisposable {
|
||||
class LeafNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
|
||||
private _size: number = 0;
|
||||
get size(): number { return this._size; }
|
||||
@@ -519,6 +550,9 @@ class LeafNode implements ISplitView, IDisposable {
|
||||
private _orthogonalSize: number;
|
||||
get orthogonalSize(): number { return this._orthogonalSize; }
|
||||
|
||||
private absoluteOffset: number = 0;
|
||||
private absoluteOrthogonalOffset: number = 0;
|
||||
|
||||
readonly onDidSashReset: Event<number[]> = Event.None;
|
||||
|
||||
private _onDidLinkedWidthNodeChange = new Relay<number | undefined>();
|
||||
@@ -565,6 +599,14 @@ class LeafNode implements ISplitView, IDisposable {
|
||||
return this.orientation === Orientation.HORIZONTAL ? this.size : this.orthogonalSize;
|
||||
}
|
||||
|
||||
get top(): number {
|
||||
return this.orientation === Orientation.HORIZONTAL ? this.absoluteOffset : this.absoluteOrthogonalOffset;
|
||||
}
|
||||
|
||||
get left(): number {
|
||||
return this.orientation === Orientation.HORIZONTAL ? this.absoluteOrthogonalOffset : this.absoluteOffset;
|
||||
}
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this.view.element;
|
||||
}
|
||||
@@ -617,18 +659,20 @@ class LeafNode implements ISplitView, IDisposable {
|
||||
// noop
|
||||
}
|
||||
|
||||
layout(size: number, orthogonalSize: number | undefined): void {
|
||||
layout(size: number, offset: number, ctx: ILayoutContext | undefined): void {
|
||||
if (!this.layoutController.isLayoutEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof orthogonalSize !== 'number') {
|
||||
if (typeof ctx === 'undefined') {
|
||||
throw new Error('Invalid state');
|
||||
}
|
||||
|
||||
this._size = size;
|
||||
this._orthogonalSize = orthogonalSize;
|
||||
this.view.layout(this.width, this.height, orthogonal(this.orientation));
|
||||
this._orthogonalSize = ctx.orthogonalSize;
|
||||
this.absoluteOffset = ctx.absoluteOffset + offset;
|
||||
this.absoluteOrthogonalOffset = ctx.absoluteOrthogonalOffset;
|
||||
this.view.layout(this.width, this.height, this.top, this.left);
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): void {
|
||||
@@ -715,7 +759,7 @@ export class GridView implements IDisposable {
|
||||
|
||||
const { size, orthogonalSize } = this._root;
|
||||
this.root = flipNode(this._root, orthogonalSize, size);
|
||||
this.root.layout(size, orthogonalSize);
|
||||
this.root.layout(size, 0, { orthogonalSize, absoluteOffset: 0, absoluteOrthogonalOffset: 0, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize });
|
||||
}
|
||||
|
||||
get width(): number { return this.root.width; }
|
||||
@@ -771,7 +815,7 @@ export class GridView implements IDisposable {
|
||||
this.firstLayoutController.isLayoutEnabled = true;
|
||||
|
||||
const [size, orthogonalSize] = this.root.orientation === Orientation.HORIZONTAL ? [height, width] : [width, height];
|
||||
this.root.layout(size, orthogonalSize);
|
||||
this.root.layout(size, 0, { orthogonalSize, absoluteOffset: 0, absoluteOrthogonalOffset: 0, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize });
|
||||
}
|
||||
|
||||
addView(view: IView, size: number | Sizing, location: number[]): void {
|
||||
@@ -1032,7 +1076,7 @@ export class GridView implements IDisposable {
|
||||
getView(location?: number[]): GridNode;
|
||||
getView(location?: number[]): GridNode {
|
||||
const node = location ? this.getNode(location)[1] : this._root;
|
||||
return this._getViews(node, this.orientation, { top: 0, left: 0, width: this.width, height: this.height });
|
||||
return this._getViews(node, this.orientation);
|
||||
}
|
||||
|
||||
static deserialize<T extends ISerializableView>(json: ISerializedGridView, deserializer: IViewDeserializer<T>, options: IGridViewOptions = {}): GridView {
|
||||
@@ -1076,24 +1120,20 @@ export class GridView implements IDisposable {
|
||||
return result;
|
||||
}
|
||||
|
||||
private _getViews(node: Node, orientation: Orientation, box: Box, cachedVisibleSize?: number): GridNode {
|
||||
private _getViews(node: Node, orientation: Orientation, cachedVisibleSize?: number): GridNode {
|
||||
const box = { top: node.top, left: node.left, width: node.width, height: node.height };
|
||||
|
||||
if (node instanceof LeafNode) {
|
||||
return { view: node.view, box, cachedVisibleSize };
|
||||
}
|
||||
|
||||
const children: GridNode[] = [];
|
||||
let i = 0;
|
||||
let offset = 0;
|
||||
|
||||
for (const child of node.children) {
|
||||
const childOrientation = orthogonal(orientation);
|
||||
const childBox: Box = orientation === Orientation.HORIZONTAL
|
||||
? { top: box.top, left: box.left + offset, width: child.width, height: box.height }
|
||||
: { top: box.top + offset, left: box.left, width: box.width, height: child.height };
|
||||
const cachedVisibleSize = node.getChildCachedVisibleSize(i++);
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
const child = node.children[i];
|
||||
const cachedVisibleSize = node.getChildCachedVisibleSize(i);
|
||||
|
||||
children.push(this._getViews(child, childOrientation, childBox, cachedVisibleSize));
|
||||
offset += orientation === Orientation.HORIZONTAL ? child.width : child.height;
|
||||
children.push(this._getViews(child, orthogonal(orientation), cachedVisibleSize));
|
||||
}
|
||||
|
||||
return { children, box };
|
||||
|
||||
@@ -84,7 +84,11 @@ export class HighlightedLabel {
|
||||
}
|
||||
|
||||
this.domNode.innerHTML = htmlContent;
|
||||
this.domNode.title = this.title;
|
||||
if (this.title) {
|
||||
this.domNode.title = this.title;
|
||||
} else {
|
||||
this.domNode.removeAttribute('title');
|
||||
}
|
||||
this.didEverRender = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import * as dom from 'vs/base/browser/dom';
|
||||
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
|
||||
import { IMatch } from 'vs/base/common/filters';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Range } from 'vs/base/common/range';
|
||||
|
||||
export interface IIconLabelCreationOptions {
|
||||
supportHighlights?: boolean;
|
||||
@@ -24,6 +25,8 @@ export interface IIconLabelValueOptions {
|
||||
matches?: IMatch[];
|
||||
labelEscapeNewLines?: boolean;
|
||||
descriptionMatches?: IMatch[];
|
||||
readonly separator?: string;
|
||||
readonly domId?: string;
|
||||
}
|
||||
|
||||
class FastLabelNode {
|
||||
@@ -86,9 +89,10 @@ class FastLabelNode {
|
||||
}
|
||||
|
||||
export class IconLabel extends Disposable {
|
||||
|
||||
private domNode: FastLabelNode;
|
||||
private labelDescriptionContainer: FastLabelNode;
|
||||
private labelNode: FastLabelNode | HighlightedLabel;
|
||||
private descriptionContainer: FastLabelNode;
|
||||
private nameNode: Label | LabelWithHighlights;
|
||||
private descriptionNode: FastLabelNode | HighlightedLabel | undefined;
|
||||
private descriptionNodeFactory: () => FastLabelNode | HighlightedLabel;
|
||||
|
||||
@@ -97,18 +101,21 @@ export class IconLabel extends Disposable {
|
||||
|
||||
this.domNode = this._register(new FastLabelNode(dom.append(container, dom.$('.monaco-icon-label'))));
|
||||
|
||||
this.labelDescriptionContainer = this._register(new FastLabelNode(dom.append(this.domNode.element, dom.$('.monaco-icon-label-description-container'))));
|
||||
const labelContainer = dom.append(this.domNode.element, dom.$('.monaco-icon-label-container'));
|
||||
|
||||
const nameContainer = dom.append(labelContainer, dom.$('span.monaco-icon-name-container'));
|
||||
this.descriptionContainer = this._register(new FastLabelNode(dom.append(labelContainer, dom.$('span.monaco-icon-description-container'))));
|
||||
|
||||
if (options?.supportHighlights) {
|
||||
this.labelNode = new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')), !!options.supportCodicons);
|
||||
this.nameNode = new LabelWithHighlights(nameContainer, !!options.supportCodicons);
|
||||
} else {
|
||||
this.labelNode = this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name'))));
|
||||
this.nameNode = new Label(nameContainer);
|
||||
}
|
||||
|
||||
if (options?.supportDescriptionHighlights) {
|
||||
this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')), !!options.supportCodicons);
|
||||
this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.descriptionContainer.element, dom.$('span.label-description')), !!options.supportCodicons);
|
||||
} else {
|
||||
this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description'))));
|
||||
this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.descriptionContainer.element, dom.$('span.label-description'))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +123,7 @@ export class IconLabel extends Disposable {
|
||||
return this.domNode.element;
|
||||
}
|
||||
|
||||
setLabel(label?: string, description?: string, options?: IIconLabelValueOptions): void {
|
||||
setLabel(label: string | string[], description?: string, options?: IIconLabelValueOptions): void {
|
||||
const classes = ['monaco-icon-label'];
|
||||
if (options) {
|
||||
if (options.extraClasses) {
|
||||
@@ -131,11 +138,7 @@ export class IconLabel extends Disposable {
|
||||
this.domNode.className = classes.join(' ');
|
||||
this.domNode.title = options?.title || '';
|
||||
|
||||
if (this.labelNode instanceof HighlightedLabel) {
|
||||
this.labelNode.set(label || '', options?.matches, options?.title, options?.labelEscapeNewLines);
|
||||
} else {
|
||||
this.labelNode.textContent = label || '';
|
||||
}
|
||||
this.nameNode.setLabel(label, options);
|
||||
|
||||
if (description || this.descriptionNode) {
|
||||
if (!this.descriptionNode) {
|
||||
@@ -157,3 +160,112 @@ export class IconLabel extends Disposable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Label {
|
||||
|
||||
private label: string | string[] | undefined = undefined;
|
||||
private singleLabel: HTMLElement | undefined = undefined;
|
||||
|
||||
constructor(private container: HTMLElement) { }
|
||||
|
||||
setLabel(label: string | string[], options?: IIconLabelValueOptions): void {
|
||||
if (this.label === label) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.label = label;
|
||||
|
||||
if (typeof label === 'string') {
|
||||
if (!this.singleLabel) {
|
||||
this.container.innerHTML = '';
|
||||
dom.removeClass(this.container, 'multiple');
|
||||
this.singleLabel = dom.append(this.container, dom.$('a.label-name', { id: options?.domId }));
|
||||
}
|
||||
|
||||
this.singleLabel.textContent = label;
|
||||
} else {
|
||||
this.container.innerHTML = '';
|
||||
dom.addClass(this.container, 'multiple');
|
||||
this.singleLabel = undefined;
|
||||
|
||||
for (let i = 0; i < label.length; i++) {
|
||||
const l = label[i];
|
||||
const id = options?.domId && `${options?.domId}_${i}`;
|
||||
|
||||
dom.append(this.container, dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i }, l));
|
||||
|
||||
if (i < label.length - 1) {
|
||||
dom.append(this.container, dom.$('span.label-separator', undefined, options?.separator || '/'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function splitMatches(labels: string[], separator: string, matches: IMatch[] | undefined): IMatch[][] | undefined {
|
||||
if (!matches) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let labelStart = 0;
|
||||
|
||||
return labels.map(label => {
|
||||
const labelRange = { start: labelStart, end: labelStart + label.length };
|
||||
|
||||
const result = matches
|
||||
.map(match => Range.intersect(labelRange, match))
|
||||
.filter(range => !Range.isEmpty(range))
|
||||
.map(({ start, end }) => ({ start: start - labelStart, end: end - labelStart }));
|
||||
|
||||
labelStart = labelRange.end + separator.length;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
class LabelWithHighlights {
|
||||
|
||||
private label: string | string[] | undefined = undefined;
|
||||
private singleLabel: HighlightedLabel | undefined = undefined;
|
||||
|
||||
constructor(private container: HTMLElement, private supportCodicons: boolean) { }
|
||||
|
||||
setLabel(label: string | string[], options?: IIconLabelValueOptions): void {
|
||||
if (this.label === label) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.label = label;
|
||||
|
||||
if (typeof label === 'string') {
|
||||
if (!this.singleLabel) {
|
||||
this.container.innerHTML = '';
|
||||
dom.removeClass(this.container, 'multiple');
|
||||
this.singleLabel = new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })), this.supportCodicons);
|
||||
}
|
||||
|
||||
this.singleLabel.set(label, options?.matches, options?.title, options?.labelEscapeNewLines);
|
||||
} else {
|
||||
|
||||
this.container.innerHTML = '';
|
||||
dom.addClass(this.container, 'multiple');
|
||||
this.singleLabel = undefined;
|
||||
|
||||
const separator = options?.separator || '/';
|
||||
const matches = splitMatches(label, separator, options?.matches);
|
||||
|
||||
for (let i = 0; i < label.length; i++) {
|
||||
const l = label[i];
|
||||
const m = matches ? matches[i] : undefined;
|
||||
const id = options?.domId && `${options?.domId}_${i}`;
|
||||
|
||||
const name = dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i });
|
||||
const highlightedLabel = new HighlightedLabel(dom.append(this.container, name), this.supportCodicons);
|
||||
highlightedLabel.set(l, m, options?.title, options?.labelEscapeNewLines);
|
||||
|
||||
if (i < label.length - 1) {
|
||||
dom.append(name, dom.$('span.label-separator', undefined, separator));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,30 +25,38 @@
|
||||
|
||||
/* fonts icons */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
vertical-align: top;
|
||||
|
||||
flex-shrink: 0; /* fix for https://github.com/Microsoft/vscode/issues/13787 */
|
||||
}
|
||||
|
||||
.monaco-icon-label > .monaco-icon-label-description-container {
|
||||
overflow: hidden; /* this causes the label/description to shrink first if decorations are enabled */
|
||||
.monaco-icon-label > .monaco-icon-label-container {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.monaco-icon-label > .monaco-icon-label-description-container > .label-name {
|
||||
.monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name {
|
||||
color: inherit;
|
||||
white-space: pre; /* enable to show labels that include multiple whitespaces */
|
||||
}
|
||||
|
||||
.monaco-icon-label > .monaco-icon-label-description-container > .label-description {
|
||||
.monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name > .label-separator {
|
||||
margin: 0 2px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.monaco-icon-label > .monaco-icon-label-container > .monaco-icon-description-container > .label-description {
|
||||
opacity: .7;
|
||||
margin-left: 0.5em;
|
||||
font-size: 0.9em;
|
||||
white-space: pre; /* enable to show labels that include multiple whitespaces */
|
||||
}
|
||||
|
||||
.monaco-icon-label.italic > .monaco-icon-label-description-container > .label-name,
|
||||
.monaco-icon-label.italic > .monaco-icon-label-description-container > .label-description {
|
||||
.monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-name-container > .label-name,
|
||||
.monaco-icon-label.italic > .monaco-icon-description-container > .label-description {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -57,7 +65,6 @@
|
||||
font-size: 90%;
|
||||
font-weight: 600;
|
||||
padding: 0 16px 0 5px;
|
||||
margin-left: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,7 @@
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
line-height: auto !important;
|
||||
box-sizing: border-box;
|
||||
|
||||
/* Customizable */
|
||||
font-size: inherit;
|
||||
@@ -37,11 +32,7 @@
|
||||
|
||||
.monaco-inputbox > .wrapper > .input {
|
||||
display: inline-block;
|
||||
-webkit-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: inherit;
|
||||
@@ -58,31 +49,26 @@
|
||||
|
||||
.monaco-inputbox > .wrapper > textarea.input {
|
||||
display: block;
|
||||
-ms-overflow-style: none; /* IE 10+ */
|
||||
overflow: -moz-scrollbars-none; /* Firefox */
|
||||
scrollbar-width: none; /* Firefox ^64 */
|
||||
-ms-overflow-style: none; /* IE 10+: hide scrollbars */
|
||||
scrollbar-width: none; /* Firefox: hide scrollbars */
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar {
|
||||
display: none; /* Chrome + Safari: hide scrollbar */
|
||||
}
|
||||
|
||||
.monaco-inputbox > .wrapper > textarea.input.empty {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-inputbox > .wrapper > .mirror {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
white-space: pre-wrap;
|
||||
visibility: hidden;
|
||||
word-wrap: break-word;
|
||||
@@ -99,11 +85,7 @@
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
-webkit-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
padding: 0.4em;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
|
||||
@@ -183,7 +183,7 @@ export class InputBox extends Widget {
|
||||
this.maxHeight = typeof this.options.flexibleMaxHeight === 'number' ? this.options.flexibleMaxHeight : Number.POSITIVE_INFINITY;
|
||||
|
||||
this.mirror = dom.append(wrapper, $('div.mirror'));
|
||||
this.mirror.innerHTML = ' ';
|
||||
this.mirror.innerHTML = ' ';
|
||||
|
||||
this.scrollableElement = new ScrollableElement(this.element, { vertical: ScrollbarVisibility.Auto });
|
||||
|
||||
@@ -242,6 +242,8 @@ export class InputBox extends Widget {
|
||||
});
|
||||
}
|
||||
|
||||
this.ignoreGesture(this.input);
|
||||
|
||||
setTimeout(() => this.updateMirror(), 0);
|
||||
|
||||
// Support actions
|
||||
@@ -327,6 +329,7 @@ export class InputBox extends Widget {
|
||||
}
|
||||
|
||||
public disable(): void {
|
||||
this.blur();
|
||||
this.input.disabled = true;
|
||||
this._hideMessage();
|
||||
}
|
||||
@@ -561,7 +564,7 @@ export class InputBox extends Widget {
|
||||
if (mirrorTextContent) {
|
||||
this.mirror.textContent = value + suffix;
|
||||
} else {
|
||||
this.mirror.innerHTML = ' ';
|
||||
this.mirror.innerHTML = ' ';
|
||||
}
|
||||
|
||||
this.layout();
|
||||
|
||||
@@ -11,12 +11,9 @@
|
||||
}
|
||||
|
||||
.monaco-list.mouse-support {
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: -moz-none;
|
||||
-ms-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.monaco-list > .monaco-scrollable-element {
|
||||
@@ -36,10 +33,7 @@
|
||||
|
||||
.monaco-list-row {
|
||||
position: absolute;
|
||||
-moz-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -59,6 +53,10 @@
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
.monaco-list:focus .monaco-list-row.selected .codicon {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Dnd */
|
||||
.monaco-drag-image {
|
||||
display: inline-block;
|
||||
@@ -115,54 +113,28 @@
|
||||
}
|
||||
|
||||
.monaco-list-type-filter > .controls > * {
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
background: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 0 0 2px;
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter > .controls > .filter:checked::before {
|
||||
content: "\eb83" !important; /* codicon-list-filter */
|
||||
}
|
||||
|
||||
.monaco-list-type-filter > .controls > .filter {
|
||||
-webkit-appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url("media/no-filter-light.svg");
|
||||
background-position: 50% 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter > .controls > .filter:checked {
|
||||
background-image: url("media/filter-light.svg");
|
||||
}
|
||||
|
||||
.vs-dark .monaco-list-type-filter > .controls > .filter {
|
||||
background-image: url("media/no-filter-dark.svg");
|
||||
}
|
||||
|
||||
.vs-dark .monaco-list-type-filter > .controls > .filter:checked {
|
||||
background-image: url("media/filter-dark.svg");
|
||||
}
|
||||
|
||||
.hc-black .monaco-list-type-filter > .controls > .filter {
|
||||
background-image: url("media/no-filter-hc.svg");
|
||||
}
|
||||
|
||||
.hc-black .monaco-list-type-filter > .controls > .filter:checked {
|
||||
background-image: url("media/filter-hc.svg");
|
||||
}
|
||||
|
||||
.monaco-list-type-filter > .controls > .clear {
|
||||
border: none;
|
||||
background: url("media/close-light.svg");
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-list-type-filter > .controls > .clear {
|
||||
background-image: url("media/close-dark.svg");
|
||||
}
|
||||
|
||||
.hc-black .monaco-list-type-filter > .controls > .clear {
|
||||
background-image: url("media/close-hc.svg");
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter-message {
|
||||
@@ -191,4 +163,4 @@
|
||||
|
||||
.monaco-list-type-filter.dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,10 +103,11 @@ export const ListDragOverReactions = {
|
||||
|
||||
export interface IListDragAndDrop<T> {
|
||||
getDragURI(element: T): string | null;
|
||||
getDragLabel?(elements: T[]): string | undefined;
|
||||
getDragLabel?(elements: T[], originalEvent: DragEvent): string | undefined;
|
||||
onDragStart?(data: IDragAndDropData, originalEvent: DragEvent): void;
|
||||
onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction;
|
||||
drop(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void;
|
||||
onDragEnd?(originalEvent: DragEvent): void;
|
||||
}
|
||||
|
||||
export class ListError extends Error {
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Range, IRange } from 'vs/base/common/range';
|
||||
import { equals, distinct } from 'vs/base/common/arrays';
|
||||
import { DataTransfers, StaticDND, IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
import { disposableTimeout, Delayer } from 'vs/base/common/async';
|
||||
import { isFirefox } from 'vs/base/browser/browser';
|
||||
|
||||
interface IItem<T> {
|
||||
readonly id: string;
|
||||
@@ -73,9 +74,10 @@ const DefaultOptions = {
|
||||
horizontalScrolling: false
|
||||
};
|
||||
|
||||
export class ElementsDragAndDropData<T> implements IDragAndDropData {
|
||||
export class ElementsDragAndDropData<T, TContext = void> implements IDragAndDropData {
|
||||
|
||||
readonly elements: T[];
|
||||
context: TContext | undefined;
|
||||
|
||||
constructor(elements: T[]) {
|
||||
this.elements = elements;
|
||||
@@ -83,7 +85,7 @@ export class ElementsDragAndDropData<T> implements IDragAndDropData {
|
||||
|
||||
update(): void { }
|
||||
|
||||
getData(): any {
|
||||
getData(): T[] {
|
||||
return this.elements;
|
||||
}
|
||||
}
|
||||
@@ -98,7 +100,7 @@ export class ExternalElementsDragAndDropData<T> implements IDragAndDropData {
|
||||
|
||||
update(): void { }
|
||||
|
||||
getData(): any {
|
||||
getData(): T[] {
|
||||
return this.elements;
|
||||
}
|
||||
}
|
||||
@@ -233,7 +235,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
this.rowsContainer = document.createElement('div');
|
||||
this.rowsContainer.className = 'monaco-list-rows';
|
||||
this.rowsContainer.style.willChange = 'transform';
|
||||
this.rowsContainer.style.transform = 'translate3d(0px, 0px, 0px)';
|
||||
this.disposables.add(Gesture.addTarget(this.rowsContainer));
|
||||
|
||||
this.scrollableElement = this.disposables.add(new ScrollableElement(this.rowsContainer, {
|
||||
@@ -595,7 +597,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
item.row.domNode.style.width = 'fit-content';
|
||||
item.row.domNode.style.width = isFirefox ? '-moz-fit-content' : 'fit-content';
|
||||
item.width = DOM.getContentWidth(item.row.domNode);
|
||||
const style = window.getComputedStyle(item.row.domNode);
|
||||
|
||||
@@ -765,7 +767,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
let label: string | undefined;
|
||||
|
||||
if (this.dnd.getDragLabel) {
|
||||
label = this.dnd.getDragLabel(elements);
|
||||
label = this.dnd.getDragLabel(elements, event);
|
||||
}
|
||||
|
||||
if (typeof label === 'undefined') {
|
||||
@@ -845,10 +847,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
feedback = distinct(feedback).filter(i => i >= -1 && i < this.length).sort();
|
||||
feedback = feedback[0] === -1 ? [-1] : feedback;
|
||||
|
||||
if (feedback.length === 0) {
|
||||
throw new Error('Invalid empty feedback list');
|
||||
}
|
||||
|
||||
if (equalsDragFeedback(this.currentDragFeedback, feedback)) {
|
||||
return true;
|
||||
}
|
||||
@@ -858,7 +856,11 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
if (feedback[0] === -1) { // entire list feedback
|
||||
DOM.addClass(this.domNode, 'drop-target');
|
||||
this.currentDragFeedbackDisposable = toDisposable(() => DOM.removeClass(this.domNode, 'drop-target'));
|
||||
DOM.addClass(this.rowsContainer, 'drop-target');
|
||||
this.currentDragFeedbackDisposable = toDisposable(() => {
|
||||
DOM.removeClass(this.domNode, 'drop-target');
|
||||
DOM.removeClass(this.rowsContainer, 'drop-target');
|
||||
});
|
||||
} else {
|
||||
for (const index of feedback) {
|
||||
const item = this.items[index]!;
|
||||
@@ -909,12 +911,16 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
this.dnd.drop(dragData, event.element, event.index, event.browserEvent);
|
||||
}
|
||||
|
||||
private onDragEnd(): void {
|
||||
private onDragEnd(event: DragEvent): void {
|
||||
this.canDrop = false;
|
||||
this.teardownDragAndDropScrollTopAnimation();
|
||||
this.clearDragOverFeedback();
|
||||
this.currentDragData = undefined;
|
||||
StaticDND.CurrentDragAndDropData = undefined;
|
||||
|
||||
if (this.dnd.onDragEnd) {
|
||||
this.dnd.onDragEnd(event);
|
||||
}
|
||||
}
|
||||
|
||||
private clearDragOverFeedback(): void {
|
||||
|
||||
@@ -702,16 +702,27 @@ export interface IAccessibilityProvider<T> {
|
||||
* https://www.w3.org/TR/wai-aria/#aria-level
|
||||
*/
|
||||
getAriaLevel?(element: T): number | undefined;
|
||||
|
||||
onDidChangeActiveDescendant?: Event<void>;
|
||||
getActiveDescendantId?(element: T): string | undefined;
|
||||
}
|
||||
|
||||
export class DefaultStyleController implements IStyleController {
|
||||
|
||||
constructor(private styleElement: HTMLStyleElement, private selectorSuffix?: string) { }
|
||||
constructor(private styleElement: HTMLStyleElement, private selectorSuffix: string) { }
|
||||
|
||||
style(styles: IListStyles): void {
|
||||
const suffix = this.selectorSuffix ? `.${this.selectorSuffix}` : '';
|
||||
const suffix = this.selectorSuffix && `.${this.selectorSuffix}`;
|
||||
const content: string[] = [];
|
||||
|
||||
if (styles.listBackground) {
|
||||
if (styles.listBackground.isOpaque()) {
|
||||
content.push(`.monaco-list${suffix} .monaco-list-rows { background: ${styles.listBackground}; }`);
|
||||
} else if (!platform.isMacintosh) { // subpixel AA doesn't exist in macOS
|
||||
console.warn(`List with id '${this.selectorSuffix}' was styled with a non-opaque background color. This will break sub-pixel antialiasing.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (styles.listFocusBackground) {
|
||||
content.push(`.monaco-list${suffix}:focus .monaco-list-row.focused { background-color: ${styles.listFocusBackground}; }`);
|
||||
content.push(`.monaco-list${suffix}:focus .monaco-list-row.focused:hover { background-color: ${styles.listFocusBackground}; }`); // overwrite :hover style in this case!
|
||||
@@ -788,6 +799,7 @@ export class DefaultStyleController implements IStyleController {
|
||||
if (styles.listDropBackground) {
|
||||
content.push(`
|
||||
.monaco-list${suffix}.drop-target,
|
||||
.monaco-list${suffix} .monaco-list-rows.drop-target,
|
||||
.monaco-list${suffix} .monaco-list-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; }
|
||||
`);
|
||||
}
|
||||
@@ -815,7 +827,7 @@ export class DefaultStyleController implements IStyleController {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IListOptions<T> extends IListStyles {
|
||||
export interface IListOptions<T> {
|
||||
readonly identityProvider?: IIdentityProvider<T>;
|
||||
readonly dnd?: IListDragAndDrop<T>;
|
||||
readonly enableKeyboardNavigation?: boolean;
|
||||
@@ -828,7 +840,7 @@ export interface IListOptions<T> extends IListStyles {
|
||||
readonly multipleSelectionSupport?: boolean;
|
||||
readonly multipleSelectionController?: IMultipleSelectionController<T>;
|
||||
readonly openController?: IOpenController;
|
||||
readonly styleController?: IStyleController;
|
||||
readonly styleController?: (suffix: string) => IStyleController;
|
||||
readonly accessibilityProvider?: IAccessibilityProvider<T>;
|
||||
|
||||
// list view options
|
||||
@@ -842,6 +854,7 @@ export interface IListOptions<T> extends IListStyles {
|
||||
}
|
||||
|
||||
export interface IListStyles {
|
||||
listBackground?: Color;
|
||||
listFocusBackground?: Color;
|
||||
listFocusForeground?: Color;
|
||||
listActiveSelectionBackground?: Color;
|
||||
@@ -1062,9 +1075,9 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
|
||||
return this.dnd.getDragURI(element);
|
||||
}
|
||||
|
||||
getDragLabel?(elements: T[]): string | undefined {
|
||||
getDragLabel?(elements: T[], originalEvent: DragEvent): string | undefined {
|
||||
if (this.dnd.getDragLabel) {
|
||||
return this.dnd.getDragLabel(elements);
|
||||
return this.dnd.getDragLabel(elements, originalEvent);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -1080,6 +1093,12 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
|
||||
return this.dnd.onDragOver(data, targetElement, targetIndex, originalEvent);
|
||||
}
|
||||
|
||||
onDragEnd(originalEvent: DragEvent): void {
|
||||
if (this.dnd.onDragEnd) {
|
||||
this.dnd.onDragEnd(originalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void {
|
||||
this.dnd.drop(data, targetElement, targetIndex, originalEvent);
|
||||
}
|
||||
@@ -1097,9 +1116,9 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
private eventBufferer = new EventBufferer();
|
||||
private view: ListView<T>;
|
||||
private spliceable: ISpliceable<T>;
|
||||
private styleElement: HTMLStyleElement;
|
||||
private styleController: IStyleController;
|
||||
private typeLabelController?: TypeLabelController<T>;
|
||||
private accessibilityProvider?: IAccessibilityProvider<T>;
|
||||
|
||||
protected readonly disposables = new DisposableStore();
|
||||
|
||||
@@ -1185,8 +1204,14 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
const baseRenderers: IListRenderer<T, ITraitTemplateData>[] = [this.focus.renderer, this.selection.renderer];
|
||||
|
||||
if (_options.accessibilityProvider) {
|
||||
baseRenderers.push(new AccessibiltyRenderer<T>(_options.accessibilityProvider));
|
||||
this.accessibilityProvider = _options.accessibilityProvider;
|
||||
|
||||
if (this.accessibilityProvider) {
|
||||
baseRenderers.push(new AccessibiltyRenderer<T>(this.accessibilityProvider));
|
||||
|
||||
if (this.accessibilityProvider.onDidChangeActiveDescendant) {
|
||||
this.accessibilityProvider.onDidChangeActiveDescendant(this.onDidChangeActiveDescendant, this, this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
renderers = renderers.map(r => new PipelineRenderer(r.templateId, [...baseRenderers, r]));
|
||||
@@ -1198,11 +1223,18 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
this.view = new ListView(container, virtualDelegate, renderers, viewOptions);
|
||||
|
||||
this.updateAriaRole();
|
||||
if (typeof _options.ariaRole !== 'string') {
|
||||
this.view.domNode.setAttribute('role', ListAriaRootRole.TREE);
|
||||
} else {
|
||||
this.view.domNode.setAttribute('role', _options.ariaRole);
|
||||
}
|
||||
|
||||
this.styleElement = DOM.createStyleSheet(this.view.domNode);
|
||||
|
||||
this.styleController = _options.styleController || new DefaultStyleController(this.styleElement, this.view.domId);
|
||||
if (_options.styleController) {
|
||||
this.styleController = _options.styleController(this.view.domId);
|
||||
} else {
|
||||
const styleElement = DOM.createStyleSheet(this.view.domNode);
|
||||
this.styleController = new DefaultStyleController(styleElement, this.view.domId);
|
||||
}
|
||||
|
||||
this.spliceable = new CombinedSpliceable([
|
||||
new TraitSpliceable(this.focus, this.view, _options.identityProvider),
|
||||
@@ -1239,8 +1271,6 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
if (_options.ariaLabel) {
|
||||
this.view.domNode.setAttribute('aria-label', localize('aria list', "{0}. Use the navigation keys to navigate.", _options.ariaLabel));
|
||||
}
|
||||
|
||||
this.style(_options);
|
||||
}
|
||||
|
||||
protected createMouseController(options: IListOptions<T>): MouseController<T> {
|
||||
@@ -1603,23 +1633,23 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
private _onFocusChange(): void {
|
||||
const focus = this.focus.get();
|
||||
|
||||
if (focus.length > 0) {
|
||||
this.view.domNode.setAttribute('aria-activedescendant', this.view.getElementDomId(focus[0]));
|
||||
} else {
|
||||
this.view.domNode.removeAttribute('aria-activedescendant');
|
||||
}
|
||||
|
||||
this.updateAriaRole();
|
||||
|
||||
DOM.toggleClass(this.view.domNode, 'element-focused', focus.length > 0);
|
||||
this.onDidChangeActiveDescendant();
|
||||
}
|
||||
|
||||
private updateAriaRole(): void {
|
||||
if (typeof this.options.ariaRole !== 'string') {
|
||||
this.view.domNode.setAttribute('role', ListAriaRootRole.TREE);
|
||||
private onDidChangeActiveDescendant(): void {
|
||||
const focus = this.focus.get();
|
||||
|
||||
if (focus.length > 0) {
|
||||
let id: string | undefined;
|
||||
|
||||
if (this.accessibilityProvider?.getActiveDescendantId) {
|
||||
id = this.accessibilityProvider.getActiveDescendantId(this.view.element(focus[0]));
|
||||
}
|
||||
|
||||
this.view.domNode.setAttribute('aria-activedescendant', id || this.view.getElementDomId(focus[0]));
|
||||
} else {
|
||||
this.view.domNode.setAttribute('role', this.options.ariaRole);
|
||||
this.view.domNode.removeAttribute('aria-activedescendant');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.00001 8.70714L11.6465 12.3536L12.3536 11.6465L8.70711 8.00004L12.3536 4.35359L11.6465 3.64648L8.00001 7.29293L4.35356 3.64648L3.64645 4.35359L7.2929 8.00004L3.64645 11.6465L4.35356 12.3536L8.00001 8.70714Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 379 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.00001 8.70714L11.6465 12.3536L12.3536 11.6465L8.70711 8.00004L12.3536 4.35359L11.6465 3.64648L8.00001 7.29293L4.35356 3.64648L3.64645 4.35359L7.2929 8.00004L3.64645 11.6465L4.35356 12.3536L8.00001 8.70714Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 379 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.00001 8.70714L11.6465 12.3536L12.3536 11.6465L8.70711 8.00004L12.3536 4.35359L11.6465 3.64648L8.00001 7.29293L4.35356 3.64648L3.64645 4.35359L7.2929 8.00004L3.64645 11.6465L4.35356 12.3536L8.00001 8.70714Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 379 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 12V11H10V12H6ZM4 7H12V8H4V7ZM14 3V4H2V3H14Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 177 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 12V11H10V12H6ZM4 7H12V8H4V7ZM14 3V4H2V3H14Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 175 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 12V11H10V12H6ZM4 7H12V8H4V7ZM14 3V4H2V3H14Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 177 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 12L1 11H10V12H1ZM1 7H15V8H1L1 7ZM12 3V4H1L1 3H12Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 183 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 12L1 11H10V12H1ZM1 7H15V8H1L1 7ZM12 3V4H1L1 3H12Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 183 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 12L1 11H10V12H1ZM1 7H15V8H1L1 7ZM12 3V4H1L1 3H12Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 183 B |
@@ -15,7 +15,6 @@
|
||||
.monaco-menu .monaco-action-bar.vertical .action-item {
|
||||
padding: 0;
|
||||
transform: none;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -24,9 +23,7 @@
|
||||
}
|
||||
|
||||
.monaco-menu .monaco-action-bar.vertical .action-menu-item {
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
height: 2em;
|
||||
align-items: center;
|
||||
@@ -34,7 +31,6 @@
|
||||
}
|
||||
|
||||
.monaco-menu .monaco-action-bar.vertical .action-label {
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
text-decoration: none;
|
||||
padding: 0 1em;
|
||||
@@ -46,7 +42,6 @@
|
||||
.monaco-menu .monaco-action-bar.vertical .keybinding,
|
||||
.monaco-menu .monaco-action-bar.vertical .submenu-indicator {
|
||||
display: inline-block;
|
||||
-ms-flex: 2 1 auto;
|
||||
flex: 2 1 auto;
|
||||
padding: 0 1em;
|
||||
text-align: right;
|
||||
@@ -56,8 +51,8 @@
|
||||
|
||||
.monaco-menu .monaco-action-bar.vertical .submenu-indicator {
|
||||
height: 100%;
|
||||
-webkit-mask: url('submenu.svg') no-repeat 90% 50%/13px 13px;
|
||||
mask: url('submenu.svg') no-repeat 90% 50%/13px 13px;
|
||||
-webkit-mask: url('submenu.svg') no-repeat 90% 50%/13px 13px;
|
||||
}
|
||||
|
||||
.monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding,
|
||||
@@ -67,11 +62,7 @@
|
||||
|
||||
.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator) {
|
||||
display: inline-block;
|
||||
-webkit-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -80,7 +71,6 @@
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
|
||||
.monaco-menu .monaco-action-bar.vertical .action-item .monaco-submenu {
|
||||
position: absolute;
|
||||
}
|
||||
@@ -104,8 +94,8 @@
|
||||
.monaco-menu .monaco-action-bar.vertical .menu-item-check {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
-webkit-mask: url('check.svg') no-repeat 50% 56%/15px 15px;
|
||||
mask: url('check.svg') no-repeat 50% 56%/15px 15px;
|
||||
-webkit-mask: url('check.svg') no-repeat 50% 56%/15px 15px;
|
||||
width: 1em;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -119,10 +109,6 @@
|
||||
.context-view.monaco-menu-container {
|
||||
outline: 0;
|
||||
border: none;
|
||||
-webkit-animation: fadeIn 0.083s linear;
|
||||
-o-animation: fadeIn 0.083s linear;
|
||||
-moz-animation: fadeIn 0.083s linear;
|
||||
-ms-animation: fadeIn 0.083s linear;
|
||||
animation: fadeIn 0.083s linear;
|
||||
}
|
||||
|
||||
@@ -173,6 +159,10 @@
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.menubar.compact {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.menubar.compact > .menubar-menu-button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -204,22 +194,24 @@
|
||||
}
|
||||
|
||||
.menubar.compact .toolbar-toggle-more {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 16px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.menubar .toolbar-toggle-more {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
-webkit-mask: url('ellipsis.svg') no-repeat 50% 55%/14px 14px;
|
||||
mask: url('ellipsis.svg') no-repeat 50% 55%/14px 14px;
|
||||
-webkit-mask: url('ellipsis.svg') no-repeat 50% 55%/14px 14px;
|
||||
}
|
||||
|
||||
.menubar.compact .toolbar-toggle-more {
|
||||
-webkit-mask: url('menu.svg') no-repeat 50% 55%/16px 16px;
|
||||
mask: url('menu.svg') no-repeat 50% 55%/16px 16px;
|
||||
-webkit-mask: url('menu.svg') no-repeat 50% 55%/16px 16px;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import 'vs/css!./menu';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { IActionRunner, IAction, Action } from 'vs/base/common/actions';
|
||||
import { IActionRunner, IAction, Action, IActionViewItem } from 'vs/base/common/actions';
|
||||
import { ActionBar, IActionViewItemProvider, ActionsOrientation, Separator, ActionViewItem, IActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses } from 'vs/base/browser/dom';
|
||||
@@ -205,8 +205,8 @@ export class Menu extends ActionBar {
|
||||
container.appendChild(this.scrollableElement.getDomNode());
|
||||
this.scrollableElement.scanDomNode();
|
||||
|
||||
this.viewItems.filter(item => !(item instanceof MenuSeparatorActionViewItem)).forEach((item: BaseMenuActionViewItem, index: number, array: any[]) => {
|
||||
item.updatePositionInSet(index + 1, array.length);
|
||||
this.viewItems.filter(item => !(item instanceof MenuSeparatorActionViewItem)).forEach((item: IActionViewItem, index: number, array: any[]) => {
|
||||
(item as BaseMenuActionViewItem).updatePositionInSet(index + 1, array.length);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ export class Menu extends ActionBar {
|
||||
|
||||
const fgColor = style.foregroundColor ? `${style.foregroundColor}` : '';
|
||||
const bgColor = style.backgroundColor ? `${style.backgroundColor}` : '';
|
||||
const border = style.borderColor ? `2px solid ${style.borderColor}` : '';
|
||||
const border = style.borderColor ? `1px solid ${style.borderColor}` : '';
|
||||
const shadow = style.shadowColor ? `0 2px 4px ${style.shadowColor}` : '';
|
||||
|
||||
container.style.border = border;
|
||||
@@ -661,7 +661,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
|
||||
if (this.item) {
|
||||
addClass(this.item, 'monaco-submenu-item');
|
||||
this.item.setAttribute('aria-haspopup', 'true');
|
||||
|
||||
this.updateAriaExpanded('false');
|
||||
this.submenuIndicator = append(this.item, $('span.submenu-indicator'));
|
||||
this.submenuIndicator.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
@@ -726,7 +726,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
|
||||
if (this.parentData.submenu && (force || (this.parentData.submenu !== this.mysubmenu))) {
|
||||
this.parentData.submenu.dispose();
|
||||
this.parentData.submenu = undefined;
|
||||
|
||||
this.updateAriaExpanded('false');
|
||||
if (this.submenuContainer) {
|
||||
this.submenuDisposables.clear();
|
||||
this.submenuContainer = undefined;
|
||||
@@ -740,6 +740,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
|
||||
}
|
||||
|
||||
if (!this.parentData.submenu) {
|
||||
this.updateAriaExpanded('true');
|
||||
this.submenuContainer = append(this.element, $('div.monaco-submenu'));
|
||||
addClasses(this.submenuContainer, 'menubar-menu-items-holder', 'context-view');
|
||||
|
||||
@@ -778,13 +779,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
|
||||
|
||||
this.parentData.parent.focus();
|
||||
|
||||
if (this.parentData.submenu) {
|
||||
this.parentData.submenu.dispose();
|
||||
this.parentData.submenu = undefined;
|
||||
}
|
||||
|
||||
this.submenuDisposables.clear();
|
||||
this.submenuContainer = undefined;
|
||||
this.cleanupExistingSubmenu(true);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -799,13 +794,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
|
||||
this.submenuDisposables.add(this.parentData.submenu.onDidCancel(() => {
|
||||
this.parentData.parent.focus();
|
||||
|
||||
if (this.parentData.submenu) {
|
||||
this.parentData.submenu.dispose();
|
||||
this.parentData.submenu = undefined;
|
||||
}
|
||||
|
||||
this.submenuDisposables.clear();
|
||||
this.submenuContainer = undefined;
|
||||
this.cleanupExistingSubmenu(true);
|
||||
}));
|
||||
|
||||
this.parentData.submenu.focus(selectFirstItem);
|
||||
@@ -816,6 +805,12 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
private updateAriaExpanded(value: string): void {
|
||||
if (this.item) {
|
||||
this.item?.setAttribute('aria-expanded', value);
|
||||
}
|
||||
}
|
||||
|
||||
protected applyStyle(): void {
|
||||
super.applyStyle();
|
||||
|
||||
|
||||
@@ -309,7 +309,7 @@ export class MenuBar extends Disposable {
|
||||
|
||||
createOverflowMenu(): void {
|
||||
const label = this.options.compactMode !== undefined ? nls.localize('mAppMenu', 'Application Menu') : nls.localize('mMore', "...");
|
||||
const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': -1, 'aria-label': label, 'aria-haspopup': true });
|
||||
const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': -1, 'aria-label': label, 'title': label, 'aria-haspopup': true });
|
||||
const titleElement = $('div.menubar-menu-title.toolbar-toggle-more', { 'role': 'none', 'aria-hidden': true });
|
||||
|
||||
buttonElement.appendChild(titleElement);
|
||||
|
||||
@@ -35,19 +35,7 @@
|
||||
animation-duration: 4s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
-ms-animation-name: progress;
|
||||
-ms-animation-duration: 4s;
|
||||
-ms-animation-iteration-count: infinite;
|
||||
-ms-animation-timing-function: linear;
|
||||
-webkit-animation-name: progress;
|
||||
-webkit-animation-duration: 4s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
-webkit-animation-timing-function: linear;
|
||||
-moz-animation-name: progress;
|
||||
-moz-animation-duration: 4s;
|
||||
-moz-animation-iteration-count: infinite;
|
||||
-moz-animation-timing-function: linear;
|
||||
will-change: transform;
|
||||
transform: translate3d(0px, 0px, 0px);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,6 +46,3 @@
|
||||
* 100%: 50 * 100 - 50 (do not overflow): 4950%
|
||||
*/
|
||||
@keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } }
|
||||
@-ms-keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } }
|
||||
@-webkit-keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } }
|
||||
@-moz-keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } }
|
||||
@@ -99,6 +99,7 @@ export abstract class AbstractScrollbar extends Widget {
|
||||
this.slider.setHeight(height);
|
||||
}
|
||||
this.slider.setLayerHinting(true);
|
||||
this.slider.setContain('strict');
|
||||
|
||||
this.domNode.domNode.appendChild(this.slider.domNode);
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ export class MouseWheelClassifier {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 0.5 * last + 0.25 * before last + 0.125 * before before last + ...
|
||||
// 0.5 * last + 0.25 * 2nd last + 0.125 * 3rd last + ...
|
||||
let remainingInfluence = 1;
|
||||
let score = 0;
|
||||
let iteration = 1;
|
||||
|
||||
@@ -16,11 +16,7 @@
|
||||
|
||||
.monaco-select-box-dropdown-container {
|
||||
display: none;
|
||||
-webkit-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown * {
|
||||
@@ -55,11 +51,7 @@
|
||||
padding-right: 1px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
-webkit-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.monaco-select-box-dropdown-container > .select-box-details-pane {
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as dom from 'vs/base/browser/dom';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { ISelectBoxDelegate, ISelectOptionItem, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { Gesture, EventType } from 'vs/base/browser/touch';
|
||||
|
||||
export class SelectBoxNative extends Disposable implements ISelectBoxDelegate {
|
||||
|
||||
@@ -44,6 +45,12 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate {
|
||||
}
|
||||
|
||||
private registerListeners() {
|
||||
this._register(Gesture.addTarget(this.selectElement));
|
||||
[EventType.Tap].forEach(eventType => {
|
||||
this._register(dom.addDisposableListener(this.selectElement, eventType, (e) => {
|
||||
this.selectElement.focus();
|
||||
}));
|
||||
});
|
||||
|
||||
this._register(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => {
|
||||
this.selectElement.title = e.target.value;
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-panel-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-panel-view .panel {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.monaco-panel-view .panel > .panel-header {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-panel-view .panel > .panel-header > .twisties {
|
||||
width: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform-origin: center;
|
||||
color: inherit;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.monaco-panel-view .panel > .panel-header.expanded > .twisties::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* TODO: actions should be part of the panel, but they aren't yet */
|
||||
.monaco-panel-view .panel > .panel-header > .actions {
|
||||
display: none;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* TODO: actions should be part of the panel, but they aren't yet */
|
||||
.monaco-panel-view .panel:hover > .panel-header.expanded > .actions,
|
||||
.monaco-panel-view .panel > .panel-header.actions-always-visible.expanded > .actions,
|
||||
.monaco-panel-view .panel > .panel-header.focused.expanded > .actions {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
/* TODO: actions should be part of the panel, but they aren't yet */
|
||||
.monaco-panel-view .panel > .panel-header > .actions .action-label.icon,
|
||||
.monaco-panel-view .panel > .panel-header > .actions .action-label.codicon {
|
||||
width: 28px;
|
||||
height: 22px;
|
||||
background-size: 16px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
margin-right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Bold font style does not go well with CJK fonts */
|
||||
.monaco-panel-view:lang(zh-Hans) .panel > .panel-header,
|
||||
.monaco-panel-view:lang(zh-Hant) .panel > .panel-header,
|
||||
.monaco-panel-view:lang(ja) .panel > .panel-header,
|
||||
.monaco-panel-view:lang(ko) .panel > .panel-header {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.monaco-panel-view .panel > .panel-header.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-panel-view .panel > .panel-body {
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Animation */
|
||||
|
||||
.monaco-panel-view.animated .split-view-view {
|
||||
transition-duration: 0.15s;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
.monaco-panel-view.animated.vertical .split-view-view {
|
||||
transition-property: height;
|
||||
}
|
||||
|
||||
.monaco-panel-view.animated.horizontal .split-view-view {
|
||||
transition-property: width;
|
||||
}
|
||||
101
src/vs/base/browser/ui/splitview/paneview.css
Normal file
@@ -0,0 +1,101 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-pane-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane > .pane-header {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane > .pane-header > .twisties {
|
||||
width: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform-origin: center;
|
||||
color: inherit;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane > .pane-header.expanded > .twisties::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* TODO: actions should be part of the pane, but they aren't yet */
|
||||
.monaco-pane-view .pane > .pane-header > .actions {
|
||||
display: none;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* TODO: actions should be part of the pane, but they aren't yet */
|
||||
.monaco-pane-view .pane:hover > .pane-header.expanded > .actions,
|
||||
.monaco-pane-view .pane > .pane-header.actions-always-visible.expanded > .actions,
|
||||
.monaco-pane-view .pane > .pane-header.focused.expanded > .actions {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
/* TODO: actions should be part of the pane, but they aren't yet */
|
||||
.monaco-pane-view .pane > .pane-header > .actions .action-label.icon,
|
||||
.monaco-pane-view .pane > .pane-header > .actions .action-label.codicon {
|
||||
width: 28px;
|
||||
height: 22px;
|
||||
background-size: 16px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
margin-right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Bold font style does not go well with CJK fonts */
|
||||
.monaco-pane-view:lang(zh-Hans) .pane > .pane-header,
|
||||
.monaco-pane-view:lang(zh-Hant) .pane > .pane-header,
|
||||
.monaco-pane-view:lang(ja) .pane > .pane-header,
|
||||
.monaco-pane-view:lang(ko) .pane > .pane-header {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane > .pane-header.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane > .pane-body {
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Animation */
|
||||
|
||||
.monaco-pane-view.animated .split-view-view {
|
||||
transition-duration: 0.15s;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
.monaco-pane-view.animated.vertical .split-view-view {
|
||||
transition-property: height;
|
||||
}
|
||||
|
||||
.monaco-pane-view.animated.horizontal .split-view-view {
|
||||
transition-property: width;
|
||||
}
|
||||
@@ -3,25 +3,27 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./panelview';
|
||||
import 'vs/css!./paneview';
|
||||
import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { $, append, addClass, removeClass, toggleClass, trackFocus } from 'vs/base/browser/dom';
|
||||
import { $, append, addClass, removeClass, toggleClass, trackFocus, EventHelper } from 'vs/base/browser/dom';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { Color, RGBA } from 'vs/base/common/color';
|
||||
import { SplitView, IView } from './splitview';
|
||||
import { isFirefox } from 'vs/base/browser/browser';
|
||||
import { DataTransfers } from 'vs/base/browser/dnd';
|
||||
|
||||
export interface IPanelOptions {
|
||||
export interface IPaneOptions {
|
||||
ariaHeaderLabel?: string;
|
||||
minimumBodySize?: number;
|
||||
maximumBodySize?: number;
|
||||
expanded?: boolean;
|
||||
}
|
||||
|
||||
export interface IPanelStyles {
|
||||
export interface IPaneStyles {
|
||||
dropBackground?: Color;
|
||||
headerForeground?: Color;
|
||||
headerBackground?: Color;
|
||||
@@ -29,7 +31,7 @@ export interface IPanelStyles {
|
||||
}
|
||||
|
||||
/**
|
||||
* A Panel is a structured SplitView view.
|
||||
* A Pane is a structured SplitView view.
|
||||
*
|
||||
* WARNING: You must call `render()` after you contruct it.
|
||||
* It can't be done automatically at the end of the ctor
|
||||
@@ -37,7 +39,7 @@ export interface IPanelStyles {
|
||||
* Subclasses wouldn't be able to set own properties
|
||||
* before the `render()` call, thus forbiding their use.
|
||||
*/
|
||||
export abstract class Panel extends Disposable implements IView {
|
||||
export abstract class Pane extends Disposable implements IView {
|
||||
|
||||
private static readonly HEADER_SIZE = 22;
|
||||
|
||||
@@ -52,7 +54,7 @@ export abstract class Panel extends Disposable implements IView {
|
||||
private _minimumBodySize: number;
|
||||
private _maximumBodySize: number;
|
||||
private ariaHeaderLabel: string;
|
||||
private styles: IPanelStyles = {};
|
||||
private styles: IPaneStyles = {};
|
||||
private animationTimer: number | undefined = undefined;
|
||||
|
||||
private readonly _onDidChange = this._register(new Emitter<number | undefined>());
|
||||
@@ -93,7 +95,7 @@ export abstract class Panel extends Disposable implements IView {
|
||||
}
|
||||
|
||||
private get headerSize(): number {
|
||||
return this.headerVisible ? Panel.HEADER_SIZE : 0;
|
||||
return this.headerVisible ? Pane.HEADER_SIZE : 0;
|
||||
}
|
||||
|
||||
get minimumSize(): number {
|
||||
@@ -114,14 +116,14 @@ export abstract class Panel extends Disposable implements IView {
|
||||
|
||||
width: number = 0;
|
||||
|
||||
constructor(options: IPanelOptions = {}) {
|
||||
constructor(options: IPaneOptions = {}) {
|
||||
super();
|
||||
this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded;
|
||||
this.ariaHeaderLabel = options.ariaHeaderLabel || '';
|
||||
this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : 120;
|
||||
this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY;
|
||||
|
||||
this.element = $('.panel');
|
||||
this.element = $('.pane');
|
||||
}
|
||||
|
||||
isExpanded(): boolean {
|
||||
@@ -167,7 +169,7 @@ export abstract class Panel extends Disposable implements IView {
|
||||
}
|
||||
|
||||
render(): void {
|
||||
this.header = $('.panel-header');
|
||||
this.header = $('.pane-header');
|
||||
append(this.element, this.header);
|
||||
this.header.setAttribute('tabindex', '0');
|
||||
this.header.setAttribute('role', 'toolbar');
|
||||
@@ -196,12 +198,12 @@ export abstract class Panel extends Disposable implements IView {
|
||||
this._register(domEvent(this.header, 'click')
|
||||
(() => this.setExpanded(!this.isExpanded()), null));
|
||||
|
||||
this.body = append(this.element, $('.panel-body'));
|
||||
this.body = append(this.element, $('.pane-body'));
|
||||
this.renderBody(this.body);
|
||||
}
|
||||
|
||||
layout(height: number): void {
|
||||
const headerSize = this.headerVisible ? Panel.HEADER_SIZE : 0;
|
||||
const headerSize = this.headerVisible ? Pane.HEADER_SIZE : 0;
|
||||
|
||||
if (this.isExpanded()) {
|
||||
this.layoutBody(height - headerSize, this.width);
|
||||
@@ -209,7 +211,7 @@ export abstract class Panel extends Disposable implements IView {
|
||||
}
|
||||
}
|
||||
|
||||
style(styles: IPanelStyles): void {
|
||||
style(styles: IPaneStyles): void {
|
||||
this.styles = styles;
|
||||
|
||||
if (!this.header) {
|
||||
@@ -240,31 +242,31 @@ export abstract class Panel extends Disposable implements IView {
|
||||
}
|
||||
|
||||
interface IDndContext {
|
||||
draggable: PanelDraggable | null;
|
||||
draggable: PaneDraggable | null;
|
||||
}
|
||||
|
||||
class PanelDraggable extends Disposable {
|
||||
class PaneDraggable extends Disposable {
|
||||
|
||||
private static readonly DefaultDragOverBackgroundColor = new Color(new RGBA(128, 128, 128, 0.5));
|
||||
|
||||
private dragOverCounter = 0; // see https://github.com/Microsoft/vscode/issues/14470
|
||||
|
||||
private _onDidDrop = this._register(new Emitter<{ from: Panel, to: Panel }>());
|
||||
private _onDidDrop = this._register(new Emitter<{ from: Pane, to: Pane }>());
|
||||
readonly onDidDrop = this._onDidDrop.event;
|
||||
|
||||
constructor(private panel: Panel, private dnd: IPanelDndController, private context: IDndContext) {
|
||||
constructor(private pane: Pane, private dnd: IPaneDndController, private context: IDndContext) {
|
||||
super();
|
||||
|
||||
panel.draggableElement.draggable = true;
|
||||
this._register(domEvent(panel.draggableElement, 'dragstart')(this.onDragStart, this));
|
||||
this._register(domEvent(panel.dropTargetElement, 'dragenter')(this.onDragEnter, this));
|
||||
this._register(domEvent(panel.dropTargetElement, 'dragleave')(this.onDragLeave, this));
|
||||
this._register(domEvent(panel.dropTargetElement, 'dragend')(this.onDragEnd, this));
|
||||
this._register(domEvent(panel.dropTargetElement, 'drop')(this.onDrop, this));
|
||||
pane.draggableElement.draggable = true;
|
||||
this._register(domEvent(pane.draggableElement, 'dragstart')(this.onDragStart, this));
|
||||
this._register(domEvent(pane.dropTargetElement, 'dragenter')(this.onDragEnter, this));
|
||||
this._register(domEvent(pane.dropTargetElement, 'dragleave')(this.onDragLeave, this));
|
||||
this._register(domEvent(pane.dropTargetElement, 'dragend')(this.onDragEnd, this));
|
||||
this._register(domEvent(pane.dropTargetElement, 'drop')(this.onDrop, this));
|
||||
}
|
||||
|
||||
private onDragStart(e: DragEvent): void {
|
||||
if (!this.dnd.canDrag(this.panel) || !e.dataTransfer) {
|
||||
if (!this.dnd.canDrag(this.pane) || !e.dataTransfer) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
@@ -272,7 +274,12 @@ class PanelDraggable extends Disposable {
|
||||
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
|
||||
const dragImage = append(document.body, $('.monaco-drag-image', {}, this.panel.draggableElement.textContent || ''));
|
||||
if (isFirefox) {
|
||||
// Firefox: requires to set a text data transfer to get going
|
||||
e.dataTransfer?.setData(DataTransfers.TEXT, this.pane.draggableElement.textContent || '');
|
||||
}
|
||||
|
||||
const dragImage = append(document.body, $('.monaco-drag-image', {}, this.pane.draggableElement.textContent || ''));
|
||||
e.dataTransfer.setDragImage(dragImage, -10, -10);
|
||||
setTimeout(() => document.body.removeChild(dragImage), 0);
|
||||
|
||||
@@ -284,7 +291,7 @@ class PanelDraggable extends Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.dnd.canDrop(this.context.draggable.panel, this.panel)) {
|
||||
if (!this.dnd.canDrop(this.context.draggable.pane, this.pane)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -297,7 +304,7 @@ class PanelDraggable extends Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.dnd.canDrop(this.context.draggable.panel, this.panel)) {
|
||||
if (!this.dnd.canDrop(this.context.draggable.pane, this.pane)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -323,11 +330,13 @@ class PanelDraggable extends Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
EventHelper.stop(e);
|
||||
|
||||
this.dragOverCounter = 0;
|
||||
this.render();
|
||||
|
||||
if (this.dnd.canDrop(this.context.draggable.panel, this.panel) && this.context.draggable !== this) {
|
||||
this._onDidDrop.fire({ from: this.context.draggable.panel, to: this.panel });
|
||||
if (this.dnd.canDrop(this.context.draggable.pane, this.pane) && this.context.draggable !== this) {
|
||||
this._onDidDrop.fire({ from: this.context.draggable.pane, to: this.pane });
|
||||
}
|
||||
|
||||
this.context.draggable = null;
|
||||
@@ -337,106 +346,106 @@ class PanelDraggable extends Disposable {
|
||||
let backgroundColor: string | null = null;
|
||||
|
||||
if (this.dragOverCounter > 0) {
|
||||
backgroundColor = (this.panel.dropBackground || PanelDraggable.DefaultDragOverBackgroundColor).toString();
|
||||
backgroundColor = (this.pane.dropBackground || PaneDraggable.DefaultDragOverBackgroundColor).toString();
|
||||
}
|
||||
|
||||
this.panel.dropTargetElement.style.backgroundColor = backgroundColor || '';
|
||||
this.pane.dropTargetElement.style.backgroundColor = backgroundColor || '';
|
||||
}
|
||||
}
|
||||
|
||||
export interface IPanelDndController {
|
||||
canDrag(panel: Panel): boolean;
|
||||
canDrop(panel: Panel, overPanel: Panel): boolean;
|
||||
export interface IPaneDndController {
|
||||
canDrag(pane: Pane): boolean;
|
||||
canDrop(pane: Pane, overPane: Pane): boolean;
|
||||
}
|
||||
|
||||
export class DefaultPanelDndController implements IPanelDndController {
|
||||
export class DefaultPaneDndController implements IPaneDndController {
|
||||
|
||||
canDrag(panel: Panel): boolean {
|
||||
canDrag(pane: Pane): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
canDrop(panel: Panel, overPanel: Panel): boolean {
|
||||
canDrop(pane: Pane, overPane: Pane): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IPanelViewOptions {
|
||||
dnd?: IPanelDndController;
|
||||
export interface IPaneViewOptions {
|
||||
dnd?: IPaneDndController;
|
||||
}
|
||||
|
||||
interface IPanelItem {
|
||||
panel: Panel;
|
||||
interface IPaneItem {
|
||||
pane: Pane;
|
||||
disposable: IDisposable;
|
||||
}
|
||||
|
||||
export class PanelView extends Disposable {
|
||||
export class PaneView extends Disposable {
|
||||
|
||||
private dnd: IPanelDndController | undefined;
|
||||
private dnd: IPaneDndController | undefined;
|
||||
private dndContext: IDndContext = { draggable: null };
|
||||
private el: HTMLElement;
|
||||
private panelItems: IPanelItem[] = [];
|
||||
private paneItems: IPaneItem[] = [];
|
||||
private width: number = 0;
|
||||
private splitview: SplitView;
|
||||
private animationTimer: number | undefined = undefined;
|
||||
|
||||
private _onDidDrop = this._register(new Emitter<{ from: Panel, to: Panel }>());
|
||||
readonly onDidDrop: Event<{ from: Panel, to: Panel }> = this._onDidDrop.event;
|
||||
private _onDidDrop = this._register(new Emitter<{ from: Pane, to: Pane }>());
|
||||
readonly onDidDrop: Event<{ from: Pane, to: Pane }> = this._onDidDrop.event;
|
||||
|
||||
readonly onDidSashChange: Event<number>;
|
||||
|
||||
constructor(container: HTMLElement, options: IPanelViewOptions = {}) {
|
||||
constructor(container: HTMLElement, options: IPaneViewOptions = {}) {
|
||||
super();
|
||||
|
||||
this.dnd = options.dnd;
|
||||
this.el = append(container, $('.monaco-panel-view'));
|
||||
this.el = append(container, $('.monaco-pane-view'));
|
||||
this.splitview = this._register(new SplitView(this.el));
|
||||
this.onDidSashChange = this.splitview.onDidSashChange;
|
||||
}
|
||||
|
||||
addPanel(panel: Panel, size: number, index = this.splitview.length): void {
|
||||
addPane(pane: Pane, size: number, index = this.splitview.length): void {
|
||||
const disposables = new DisposableStore();
|
||||
panel.onDidChangeExpansionState(this.setupAnimation, this, disposables);
|
||||
pane.onDidChangeExpansionState(this.setupAnimation, this, disposables);
|
||||
|
||||
const panelItem = { panel, disposable: disposables };
|
||||
this.panelItems.splice(index, 0, panelItem);
|
||||
panel.width = this.width;
|
||||
this.splitview.addView(panel, size, index);
|
||||
const paneItem = { pane: pane, disposable: disposables };
|
||||
this.paneItems.splice(index, 0, paneItem);
|
||||
pane.width = this.width;
|
||||
this.splitview.addView(pane, size, index);
|
||||
|
||||
if (this.dnd) {
|
||||
const draggable = new PanelDraggable(panel, this.dnd, this.dndContext);
|
||||
const draggable = new PaneDraggable(pane, this.dnd, this.dndContext);
|
||||
disposables.add(draggable);
|
||||
disposables.add(draggable.onDidDrop(this._onDidDrop.fire, this._onDidDrop));
|
||||
}
|
||||
}
|
||||
|
||||
removePanel(panel: Panel): void {
|
||||
const index = firstIndex(this.panelItems, item => item.panel === panel);
|
||||
removePane(pane: Pane): void {
|
||||
const index = firstIndex(this.paneItems, item => item.pane === pane);
|
||||
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.splitview.removeView(index);
|
||||
const panelItem = this.panelItems.splice(index, 1)[0];
|
||||
panelItem.disposable.dispose();
|
||||
const paneItem = this.paneItems.splice(index, 1)[0];
|
||||
paneItem.disposable.dispose();
|
||||
}
|
||||
|
||||
movePanel(from: Panel, to: Panel): void {
|
||||
const fromIndex = firstIndex(this.panelItems, item => item.panel === from);
|
||||
const toIndex = firstIndex(this.panelItems, item => item.panel === to);
|
||||
movePane(from: Pane, to: Pane): void {
|
||||
const fromIndex = firstIndex(this.paneItems, item => item.pane === from);
|
||||
const toIndex = firstIndex(this.paneItems, item => item.pane === to);
|
||||
|
||||
if (fromIndex === -1 || toIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [panelItem] = this.panelItems.splice(fromIndex, 1);
|
||||
this.panelItems.splice(toIndex, 0, panelItem);
|
||||
const [paneItem] = this.paneItems.splice(fromIndex, 1);
|
||||
this.paneItems.splice(toIndex, 0, paneItem);
|
||||
|
||||
this.splitview.moveView(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
resizePanel(panel: Panel, size: number): void {
|
||||
const index = firstIndex(this.panelItems, item => item.panel === panel);
|
||||
resizePane(pane: Pane, size: number): void {
|
||||
const index = firstIndex(this.paneItems, item => item.pane === pane);
|
||||
|
||||
if (index === -1) {
|
||||
return;
|
||||
@@ -445,8 +454,8 @@ export class PanelView extends Disposable {
|
||||
this.splitview.resizeView(index, size);
|
||||
}
|
||||
|
||||
getPanelSize(panel: Panel): number {
|
||||
const index = firstIndex(this.panelItems, item => item.panel === panel);
|
||||
getPaneSize(pane: Pane): number {
|
||||
const index = firstIndex(this.paneItems, item => item.pane === pane);
|
||||
|
||||
if (index === -1) {
|
||||
return -1;
|
||||
@@ -458,8 +467,8 @@ export class PanelView extends Disposable {
|
||||
layout(height: number, width: number): void {
|
||||
this.width = width;
|
||||
|
||||
for (const panelItem of this.panelItems) {
|
||||
panelItem.panel.width = width;
|
||||
for (const paneItem of this.paneItems) {
|
||||
paneItem.pane.width = width;
|
||||
}
|
||||
|
||||
this.splitview.layout(height);
|
||||
@@ -481,6 +490,6 @@ export class PanelView extends Disposable {
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.panelItems.forEach(i => i.disposable.dispose());
|
||||
this.paneItems.forEach(i => i.disposable.dispose());
|
||||
}
|
||||
}
|
||||
@@ -23,14 +23,14 @@ const defaultStyles: ISplitViewStyles = {
|
||||
separatorBorder: Color.transparent
|
||||
};
|
||||
|
||||
export interface ISplitViewOptions {
|
||||
export interface ISplitViewOptions<TLayoutContext = undefined> {
|
||||
readonly orientation?: Orientation; // default Orientation.VERTICAL
|
||||
readonly styles?: ISplitViewStyles;
|
||||
readonly orthogonalStartSash?: Sash;
|
||||
readonly orthogonalEndSash?: Sash;
|
||||
readonly inverseAltBehavior?: boolean;
|
||||
readonly proportionalLayout?: boolean; // default true,
|
||||
readonly descriptor?: ISplitViewDescriptor;
|
||||
readonly descriptor?: ISplitViewDescriptor<TLayoutContext>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,14 +42,14 @@ export const enum LayoutPriority {
|
||||
High
|
||||
}
|
||||
|
||||
export interface IView {
|
||||
export interface IView<TLayoutContext = undefined> {
|
||||
readonly element: HTMLElement;
|
||||
readonly minimumSize: number;
|
||||
readonly maximumSize: number;
|
||||
readonly onDidChange: Event<number | undefined>;
|
||||
readonly priority?: LayoutPriority;
|
||||
readonly snap?: boolean;
|
||||
layout(size: number, orthogonalSize: number | undefined): void;
|
||||
layout(size: number, offset: number, context: TLayoutContext | undefined): void;
|
||||
setVisible?(visible: boolean): void;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ interface ISashEvent {
|
||||
|
||||
type ViewItemSize = number | { cachedVisibleSize: number };
|
||||
|
||||
abstract class ViewItem {
|
||||
abstract class ViewItem<TLayoutContext> {
|
||||
|
||||
private _size: number;
|
||||
set size(size: number) {
|
||||
@@ -109,9 +109,13 @@ abstract class ViewItem {
|
||||
get priority(): LayoutPriority | undefined { return this.view.priority; }
|
||||
get snap(): boolean { return !!this.view.snap; }
|
||||
|
||||
set enabled(enabled: boolean) {
|
||||
this.container.style.pointerEvents = enabled ? null : 'none';
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected container: HTMLElement,
|
||||
private view: IView,
|
||||
private view: IView<TLayoutContext>,
|
||||
size: ViewItemSize,
|
||||
private disposable: IDisposable
|
||||
) {
|
||||
@@ -125,31 +129,31 @@ abstract class ViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
layout(position: number, orthogonalSize: number | undefined): void {
|
||||
this.layoutContainer(position);
|
||||
this.view.layout(this.size, orthogonalSize);
|
||||
layout(offset: number, layoutContext: TLayoutContext | undefined): void {
|
||||
this.layoutContainer(offset);
|
||||
this.view.layout(this.size, offset, layoutContext);
|
||||
}
|
||||
|
||||
abstract layoutContainer(position: number): void;
|
||||
abstract layoutContainer(offset: number): void;
|
||||
|
||||
dispose(): IView {
|
||||
dispose(): IView<TLayoutContext> {
|
||||
this.disposable.dispose();
|
||||
return this.view;
|
||||
}
|
||||
}
|
||||
|
||||
class VerticalViewItem extends ViewItem {
|
||||
class VerticalViewItem<TLayoutContext> extends ViewItem<TLayoutContext> {
|
||||
|
||||
layoutContainer(position: number): void {
|
||||
this.container.style.top = `${position}px`;
|
||||
layoutContainer(offset: number): void {
|
||||
this.container.style.top = `${offset}px`;
|
||||
this.container.style.height = `${this.size}px`;
|
||||
}
|
||||
}
|
||||
|
||||
class HorizontalViewItem extends ViewItem {
|
||||
class HorizontalViewItem<TLayoutContext> extends ViewItem<TLayoutContext> {
|
||||
|
||||
layoutContainer(position: number): void {
|
||||
this.container.style.left = `${position}px`;
|
||||
layoutContainer(offset: number): void {
|
||||
this.container.style.left = `${offset}px`;
|
||||
this.container.style.width = `${this.size}px`;
|
||||
}
|
||||
}
|
||||
@@ -194,26 +198,26 @@ export namespace Sizing {
|
||||
export function Invisible(cachedVisibleSize: number): InvisibleSizing { return { type: 'invisible', cachedVisibleSize }; }
|
||||
}
|
||||
|
||||
export interface ISplitViewDescriptor {
|
||||
export interface ISplitViewDescriptor<TLayoutContext> {
|
||||
size: number;
|
||||
views: {
|
||||
visible?: boolean;
|
||||
size: number;
|
||||
view: IView;
|
||||
view: IView<TLayoutContext>;
|
||||
}[];
|
||||
}
|
||||
|
||||
export class SplitView extends Disposable {
|
||||
export class SplitView<TLayoutContext = undefined> extends Disposable {
|
||||
|
||||
readonly orientation: Orientation;
|
||||
readonly el: HTMLElement;
|
||||
private sashContainer: HTMLElement;
|
||||
private viewContainer: HTMLElement;
|
||||
private size = 0;
|
||||
private orthogonalSize: number | undefined;
|
||||
private layoutContext: TLayoutContext | undefined;
|
||||
private contentSize = 0;
|
||||
private proportions: undefined | number[] = undefined;
|
||||
private viewItems: ViewItem[] = [];
|
||||
private viewItems: ViewItem<TLayoutContext>[] = [];
|
||||
private sashItems: ISashItem[] = [];
|
||||
private sashDragState: ISashDragState | undefined;
|
||||
private state: State = State.Idle;
|
||||
@@ -262,7 +266,29 @@ export class SplitView extends Disposable {
|
||||
return this.sashItems.map(s => s.sash);
|
||||
}
|
||||
|
||||
constructor(container: HTMLElement, options: ISplitViewOptions = {}) {
|
||||
private _startSnappingEnabled = true;
|
||||
get startSnappingEnabled(): boolean { return this._startSnappingEnabled; }
|
||||
set startSnappingEnabled(startSnappingEnabled: boolean) {
|
||||
if (this._startSnappingEnabled === startSnappingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._startSnappingEnabled = startSnappingEnabled;
|
||||
this.updateSashEnablement();
|
||||
}
|
||||
|
||||
private _endSnappingEnabled = true;
|
||||
get endSnappingEnabled(): boolean { return this._endSnappingEnabled; }
|
||||
set endSnappingEnabled(endSnappingEnabled: boolean) {
|
||||
if (this._endSnappingEnabled === endSnappingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._endSnappingEnabled = endSnappingEnabled;
|
||||
this.updateSashEnablement();
|
||||
}
|
||||
|
||||
constructor(container: HTMLElement, options: ISplitViewOptions<TLayoutContext> = {}) {
|
||||
super();
|
||||
|
||||
this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
|
||||
@@ -305,11 +331,11 @@ export class SplitView extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
addView(view: IView, size: number | Sizing, index = this.viewItems.length): void {
|
||||
addView(view: IView<TLayoutContext>, size: number | Sizing, index = this.viewItems.length): void {
|
||||
this.doAddView(view, size, index, false);
|
||||
}
|
||||
|
||||
removeView(index: number, sizing?: Sizing): IView {
|
||||
removeView(index: number, sizing?: Sizing): IView<TLayoutContext> {
|
||||
if (this.state !== State.Idle) {
|
||||
throw new Error('Cant modify splitview');
|
||||
}
|
||||
@@ -401,10 +427,10 @@ export class SplitView extends Disposable {
|
||||
return viewItem.cachedVisibleSize;
|
||||
}
|
||||
|
||||
layout(size: number, orthogonalSize?: number): void {
|
||||
layout(size: number, layoutContext?: TLayoutContext): void {
|
||||
const previousSize = Math.max(this.size, this.contentSize);
|
||||
this.size = size;
|
||||
this.orthogonalSize = orthogonalSize;
|
||||
this.layoutContext = layoutContext;
|
||||
|
||||
if (!this.proportions) {
|
||||
const indexes = range(this.viewItems.length);
|
||||
@@ -430,6 +456,10 @@ export class SplitView extends Disposable {
|
||||
}
|
||||
|
||||
private onSashStart({ sash, start, alt }: ISashEvent): void {
|
||||
for (const item of this.viewItems) {
|
||||
item.enabled = false;
|
||||
}
|
||||
|
||||
const index = firstIndex(this.sashItems, item => item.sash === sash);
|
||||
|
||||
// This way, we can press Alt while we resize a sash, macOS style!
|
||||
@@ -535,9 +565,13 @@ export class SplitView extends Disposable {
|
||||
this._onDidSashChange.fire(index);
|
||||
this.sashDragState!.disposable.dispose();
|
||||
this.saveProportions();
|
||||
|
||||
for (const item of this.viewItems) {
|
||||
item.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private onViewChange(item: ViewItem, size: number | undefined): void {
|
||||
private onViewChange(item: ViewItem<TLayoutContext>, size: number | undefined): void {
|
||||
const index = this.viewItems.indexOf(item);
|
||||
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
@@ -584,7 +618,7 @@ export class SplitView extends Disposable {
|
||||
}
|
||||
|
||||
distributeViewSizes(): void {
|
||||
const flexibleViewItems: ViewItem[] = [];
|
||||
const flexibleViewItems: ViewItem<TLayoutContext>[] = [];
|
||||
let flexibleSize = 0;
|
||||
|
||||
for (const item of this.viewItems) {
|
||||
@@ -615,7 +649,7 @@ export class SplitView extends Disposable {
|
||||
return this.viewItems[index].size;
|
||||
}
|
||||
|
||||
private doAddView(view: IView, size: number | Sizing, index = this.viewItems.length, skipLayout?: boolean): void {
|
||||
private doAddView(view: IView<TLayoutContext>, size: number | Sizing, index = this.viewItems.length, skipLayout?: boolean): void {
|
||||
if (this.state !== State.Idle) {
|
||||
throw new Error('Cant modify splitview');
|
||||
}
|
||||
@@ -849,17 +883,19 @@ export class SplitView extends Disposable {
|
||||
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
|
||||
// Layout views
|
||||
let position = 0;
|
||||
let offset = 0;
|
||||
|
||||
for (const viewItem of this.viewItems) {
|
||||
viewItem.layout(position, this.orthogonalSize);
|
||||
position += viewItem.size;
|
||||
viewItem.layout(offset, this.layoutContext);
|
||||
offset += viewItem.size;
|
||||
}
|
||||
|
||||
// Layout sashes
|
||||
this.sashItems.forEach(item => item.sash.layout());
|
||||
this.updateSashEnablement();
|
||||
}
|
||||
|
||||
// Update sashes enablement
|
||||
private updateSashEnablement(): void {
|
||||
let previous = false;
|
||||
const collapsesDown = this.viewItems.map(i => previous = (i.size - i.minimumSize > 0) || previous);
|
||||
|
||||
@@ -873,7 +909,12 @@ export class SplitView extends Disposable {
|
||||
previous = false;
|
||||
const expandsUp = reverseViews.map(i => previous = (i.maximumSize - i.size > 0) || previous).reverse();
|
||||
|
||||
this.sashItems.forEach(({ sash }, index) => {
|
||||
let position = 0;
|
||||
for (let index = 0; index < this.sashItems.length; index++) {
|
||||
const { sash } = this.sashItems[index];
|
||||
const viewItem = this.viewItems[index];
|
||||
position += viewItem.size;
|
||||
|
||||
const min = !(collapsesDown[index] && expandsUp[index + 1]);
|
||||
const max = !(expandsDown[index] && collapsesUp[index + 1]);
|
||||
|
||||
@@ -886,9 +927,9 @@ export class SplitView extends Disposable {
|
||||
const snappedBefore = typeof snapBeforeIndex === 'number' && !this.viewItems[snapBeforeIndex].visible;
|
||||
const snappedAfter = typeof snapAfterIndex === 'number' && !this.viewItems[snapAfterIndex].visible;
|
||||
|
||||
if (snappedBefore && collapsesUp[index]) {
|
||||
if (snappedBefore && collapsesUp[index] && (position > 0 || this.startSnappingEnabled)) {
|
||||
sash.state = SashState.Minimum;
|
||||
} else if (snappedAfter && collapsesDown[index]) {
|
||||
} else if (snappedAfter && collapsesDown[index] && (position < this.contentSize || this.endSnappingEnabled)) {
|
||||
sash.state = SashState.Maximum;
|
||||
} else {
|
||||
sash.state = SashState.Disabled;
|
||||
@@ -900,8 +941,7 @@ export class SplitView extends Disposable {
|
||||
} else {
|
||||
sash.state = SashState.Enabled;
|
||||
}
|
||||
// }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getSashPosition(sash: Sash): number {
|
||||
|
||||
@@ -52,7 +52,7 @@ export class ToolBar extends Disposable {
|
||||
orientation: options.orientation,
|
||||
ariaLabel: options.ariaLabel,
|
||||
actionRunner: options.actionRunner,
|
||||
actionViewItemProvider: (action: Action) => {
|
||||
actionViewItemProvider: (action: IAction) => {
|
||||
|
||||
// Return special action item for the toggle menu action
|
||||
if (action.id === ToggleMenuAction.ID) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'vs/css!./media/tree';
|
||||
import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode } from 'vs/base/browser/dom';
|
||||
import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode, addClasses, removeClasses } from 'vs/base/browser/dom';
|
||||
import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
@@ -27,10 +27,24 @@ import { clamp } from 'vs/base/common/numbers';
|
||||
import { ScrollEvent } from 'vs/base/common/scrollable';
|
||||
import { SetMap } from 'vs/base/common/collections';
|
||||
|
||||
class TreeElementsDragAndDropData<T, TFilterData, TContext> extends ElementsDragAndDropData<T, TContext> {
|
||||
|
||||
set context(context: TContext | undefined) {
|
||||
this.data.context = context;
|
||||
}
|
||||
|
||||
get context(): TContext | undefined {
|
||||
return this.data.context;
|
||||
}
|
||||
|
||||
constructor(private data: ElementsDragAndDropData<ITreeNode<T, TFilterData>, TContext>) {
|
||||
super(data.elements.map(node => node.element));
|
||||
}
|
||||
}
|
||||
|
||||
function asTreeDragAndDropData<T, TFilterData>(data: IDragAndDropData): IDragAndDropData {
|
||||
if (data instanceof ElementsDragAndDropData) {
|
||||
const nodes = (data as ElementsDragAndDropData<ITreeNode<T, TFilterData>>).elements;
|
||||
return new ElementsDragAndDropData(nodes.map(node => node.element));
|
||||
return new TreeElementsDragAndDropData(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
@@ -47,9 +61,9 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
|
||||
return this.dnd.getDragURI(node.element);
|
||||
}
|
||||
|
||||
getDragLabel(nodes: ITreeNode<T, TFilterData>[]): string | undefined {
|
||||
getDragLabel(nodes: ITreeNode<T, TFilterData>[], originalEvent: DragEvent): string | undefined {
|
||||
if (this.dnd.getDragLabel) {
|
||||
return this.dnd.getDragLabel(nodes.map(node => node.element));
|
||||
return this.dnd.getDragLabel(nodes.map(node => node.element), originalEvent);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -87,7 +101,7 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
|
||||
}, 500);
|
||||
}
|
||||
|
||||
if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined') {
|
||||
if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined' || result.feedback) {
|
||||
if (!raw) {
|
||||
const accept = typeof result === 'boolean' ? result : result.accept;
|
||||
const effect = typeof result === 'boolean' ? undefined : result.effect;
|
||||
@@ -121,6 +135,12 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
|
||||
|
||||
this.dnd.drop(asTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, originalEvent);
|
||||
}
|
||||
|
||||
onDragEnd(originalEvent: DragEvent): void {
|
||||
if (this.dnd.onDragEnd) {
|
||||
this.dnd.onDragEnd(originalEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T, TFilterData, TRef>, options?: IAbstractTreeOptions<T, TFilterData>): IListOptions<ITreeNode<T, TFilterData>> | undefined {
|
||||
@@ -141,12 +161,16 @@ function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T,
|
||||
}
|
||||
},
|
||||
accessibilityProvider: options.accessibilityProvider && {
|
||||
...options.accessibilityProvider,
|
||||
getAriaLabel(e) {
|
||||
return options.accessibilityProvider!.getAriaLabel(e.element);
|
||||
},
|
||||
getAriaLevel(node) {
|
||||
return node.depth;
|
||||
}
|
||||
},
|
||||
getActiveDescendantId: options.accessibilityProvider.getActiveDescendantId && (node => {
|
||||
return options.accessibilityProvider!.getActiveDescendantId!(node.element);
|
||||
})
|
||||
},
|
||||
keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && {
|
||||
...options.keyboardNavigationLabelProvider,
|
||||
@@ -211,6 +235,8 @@ export enum RenderIndentGuides {
|
||||
interface ITreeRendererOptions {
|
||||
readonly indent?: number;
|
||||
readonly renderIndentGuides?: RenderIndentGuides;
|
||||
// TODO@joao replace this with collapsible: boolean | 'ondemand'
|
||||
readonly hideTwistiesOfChildlessElements?: boolean;
|
||||
}
|
||||
|
||||
interface IRenderData<TTemplateData> {
|
||||
@@ -244,6 +270,7 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
|
||||
private renderedElements = new Map<T, ITreeNode<T, TFilterData>>();
|
||||
private renderedNodes = new Map<ITreeNode<T, TFilterData>, IRenderData<TTemplateData>>();
|
||||
private indent: number = TreeRenderer.DefaultIndent;
|
||||
private hideTwistiesOfChildlessElements: boolean = false;
|
||||
|
||||
private shouldRenderIndentGuides: boolean = false;
|
||||
private renderedIndentGuides = new SetMap<ITreeNode<T, TFilterData>, HTMLDivElement>();
|
||||
@@ -290,6 +317,10 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof options.hideTwistiesOfChildlessElements !== 'undefined') {
|
||||
this.hideTwistiesOfChildlessElements = options.hideTwistiesOfChildlessElements;
|
||||
}
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): ITreeListTemplateData<TTemplateData> {
|
||||
@@ -309,7 +340,7 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
|
||||
}
|
||||
|
||||
const indent = TreeRenderer.DefaultIndent + (node.depth - 1) * this.indent;
|
||||
templateData.twistie.style.marginLeft = `${indent}px`;
|
||||
templateData.twistie.style.paddingLeft = `${indent}px`;
|
||||
templateData.indent.style.width = `${indent + this.indent - 16}px`;
|
||||
|
||||
this.renderTwistie(node, templateData);
|
||||
@@ -365,10 +396,12 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
|
||||
this.renderer.renderTwistie(node.element, templateData.twistie);
|
||||
}
|
||||
|
||||
toggleClass(templateData.twistie, 'codicon', node.collapsible);
|
||||
toggleClass(templateData.twistie, 'codicon-chevron-down', node.collapsible);
|
||||
toggleClass(templateData.twistie, 'collapsible', node.collapsible);
|
||||
toggleClass(templateData.twistie, 'collapsed', node.collapsible && node.collapsed);
|
||||
if (node.collapsible && (!this.hideTwistiesOfChildlessElements || node.visibleChildrenCount > 0)) {
|
||||
addClasses(templateData.twistie, 'codicon', 'codicon-chevron-down', 'collapsible');
|
||||
toggleClass(templateData.twistie, 'collapsed', node.collapsed);
|
||||
} else {
|
||||
removeClasses(templateData.twistie, 'codicon', 'codicon-chevron-down', 'collapsible', 'collapsed');
|
||||
}
|
||||
|
||||
if (node.collapsible) {
|
||||
templateData.container.setAttribute('aria-expanded', String(!node.collapsed));
|
||||
@@ -430,12 +463,16 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
|
||||
|
||||
nodes.forEach(node => {
|
||||
const ref = model.getNodeLocation(node);
|
||||
const parentRef = model.getParentNodeLocation(ref);
|
||||
try {
|
||||
const parentRef = model.getParentNodeLocation(ref);
|
||||
|
||||
if (node.collapsible && node.children.length > 0 && !node.collapsed) {
|
||||
set.add(node);
|
||||
} else if (parentRef) {
|
||||
set.add(model.getNode(parentRef));
|
||||
if (node.collapsible && node.children.length > 0 && !node.collapsed) {
|
||||
set.add(node);
|
||||
} else if (parentRef) {
|
||||
set.add(model.getNode(parentRef));
|
||||
}
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
});
|
||||
|
||||
@@ -601,14 +638,14 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
|
||||
const controls = append(this.domNode, $('.controls'));
|
||||
|
||||
this._filterOnType = !!tree.options.filterOnType;
|
||||
this.filterOnTypeDomNode = append(controls, $<HTMLInputElement>('input.filter'));
|
||||
this.filterOnTypeDomNode = append(controls, $<HTMLInputElement>('input.filter.codicon.codicon-list-selection'));
|
||||
this.filterOnTypeDomNode.type = 'checkbox';
|
||||
this.filterOnTypeDomNode.checked = this._filterOnType;
|
||||
this.filterOnTypeDomNode.tabIndex = -1;
|
||||
this.updateFilterOnTypeTitle();
|
||||
domEvent(this.filterOnTypeDomNode, 'input')(this.onDidChangeFilterOnType, this, this.disposables);
|
||||
|
||||
this.clearDomNode = append(controls, $<HTMLInputElement>('button.clear'));
|
||||
this.clearDomNode = append(controls, $<HTMLInputElement>('button.clear.codicon.codicon-close'));
|
||||
this.clearDomNode.tabIndex = -1;
|
||||
this.clearDomNode.title = localize('clear', "Clear");
|
||||
|
||||
@@ -657,7 +694,7 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
|
||||
|
||||
const onKeyDown = Event.chain(domEvent(this.view.getHTMLElement(), 'keydown'))
|
||||
.filter(e => !isInputElement(e.target as HTMLElement) || e.target === this.filterOnTypeDomNode)
|
||||
.filter(e => e.key !== 'Dead')
|
||||
.filter(e => e.key !== 'Dead' && !/^Media/.test(e.key))
|
||||
.map(e => new StandardKeyboardEvent(e))
|
||||
.filter(this.keyboardNavigationEventFilter || (() => true))
|
||||
.filter(() => this.automaticKeyboardNavigation || this.triggered)
|
||||
@@ -918,7 +955,6 @@ export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTr
|
||||
readonly collapseByDefault?: boolean; // defaults to false
|
||||
readonly filter?: ITreeFilter<T, TFilterData>;
|
||||
readonly dnd?: ITreeDragAndDrop<T>;
|
||||
readonly autoExpandSingleChildren?: boolean;
|
||||
readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter;
|
||||
readonly expandOnlyOnTwistieClick?: boolean | ((e: T) => boolean);
|
||||
readonly additionalScrollHeight?: number;
|
||||
@@ -1385,8 +1421,13 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
return this.view.renderHeight;
|
||||
}
|
||||
|
||||
get firstVisibleElement(): T {
|
||||
get firstVisibleElement(): T | undefined {
|
||||
const index = this.view.firstVisibleIndex;
|
||||
|
||||
if (index < 0 || index >= this.view.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const node = this.view.element(index);
|
||||
return node.element;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
|
||||
import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper, ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
@@ -19,6 +19,8 @@ import { toggleClass } from 'vs/base/browser/dom';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { ScrollEvent } from 'vs/base/common/scrollable';
|
||||
import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||
import { IThemable } from 'vs/base/common/styler';
|
||||
import { isFilterResult, getVisibleState } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
|
||||
interface IAsyncDataTreeNode<TInput, T> {
|
||||
element: TInput | T;
|
||||
@@ -149,20 +151,24 @@ function asTreeContextMenuEvent<TInput, T>(e: ITreeContextMenuEvent<IAsyncDataTr
|
||||
};
|
||||
}
|
||||
|
||||
export enum ChildrenResolutionReason {
|
||||
Refresh,
|
||||
Expand
|
||||
}
|
||||
class AsyncDataTreeElementsDragAndDropData<TInput, T, TContext> extends ElementsDragAndDropData<T, TContext> {
|
||||
|
||||
export interface IChildrenResolutionEvent<T> {
|
||||
readonly element: T | null;
|
||||
readonly reason: ChildrenResolutionReason;
|
||||
set context(context: TContext | undefined) {
|
||||
this.data.context = context;
|
||||
}
|
||||
|
||||
get context(): TContext | undefined {
|
||||
return this.data.context;
|
||||
}
|
||||
|
||||
constructor(private data: ElementsDragAndDropData<IAsyncDataTreeNode<TInput, T>, TContext>) {
|
||||
super(data.elements.map(node => node.element as T));
|
||||
}
|
||||
}
|
||||
|
||||
function asAsyncDataTreeDragAndDropData<TInput, T>(data: IDragAndDropData): IDragAndDropData {
|
||||
if (data instanceof ElementsDragAndDropData) {
|
||||
const nodes = (data as ElementsDragAndDropData<IAsyncDataTreeNode<TInput, T>>).elements;
|
||||
return new ElementsDragAndDropData(nodes.map(node => node.element));
|
||||
return new AsyncDataTreeElementsDragAndDropData(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
@@ -176,9 +182,9 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
|
||||
return this.dnd.getDragURI(node.element as T);
|
||||
}
|
||||
|
||||
getDragLabel(nodes: IAsyncDataTreeNode<TInput, T>[]): string | undefined {
|
||||
getDragLabel(nodes: IAsyncDataTreeNode<TInput, T>[], originalEvent: DragEvent): string | undefined {
|
||||
if (this.dnd.getDragLabel) {
|
||||
return this.dnd.getDragLabel(nodes.map(node => node.element as T));
|
||||
return this.dnd.getDragLabel(nodes.map(node => node.element as T), originalEvent);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -197,6 +203,12 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
|
||||
drop(data: IDragAndDropData, targetNode: IAsyncDataTreeNode<TInput, T> | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void {
|
||||
this.dnd.drop(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, originalEvent);
|
||||
}
|
||||
|
||||
onDragEnd(originalEvent: DragEvent): void {
|
||||
if (this.dnd.onDragEnd) {
|
||||
this.dnd.onDragEnd(originalEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<TInput, T>, TFilterData> | undefined {
|
||||
@@ -218,9 +230,16 @@ function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOpt
|
||||
}
|
||||
},
|
||||
accessibilityProvider: options.accessibilityProvider && {
|
||||
...options.accessibilityProvider,
|
||||
getAriaLabel(e) {
|
||||
return options.accessibilityProvider!.getAriaLabel(e.element as T);
|
||||
}
|
||||
},
|
||||
getAriaLevel: options.accessibilityProvider!.getAriaLevel && (node => {
|
||||
return options.accessibilityProvider!.getAriaLevel!(node.element as T);
|
||||
}),
|
||||
getActiveDescendantId: options.accessibilityProvider.getActiveDescendantId && (node => {
|
||||
return options.accessibilityProvider!.getActiveDescendantId!(node.element as T);
|
||||
})
|
||||
},
|
||||
filter: options.filter && {
|
||||
filter(e, parentVisibility) {
|
||||
@@ -271,10 +290,10 @@ function dfs<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, fn: (node: IAsyncDa
|
||||
node.children.forEach(child => dfs(child, fn));
|
||||
}
|
||||
|
||||
export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable {
|
||||
export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable, IThemable {
|
||||
|
||||
private readonly tree: ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
|
||||
private readonly root: IAsyncDataTreeNode<TInput, T>;
|
||||
protected readonly tree: ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
|
||||
protected readonly root: IAsyncDataTreeNode<TInput, T>;
|
||||
private readonly nodes = new Map<null | T, IAsyncDataTreeNode<TInput, T>>();
|
||||
private readonly sorter?: ITreeSorter<T>;
|
||||
private readonly collapseByDefault?: { (e: T): boolean; };
|
||||
@@ -282,7 +301,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
private readonly subTreeRefreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, Promise<void>>();
|
||||
private readonly refreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, CancelablePromise<T[]>>();
|
||||
|
||||
private readonly identityProvider?: IIdentityProvider<T>;
|
||||
protected readonly identityProvider?: IIdentityProvider<T>;
|
||||
private readonly autoExpandSingleChildren: boolean;
|
||||
|
||||
private readonly _onDidRender = new Emitter<void>();
|
||||
@@ -323,7 +342,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
get onDidDispose(): Event<void> { return this.tree.onDidDispose; }
|
||||
|
||||
constructor(
|
||||
private user: string,
|
||||
protected user: string,
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<T, TFilterData, any>[],
|
||||
@@ -411,10 +430,6 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
return this.tree.renderHeight;
|
||||
}
|
||||
|
||||
get firstVisibleElement(): T {
|
||||
return this.tree.firstVisibleElement!.element as T;
|
||||
}
|
||||
|
||||
get lastVisibleElement(): T {
|
||||
return this.tree.lastVisibleElement!.element as T;
|
||||
}
|
||||
@@ -445,7 +460,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
|
||||
const viewStateContext = viewState && { viewState, focus: [], selection: [] } as IAsyncDataTreeViewStateContext<TInput, T>;
|
||||
|
||||
await this._updateChildren(input, true, viewStateContext);
|
||||
await this._updateChildren(input, true, false, viewStateContext);
|
||||
|
||||
if (viewStateContext) {
|
||||
this.tree.setFocus(viewStateContext.focus);
|
||||
@@ -457,11 +472,11 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
async updateChildren(element: TInput | T = this.root.element, recursive = true): Promise<void> {
|
||||
await this._updateChildren(element, recursive);
|
||||
async updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false): Promise<void> {
|
||||
await this._updateChildren(element, recursive, rerender);
|
||||
}
|
||||
|
||||
private async _updateChildren(element: TInput | T = this.root.element, recursive = true, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
private async _updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
if (typeof this.root.element === 'undefined') {
|
||||
throw new TreeError(this.user, 'Tree input not set');
|
||||
}
|
||||
@@ -471,7 +486,17 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
await Event.toPromise(this._onDidRender.event);
|
||||
}
|
||||
|
||||
await this.refreshAndRenderNode(this.getDataNode(element), recursive, ChildrenResolutionReason.Refresh, viewStateContext);
|
||||
const node = this.getDataNode(element);
|
||||
await this.refreshAndRenderNode(node, recursive, viewStateContext);
|
||||
|
||||
if (rerender) {
|
||||
try {
|
||||
this.tree.rerender(node);
|
||||
} catch {
|
||||
// missing nodes are fine, this could've resulted from
|
||||
// parallel refresh calls, removing `node` altogether
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resort(element: TInput | T = this.root.element, recursive = true): void {
|
||||
@@ -653,18 +678,9 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
return node;
|
||||
}
|
||||
|
||||
private async refreshAndRenderNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, reason: ChildrenResolutionReason, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
private async refreshAndRenderNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
await this.refreshNode(node, recursive, viewStateContext);
|
||||
this.render(node, viewStateContext);
|
||||
|
||||
if (node !== this.root && this.autoExpandSingleChildren && reason === ChildrenResolutionReason.Expand) {
|
||||
const treeNode = this.tree.getNode(node);
|
||||
const visibleChildren = treeNode.children.filter(node => node.visible);
|
||||
|
||||
if (visibleChildren.length === 1) {
|
||||
await this.tree.expand(visibleChildren[0].element, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async refreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
@@ -752,12 +768,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
|
||||
result = createCancelablePromise(async () => {
|
||||
const children = await this.dataSource.getChildren(node.element!);
|
||||
|
||||
if (this.sorter) {
|
||||
children.sort(this.sorter.compare.bind(this.sorter));
|
||||
}
|
||||
|
||||
return children;
|
||||
return this.processChildren(children);
|
||||
});
|
||||
|
||||
this.refreshPromises.set(node, result);
|
||||
@@ -770,7 +781,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
if (deep) {
|
||||
this.collapse(node.element.element as T);
|
||||
} else {
|
||||
this.refreshAndRenderNode(node.element, false, ChildrenResolutionReason.Expand)
|
||||
this.refreshAndRenderNode(node.element, false)
|
||||
.catch(onUnexpectedError);
|
||||
}
|
||||
}
|
||||
@@ -783,13 +794,14 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
}
|
||||
|
||||
const nodesToForget = new Map<T, IAsyncDataTreeNode<TInput, T>>();
|
||||
const childrenTreeNodesById = new Map<string, ITreeNode<IAsyncDataTreeNode<TInput, T> | null, TFilterData>>();
|
||||
const childrenTreeNodesById = new Map<string, { node: IAsyncDataTreeNode<TInput, T>, collapsed: boolean }>();
|
||||
|
||||
for (const child of node.children) {
|
||||
nodesToForget.set(child.element as T, child);
|
||||
|
||||
if (this.identityProvider) {
|
||||
childrenTreeNodesById.set(child.id!, this.tree.getNode(child));
|
||||
const collapsed = this.tree.isCollapsed(child);
|
||||
childrenTreeNodesById.set(child.id!, { node: child, collapsed });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -810,10 +822,10 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
}
|
||||
|
||||
const id = this.identityProvider.getId(element).toString();
|
||||
const childNode = childrenTreeNodesById.get(id);
|
||||
const result = childrenTreeNodesById.get(id);
|
||||
|
||||
if (childNode) {
|
||||
const asyncDataTreeNode = childNode.element!;
|
||||
if (result) {
|
||||
const asyncDataTreeNode = result.node;
|
||||
|
||||
nodesToForget.delete(asyncDataTreeNode.element as T);
|
||||
this.nodes.delete(asyncDataTreeNode.element as T);
|
||||
@@ -823,8 +835,10 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
asyncDataTreeNode.hasChildren = hasChildren;
|
||||
|
||||
if (recursive) {
|
||||
if (childNode.collapsed) {
|
||||
dfs(asyncDataTreeNode, node => node.stale = true);
|
||||
if (result.collapsed) {
|
||||
asyncDataTreeNode.children.forEach(node => dfs(node, node => this.nodes.delete(node.element as T)));
|
||||
asyncDataTreeNode.children.splice(0, asyncDataTreeNode.children.length);
|
||||
asyncDataTreeNode.stale = true;
|
||||
} else {
|
||||
childrenToRefresh.push(asyncDataTreeNode);
|
||||
}
|
||||
@@ -866,10 +880,16 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
|
||||
node.children.splice(0, node.children.length, ...children);
|
||||
|
||||
// TODO@joao this doesn't take filter into account
|
||||
if (node !== this.root && this.autoExpandSingleChildren && children.length === 1 && childrenToRefresh.length === 0) {
|
||||
children[0].collapsedByDefault = false;
|
||||
childrenToRefresh.push(children[0]);
|
||||
}
|
||||
|
||||
return childrenToRefresh;
|
||||
}
|
||||
|
||||
private render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
|
||||
protected render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
|
||||
const children = node.children.map(node => this.asTreeElement(node, viewStateContext));
|
||||
this.tree.setChildren(node === this.root ? null : node, children);
|
||||
|
||||
@@ -881,6 +901,14 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
}
|
||||
|
||||
protected asTreeElement(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
|
||||
if (node.stale) {
|
||||
return {
|
||||
element: node,
|
||||
collapsible: node.hasChildren,
|
||||
collapsed: true
|
||||
};
|
||||
}
|
||||
|
||||
let collapsed: boolean | undefined;
|
||||
|
||||
if (viewStateContext && viewStateContext.viewState.expanded && node.id && viewStateContext.viewState.expanded.indexOf(node.id) > -1) {
|
||||
@@ -899,6 +927,14 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
};
|
||||
}
|
||||
|
||||
protected processChildren(children: T[]): T[] {
|
||||
if (this.sorter) {
|
||||
children.sort(this.sorter.compare.bind(this.sorter));
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
// view state
|
||||
|
||||
getViewState(): IAsyncDataTreeViewState {
|
||||
@@ -994,6 +1030,12 @@ class CompressibleAsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> i
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
||||
this.renderer.disposeTemplate(templateData.templateData);
|
||||
}
|
||||
@@ -1023,12 +1065,19 @@ function asCompressibleObjectTreeOptions<TInput, T, TFilterData>(options?: IComp
|
||||
}
|
||||
|
||||
export interface ICompressibleAsyncDataTreeOptions<T, TFilterData = void> extends IAsyncDataTreeOptions<T, TFilterData> {
|
||||
readonly compressionEnabled?: boolean;
|
||||
readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider<T>;
|
||||
}
|
||||
|
||||
export interface ICompressibleAsyncDataTreeOptionsUpdate extends IAsyncDataTreeOptionsUpdate {
|
||||
readonly compressionEnabled?: boolean;
|
||||
}
|
||||
|
||||
export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> {
|
||||
|
||||
protected readonly tree!: CompressibleObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
|
||||
protected readonly compressibleNodeMapper: CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData> = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node));
|
||||
private filter?: ITreeFilter<T, TFilterData>;
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
@@ -1037,9 +1086,10 @@ export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends As
|
||||
private compressionDelegate: ITreeCompressionDelegate<T>,
|
||||
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
|
||||
dataSource: IAsyncDataSource<TInput, T>,
|
||||
options: IAsyncDataTreeOptions<T, TFilterData> = {}
|
||||
options: ICompressibleAsyncDataTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
super(user, container, virtualDelegate, renderers, dataSource, options);
|
||||
this.filter = options.filter;
|
||||
}
|
||||
|
||||
protected createTree(
|
||||
@@ -1062,4 +1112,137 @@ export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends As
|
||||
...super.asTreeElement(node, viewStateContext)
|
||||
};
|
||||
}
|
||||
|
||||
updateOptions(options: ICompressibleAsyncDataTreeOptionsUpdate = {}): void {
|
||||
this.tree.updateOptions(options);
|
||||
}
|
||||
|
||||
getViewState(): IAsyncDataTreeViewState {
|
||||
if (!this.identityProvider) {
|
||||
throw new TreeError(this.user, 'Can\'t get tree view state without an identity provider');
|
||||
}
|
||||
|
||||
const getId = (element: T) => this.identityProvider!.getId(element).toString();
|
||||
const focus = this.getFocus().map(getId);
|
||||
const selection = this.getSelection().map(getId);
|
||||
|
||||
const expanded: string[] = [];
|
||||
const root = this.tree.getCompressedTreeNode();
|
||||
const queue = [root];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const node = queue.shift()!;
|
||||
|
||||
if (node !== root && node.collapsible && !node.collapsed) {
|
||||
for (const asyncNode of node.element!.elements) {
|
||||
expanded.push(getId(asyncNode.element as T));
|
||||
}
|
||||
}
|
||||
|
||||
queue.push(...node.children);
|
||||
}
|
||||
|
||||
return { focus, selection, expanded, scrollTop: this.scrollTop };
|
||||
}
|
||||
|
||||
protected render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
|
||||
if (!this.identityProvider) {
|
||||
return super.render(node, viewStateContext);
|
||||
}
|
||||
|
||||
// Preserve traits across compressions. Hacky but does the trick.
|
||||
// This is hard to fix properly since it requires rewriting the traits
|
||||
// across trees and lists. Let's just keep it this way for now.
|
||||
const getId = (element: T) => this.identityProvider!.getId(element).toString();
|
||||
const getUncompressedIds = (nodes: IAsyncDataTreeNode<TInput, T>[]): Set<string> => {
|
||||
const result = new Set<string>();
|
||||
|
||||
for (const node of nodes) {
|
||||
const compressedNode = this.tree.getCompressedTreeNode(node === this.root ? null : node);
|
||||
|
||||
if (!compressedNode.element) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const node of compressedNode.element.elements) {
|
||||
result.add(getId(node.element as T));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const oldSelection = getUncompressedIds(this.tree.getSelection() as IAsyncDataTreeNode<TInput, T>[]);
|
||||
const oldFocus = getUncompressedIds(this.tree.getFocus() as IAsyncDataTreeNode<TInput, T>[]);
|
||||
|
||||
super.render(node, viewStateContext);
|
||||
|
||||
const selection = this.getSelection();
|
||||
let didChangeSelection = false;
|
||||
|
||||
const focus = this.getFocus();
|
||||
let didChangeFocus = false;
|
||||
|
||||
const visit = (node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>> | null, TFilterData>) => {
|
||||
const compressedNode = node.element;
|
||||
|
||||
if (compressedNode) {
|
||||
for (let i = 0; i < compressedNode.elements.length; i++) {
|
||||
const id = getId(compressedNode.elements[i].element as T);
|
||||
|
||||
if (oldSelection.has(id)) {
|
||||
selection.push(compressedNode.elements[compressedNode.elements.length - 1].element as T);
|
||||
didChangeSelection = true;
|
||||
}
|
||||
|
||||
if (oldFocus.has(id)) {
|
||||
focus.push(compressedNode.elements[compressedNode.elements.length - 1].element as T);
|
||||
didChangeFocus = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.children.forEach(visit);
|
||||
};
|
||||
|
||||
visit(this.tree.getCompressedTreeNode(node === this.root ? null : node));
|
||||
|
||||
if (didChangeSelection) {
|
||||
this.setSelection(selection);
|
||||
}
|
||||
|
||||
if (didChangeFocus) {
|
||||
this.setFocus(focus);
|
||||
}
|
||||
}
|
||||
|
||||
// For compressed async data trees, `TreeVisibility.Recurse` doesn't currently work
|
||||
// and we have to filter everything beforehand
|
||||
// Related to #85193 and #85835
|
||||
protected processChildren(children: T[]): T[] {
|
||||
if (this.filter) {
|
||||
children = children.filter(e => {
|
||||
const result = this.filter!.filter(e, TreeVisibility.Visible);
|
||||
const visibility = getVisibility(result);
|
||||
|
||||
if (visibility === TreeVisibility.Recurse) {
|
||||
throw new Error('Recursive tree visibility not supported in async data compressed trees');
|
||||
}
|
||||
|
||||
return visibility === TreeVisibility.Visible;
|
||||
});
|
||||
}
|
||||
|
||||
return super.processChildren(children);
|
||||
}
|
||||
}
|
||||
|
||||
function getVisibility<TFilterData>(filterResult: TreeFilterResult<TFilterData>): TreeVisibility {
|
||||
if (typeof filterResult === 'boolean') {
|
||||
return filterResult ? TreeVisibility.Visible : TreeVisibility.Hidden;
|
||||
} else if (isFilterResult(filterResult)) {
|
||||
return getVisibleState(filterResult.visibility);
|
||||
} else {
|
||||
return getVisibleState(filterResult);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IObjectTreeModelOptions, ObjectTreeModel, IObjectTreeModel } from 'vs/b
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
export interface ICompressedTreeElement<T> extends ITreeElement<T> {
|
||||
readonly children?: Iterator<ICompressedTreeElement<T>> | ICompressedTreeElement<T>[];
|
||||
readonly children?: ISequence<ICompressedTreeElement<T>>;
|
||||
readonly incompressible?: boolean;
|
||||
}
|
||||
|
||||
@@ -100,16 +100,15 @@ export function decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>): IC
|
||||
|
||||
function splice<T>(treeElement: ICompressedTreeElement<T>, element: T, children: Iterator<ICompressedTreeElement<T>>): ICompressedTreeElement<T> {
|
||||
if (treeElement.element === element) {
|
||||
return { element, children };
|
||||
return { ...treeElement, children };
|
||||
}
|
||||
|
||||
return {
|
||||
...treeElement,
|
||||
children: Iterator.map(Iterator.from(treeElement.children), e => splice(e, element, children))
|
||||
};
|
||||
return { ...treeElement, children: Iterator.map(Iterator.from(treeElement.children), e => splice(e, element, children)) };
|
||||
}
|
||||
|
||||
interface ICompressedObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<ICompressedTreeNode<T>, TFilterData> { }
|
||||
interface ICompressedObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<ICompressedTreeNode<T>, TFilterData> {
|
||||
readonly compressionEnabled?: boolean;
|
||||
}
|
||||
|
||||
// Exported only for test reasons, do not use directly
|
||||
export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<ICompressedTreeNode<T> | null, TFilterData, T | null> {
|
||||
@@ -122,7 +121,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
|
||||
private model: ObjectTreeModel<ICompressedTreeNode<T>, TFilterData>;
|
||||
private nodes = new Map<T | null, ICompressedTreeNode<T>>();
|
||||
private enabled: boolean = true;
|
||||
private enabled: boolean;
|
||||
|
||||
get size(): number { return this.nodes.size; }
|
||||
|
||||
@@ -132,13 +131,13 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e
|
||||
options: ICompressedObjectTreeModelOptions<T, TFilterData> = {}
|
||||
) {
|
||||
this.model = new ObjectTreeModel(user, list, options);
|
||||
this.enabled = typeof options.compressionEnabled === 'undefined' ? true : options.compressionEnabled;
|
||||
}
|
||||
|
||||
setChildren(
|
||||
element: T | null,
|
||||
children: ISequence<ICompressedTreeElement<T>> | undefined
|
||||
): void {
|
||||
|
||||
if (element === null) {
|
||||
const compressedChildren = Iterator.map(Iterator.from(children), this.enabled ? compress : noCompress);
|
||||
this._setChildren(null, compressedChildren);
|
||||
@@ -368,6 +367,7 @@ function mapOptions<T, TFilterData>(compressedNodeUnwrapper: CompressedNodeUnwra
|
||||
}
|
||||
|
||||
export interface ICompressibleObjectTreeModelOptions<T, TFilterData> extends IObjectTreeModelOptions<T, TFilterData> {
|
||||
readonly compressionEnabled?: boolean;
|
||||
readonly elementMapper?: ElementMapper<T>;
|
||||
}
|
||||
|
||||
@@ -493,7 +493,7 @@ export class CompressibleObjectTreeModel<T extends NonNullable<any>, TFilterData
|
||||
return this.model.resort(element, recursive);
|
||||
}
|
||||
|
||||
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData> {
|
||||
return this.model.getNode(element) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
|
||||
getCompressedTreeNode(location: T | null = null): ITreeNode<ICompressedTreeNode<T> | null, TFilterData> {
|
||||
return this.model.getNode(location);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list
|
||||
import { Iterator } from 'vs/base/common/iterator';
|
||||
|
||||
export interface IDataTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
|
||||
sorter?: ITreeSorter<T>;
|
||||
readonly sorter?: ITreeSorter<T>;
|
||||
}
|
||||
|
||||
export interface IDataTreeViewState {
|
||||
|
||||
@@ -442,6 +442,7 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
|
||||
if (visibility === TreeVisibility.Hidden) {
|
||||
node.visible = false;
|
||||
node.renderNodeCount = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-panel-view .panel > .panel-header h3.title {
|
||||
.monaco-pane-view .pane > .pane-header h3.title {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-size: 11px;
|
||||
-webkit-margin-before: 0;
|
||||
-webkit-margin-after: 0;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 18px;
|
||||
left: 16px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
.monaco-tl-twistie {
|
||||
font-size: 10px;
|
||||
text-align: right;
|
||||
margin-right: 6px;
|
||||
padding-right: 6px;
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
display: flex !important;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISequence } from 'vs/base/common/iterator';
|
||||
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { AbstractTree, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
@@ -56,7 +56,7 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
|
||||
}
|
||||
|
||||
interface ICompressedTreeNodeProvider<T, TFilterData> {
|
||||
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData>;
|
||||
getCompressedTreeNode(location: T | null): ITreeNode<ICompressedTreeNode<T> | null, TFilterData>;
|
||||
}
|
||||
|
||||
export interface ICompressibleTreeRenderer<T, TFilterData = void, TTemplateData = void> extends ITreeRenderer<T, TFilterData, TTemplateData> {
|
||||
@@ -69,7 +69,7 @@ interface CompressibleTemplateData<T, TFilterData, TTemplateData> {
|
||||
readonly data: TTemplateData;
|
||||
}
|
||||
|
||||
class CompressibleRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<T, TFilterData, CompressibleTemplateData<T, TFilterData, TTemplateData>> {
|
||||
class CompressibleRenderer<T extends NonNullable<any>, TFilterData, TTemplateData> implements ITreeRenderer<T, TFilterData, CompressibleTemplateData<T, TFilterData, TTemplateData>> {
|
||||
|
||||
readonly templateId: string;
|
||||
readonly onDidChangeTwistieState: Event<T> | undefined;
|
||||
@@ -93,7 +93,7 @@ class CompressibleRenderer<T, TFilterData, TTemplateData> implements ITreeRender
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: CompressibleTemplateData<T, TFilterData, TTemplateData>, height: number | undefined): void {
|
||||
const compressedTreeNode = this.compressedTreeNodeProvider.getCompressedTreeNode(node.element);
|
||||
const compressedTreeNode = this.compressedTreeNodeProvider.getCompressedTreeNode(node.element) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
|
||||
|
||||
if (compressedTreeNode.element.elements.length === 1) {
|
||||
templateData.compressedTreeNode = undefined;
|
||||
@@ -132,6 +132,7 @@ export interface ICompressibleKeyboardNavigationLabelProvider<T> extends IKeyboa
|
||||
}
|
||||
|
||||
export interface ICompressibleObjectTreeOptions<T, TFilterData = void> extends IObjectTreeOptions<T, TFilterData> {
|
||||
readonly compressionEnabled?: boolean;
|
||||
readonly elementMapper?: ElementMapper<T>;
|
||||
readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider<T>;
|
||||
}
|
||||
@@ -144,7 +145,7 @@ function asObjectTreeOptions<T, TFilterData>(compressedTreeNodeProvider: () => I
|
||||
let compressedTreeNode: ITreeNode<ICompressedTreeNode<T>, TFilterData>;
|
||||
|
||||
try {
|
||||
compressedTreeNode = compressedTreeNodeProvider().getCompressedTreeNode(e);
|
||||
compressedTreeNode = compressedTreeNodeProvider().getCompressedTreeNode(e) as ITreeNode<ICompressedTreeNode<T>, TFilterData>;
|
||||
} catch {
|
||||
return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e);
|
||||
}
|
||||
@@ -159,6 +160,10 @@ function asObjectTreeOptions<T, TFilterData>(compressedTreeNodeProvider: () => I
|
||||
};
|
||||
}
|
||||
|
||||
export interface ICompressibleObjectTreeOptionsUpdate extends IAbstractTreeOptionsUpdate {
|
||||
readonly compressionEnabled?: boolean;
|
||||
}
|
||||
|
||||
export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = void> extends ObjectTree<T, TFilterData> implements ICompressedTreeNodeProvider<T, TFilterData> {
|
||||
|
||||
protected model!: CompressibleObjectTreeModel<T, TFilterData>;
|
||||
@@ -171,7 +176,7 @@ export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = vo
|
||||
options: ICompressibleObjectTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
const compressedTreeNodeProvider = () => this;
|
||||
const compressibleRenderers = renderers.map(r => new CompressibleRenderer(compressedTreeNodeProvider, r));
|
||||
const compressibleRenderers = renderers.map(r => new CompressibleRenderer<T, TFilterData, any>(compressedTreeNodeProvider, r));
|
||||
super(user, container, delegate, compressibleRenderers, asObjectTreeOptions(compressedTreeNodeProvider, options));
|
||||
}
|
||||
|
||||
@@ -183,15 +188,15 @@ export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = vo
|
||||
return new CompressibleObjectTreeModel(user, view, options);
|
||||
}
|
||||
|
||||
isCompressionEnabled(): boolean {
|
||||
return this.model.isCompressionEnabled();
|
||||
updateOptions(optionsUpdate: ICompressibleObjectTreeOptionsUpdate = {}): void {
|
||||
super.updateOptions(optionsUpdate);
|
||||
|
||||
if (typeof optionsUpdate.compressionEnabled !== 'undefined') {
|
||||
this.model.setCompressionEnabled(optionsUpdate.compressionEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
setCompressionEnabled(enabled: boolean): void {
|
||||
this.model.setCompressionEnabled(enabled);
|
||||
}
|
||||
|
||||
getCompressedTreeNode(element: T): ITreeNode<ICompressedTreeNode<T>, TFilterData> {
|
||||
return this.model.getCompressedTreeNode(element)!;
|
||||
getCompressedTreeNode(element: T | null = null): ITreeNode<ICompressedTreeNode<T> | null, TFilterData> {
|
||||
return this.model.getCompressedTreeNode(element);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,7 +257,12 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
throw new TreeError(this.user, `Invalid getParentNodeLocation call`);
|
||||
}
|
||||
|
||||
const node = this.nodes.get(element)!;
|
||||
const node = this.nodes.get(element);
|
||||
|
||||
if (!node) {
|
||||
throw new TreeError(this.user, `Tree element not found: ${element}`);
|
||||
}
|
||||
|
||||
const location = this.model.getNodeLocation(node);
|
||||
const parentLocation = this.model.getParentNodeLocation(location);
|
||||
const parent = this.model.getNode(parentLocation);
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as dom from 'vs/base/browser/dom';
|
||||
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Gesture } from 'vs/base/browser/touch';
|
||||
|
||||
export abstract class Widget extends Disposable {
|
||||
|
||||
@@ -49,4 +50,8 @@ export abstract class Widget extends Disposable {
|
||||
protected onchange(domNode: HTMLElement, listener: (e: Event) => void): void {
|
||||
this._register(dom.addDisposableListener(domNode, dom.EventType.CHANGE, listener));
|
||||
}
|
||||
|
||||
protected ignoreGesture(domNode: HTMLElement): void {
|
||||
Gesture.ignoreTarget(domNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -764,5 +764,5 @@ export async function retry<T>(task: ITask<Promise<T>>, delay: number, retries:
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(lastError);
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,14 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as streams from 'vs/base/common/stream';
|
||||
|
||||
declare var Buffer: any;
|
||||
export const hasBuffer = (typeof Buffer !== 'undefined');
|
||||
|
||||
const hasBuffer = (typeof Buffer !== 'undefined');
|
||||
const hasTextEncoder = (typeof TextEncoder !== 'undefined');
|
||||
const hasTextDecoder = (typeof TextDecoder !== 'undefined');
|
||||
|
||||
let textEncoder: TextEncoder | null;
|
||||
let textDecoder: TextDecoder | null;
|
||||
@@ -31,11 +37,13 @@ export class VSBuffer {
|
||||
static fromString(source: string): VSBuffer {
|
||||
if (hasBuffer) {
|
||||
return new VSBuffer(Buffer.from(source));
|
||||
} else {
|
||||
} else if (hasTextEncoder) {
|
||||
if (!textEncoder) {
|
||||
textEncoder = new TextEncoder();
|
||||
}
|
||||
return new VSBuffer(textEncoder.encode(source));
|
||||
} else {
|
||||
return new VSBuffer(strings.encodeUTF8(source));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,11 +77,13 @@ export class VSBuffer {
|
||||
toString(): string {
|
||||
if (hasBuffer) {
|
||||
return this.buffer.toString();
|
||||
} else {
|
||||
} else if (hasTextDecoder) {
|
||||
if (!textDecoder) {
|
||||
textDecoder = new TextDecoder();
|
||||
}
|
||||
return textDecoder.decode(this.buffer);
|
||||
} else {
|
||||
return strings.decodeUTF8(this.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,335 +142,32 @@ function writeUInt8(destination: Uint8Array, value: number, offset: number): voi
|
||||
destination[offset] = value;
|
||||
}
|
||||
|
||||
export interface VSBufferReadable {
|
||||
export interface VSBufferReadable extends streams.Readable<VSBuffer> { }
|
||||
|
||||
/**
|
||||
* Read data from the underlying source. Will return
|
||||
* null to indicate that no more data can be read.
|
||||
*/
|
||||
read(): VSBuffer | null;
|
||||
}
|
||||
export interface VSBufferReadableStream extends streams.ReadableStream<VSBuffer> { }
|
||||
|
||||
export interface ReadableStream<T> {
|
||||
export interface VSBufferWriteableStream extends streams.WriteableStream<VSBuffer> { }
|
||||
|
||||
/**
|
||||
* The 'data' event is emitted whenever the stream is
|
||||
* relinquishing ownership of a chunk of data to a consumer.
|
||||
*/
|
||||
on(event: 'data', callback: (chunk: T) => void): void;
|
||||
|
||||
/**
|
||||
* Emitted when any error occurs.
|
||||
*/
|
||||
on(event: 'error', callback: (err: any) => void): void;
|
||||
|
||||
/**
|
||||
* The 'end' event is emitted when there is no more data
|
||||
* to be consumed from the stream. The 'end' event will
|
||||
* not be emitted unless the data is completely consumed.
|
||||
*/
|
||||
on(event: 'end', callback: () => void): void;
|
||||
|
||||
/**
|
||||
* Stops emitting any events until resume() is called.
|
||||
*/
|
||||
pause?(): void;
|
||||
|
||||
/**
|
||||
* Starts emitting events again after pause() was called.
|
||||
*/
|
||||
resume?(): void;
|
||||
|
||||
/**
|
||||
* Destroys the stream and stops emitting any event.
|
||||
*/
|
||||
destroy?(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A readable stream that sends data via VSBuffer.
|
||||
*/
|
||||
export interface VSBufferReadableStream extends ReadableStream<VSBuffer> {
|
||||
pause(): void;
|
||||
resume(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export function isVSBufferReadableStream(obj: any): obj is VSBufferReadableStream {
|
||||
const candidate: VSBufferReadableStream = obj;
|
||||
|
||||
return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to fully read a VSBuffer readable into a single buffer.
|
||||
*/
|
||||
export function readableToBuffer(readable: VSBufferReadable): VSBuffer {
|
||||
const chunks: VSBuffer[] = [];
|
||||
|
||||
let chunk: VSBuffer | null;
|
||||
while (chunk = readable.read()) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
return VSBuffer.concat(chunks);
|
||||
return streams.consumeReadable<VSBuffer>(readable, chunks => VSBuffer.concat(chunks));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to convert a buffer into a readable buffer.
|
||||
*/
|
||||
export function bufferToReadable(buffer: VSBuffer): VSBufferReadable {
|
||||
let done = false;
|
||||
|
||||
return {
|
||||
read: () => {
|
||||
if (done) {
|
||||
return null;
|
||||
}
|
||||
|
||||
done = true;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
};
|
||||
return streams.toReadable<VSBuffer>(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to fully read a VSBuffer stream into a single buffer.
|
||||
*/
|
||||
export function streamToBuffer(stream: VSBufferReadableStream): Promise<VSBuffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: VSBuffer[] = [];
|
||||
|
||||
stream.on('data', chunk => chunks.push(chunk));
|
||||
stream.on('error', error => reject(error));
|
||||
stream.on('end', () => resolve(VSBuffer.concat(chunks)));
|
||||
});
|
||||
export function streamToBuffer(stream: streams.ReadableStream<VSBuffer>): Promise<VSBuffer> {
|
||||
return streams.consumeStream<VSBuffer>(stream, chunks => VSBuffer.concat(chunks));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a VSBufferStream from an existing VSBuffer.
|
||||
*/
|
||||
export function bufferToStream(buffer: VSBuffer): VSBufferReadableStream {
|
||||
const stream = writeableBufferStream();
|
||||
|
||||
stream.end(buffer);
|
||||
|
||||
return stream;
|
||||
export function bufferToStream(buffer: VSBuffer): streams.ReadableStream<VSBuffer> {
|
||||
return streams.toStream<VSBuffer>(buffer, chunks => VSBuffer.concat(chunks));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a VSBufferStream from a Uint8Array stream.
|
||||
*/
|
||||
export function toVSBufferReadableStream(stream: ReadableStream<Uint8Array | string>): VSBufferReadableStream {
|
||||
const vsbufferStream = writeableBufferStream();
|
||||
|
||||
stream.on('data', data => vsbufferStream.write(typeof data === 'string' ? VSBuffer.fromString(data) : VSBuffer.wrap(data)));
|
||||
stream.on('end', () => vsbufferStream.end());
|
||||
stream.on('error', error => vsbufferStream.error(error));
|
||||
|
||||
return vsbufferStream;
|
||||
export function streamToBufferReadableStream(stream: streams.ReadableStreamEvents<Uint8Array | string>): streams.ReadableStream<VSBuffer> {
|
||||
return streams.transform<Uint8Array | string, VSBuffer>(stream, { data: data => typeof data === 'string' ? VSBuffer.fromString(data) : VSBuffer.wrap(data) }, chunks => VSBuffer.concat(chunks));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a VSBufferStream that can be pushed
|
||||
* buffers to. Will only start to emit data when a listener
|
||||
* is added.
|
||||
*/
|
||||
export function writeableBufferStream(): VSBufferWriteableStream {
|
||||
return new VSBufferWriteableStreamImpl();
|
||||
}
|
||||
|
||||
export interface VSBufferWriteableStream extends VSBufferReadableStream {
|
||||
write(chunk: VSBuffer): void;
|
||||
error(error: Error): void;
|
||||
end(result?: VSBuffer | Error): void;
|
||||
}
|
||||
|
||||
class VSBufferWriteableStreamImpl implements VSBufferWriteableStream {
|
||||
|
||||
private readonly state = {
|
||||
flowing: false,
|
||||
ended: false,
|
||||
destroyed: false
|
||||
};
|
||||
|
||||
private readonly buffer = {
|
||||
data: [] as VSBuffer[],
|
||||
error: [] as Error[]
|
||||
};
|
||||
|
||||
private readonly listeners = {
|
||||
data: [] as { (chunk: VSBuffer): void }[],
|
||||
error: [] as { (error: Error): void }[],
|
||||
end: [] as { (): void }[]
|
||||
};
|
||||
|
||||
pause(): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.flowing = false;
|
||||
}
|
||||
|
||||
resume(): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.state.flowing) {
|
||||
this.state.flowing = true;
|
||||
|
||||
// emit buffered events
|
||||
this.flowData();
|
||||
this.flowErrors();
|
||||
this.flowEnd();
|
||||
}
|
||||
}
|
||||
|
||||
write(chunk: VSBuffer): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// flowing: directly send the data to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.data.forEach(listener => listener(chunk));
|
||||
}
|
||||
|
||||
// not yet flowing: buffer data until flowing
|
||||
else {
|
||||
this.buffer.data.push(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
error(error: Error): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// flowing: directly send the error to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.error.forEach(listener => listener(error));
|
||||
}
|
||||
|
||||
// not yet flowing: buffer errors until flowing
|
||||
else {
|
||||
this.buffer.error.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
end(result?: VSBuffer | Error): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// end with data or error if provided
|
||||
if (result instanceof Error) {
|
||||
this.error(result);
|
||||
} else if (result) {
|
||||
this.write(result);
|
||||
}
|
||||
|
||||
// flowing: send end event to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.end.forEach(listener => listener());
|
||||
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
// not yet flowing: remember state
|
||||
else {
|
||||
this.state.ended = true;
|
||||
}
|
||||
}
|
||||
|
||||
on(event: 'data', callback: (chunk: VSBuffer) => void): void;
|
||||
on(event: 'error', callback: (err: any) => void): void;
|
||||
on(event: 'end', callback: () => void): void;
|
||||
on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case 'data':
|
||||
this.listeners.data.push(callback);
|
||||
|
||||
// switch into flowing mode as soon as the first 'data'
|
||||
// listener is added and we are not yet in flowing mode
|
||||
this.resume();
|
||||
|
||||
break;
|
||||
|
||||
case 'end':
|
||||
this.listeners.end.push(callback);
|
||||
|
||||
// emit 'end' event directly if we are flowing
|
||||
// and the end has already been reached
|
||||
//
|
||||
// finish() when it went through
|
||||
if (this.state.flowing && this.flowEnd()) {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
this.listeners.error.push(callback);
|
||||
|
||||
// emit buffered 'error' events unless done already
|
||||
// now that we know that we have at least one listener
|
||||
if (this.state.flowing) {
|
||||
this.flowErrors();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private flowData(): void {
|
||||
if (this.buffer.data.length > 0) {
|
||||
const fullDataBuffer = VSBuffer.concat(this.buffer.data);
|
||||
|
||||
this.listeners.data.forEach(listener => listener(fullDataBuffer));
|
||||
|
||||
this.buffer.data.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private flowErrors(): void {
|
||||
if (this.listeners.error.length > 0) {
|
||||
for (const error of this.buffer.error) {
|
||||
this.listeners.error.forEach(listener => listener(error));
|
||||
}
|
||||
|
||||
this.buffer.error.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private flowEnd(): boolean {
|
||||
if (this.state.ended) {
|
||||
this.listeners.end.forEach(listener => listener());
|
||||
|
||||
return this.listeners.end.length > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (!this.state.destroyed) {
|
||||
this.state.destroyed = true;
|
||||
this.state.ended = true;
|
||||
|
||||
this.buffer.data.length = 0;
|
||||
this.buffer.error.length = 0;
|
||||
|
||||
this.listeners.data.length = 0;
|
||||
this.listeners.error.length = 0;
|
||||
this.listeners.end.length = 0;
|
||||
}
|
||||
}
|
||||
export function newWriteableBufferStream(): streams.WriteableStream<VSBuffer> {
|
||||
return streams.newWriteableStream<VSBuffer>(chunks => VSBuffer.concat(chunks));
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ export class Cache<T> {
|
||||
|
||||
const cts = new CancellationTokenSource();
|
||||
const promise = this.task(cts.token);
|
||||
promise.finally(() => cts.dispose());
|
||||
|
||||
this.result = {
|
||||
promise,
|
||||
|
||||
@@ -399,6 +399,23 @@ export class Color {
|
||||
return new Color(new RGBA(r, g, b, a));
|
||||
}
|
||||
|
||||
makeOpaque(opaqueBackground: Color): Color {
|
||||
if (this.isOpaque() || opaqueBackground.rgba.a !== 1) {
|
||||
// only allow to blend onto a non-opaque color onto a opaque color
|
||||
return this;
|
||||
}
|
||||
|
||||
const { r, g, b, a } = this.rgba;
|
||||
|
||||
// https://stackoverflow.com/questions/12228548/finding-equivalent-color-with-opacity
|
||||
return new Color(new RGBA(
|
||||
opaqueBackground.rgba.r - a * (opaqueBackground.rgba.r - r),
|
||||
opaqueBackground.rgba.g - a * (opaqueBackground.rgba.g - g),
|
||||
opaqueBackground.rgba.b - a * (opaqueBackground.rgba.b - b),
|
||||
1
|
||||
));
|
||||
}
|
||||
|
||||
flatten(...backgrounds: Color[]): Color {
|
||||
const background = backgrounds.reduceRight((accumulator, color) => {
|
||||
return Color._flatten(color, accumulator);
|
||||
|
||||
@@ -195,7 +195,6 @@ export function getErrorMessage(err: any): string {
|
||||
return String(err);
|
||||
}
|
||||
|
||||
|
||||
export class NotImplementedError extends Error {
|
||||
constructor(message?: string) {
|
||||
super('NotImplemented');
|
||||
|
||||
@@ -7,6 +7,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { once as onceFn } from 'vs/base/common/functional';
|
||||
import { Disposable, IDisposable, toDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
/**
|
||||
* To an event a function with one or zero parameters
|
||||
@@ -653,27 +654,39 @@ export interface IWaitUntil {
|
||||
|
||||
export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {
|
||||
|
||||
private _asyncDeliveryQueue?: [Listener<T>, T, Promise<any>[]][];
|
||||
private _asyncDeliveryQueue?: LinkedList<[Listener<T>, Omit<T, 'waitUntil'>]>;
|
||||
|
||||
async fireAsync(eventFn: (thenables: Promise<any>[], listener: Function) => T): Promise<void> {
|
||||
async fireAsync(data: Omit<T, 'waitUntil'>, token: CancellationToken, promiseJoin?: (p: Promise<any>, listener: Function) => Promise<any>): Promise<void> {
|
||||
if (!this._listeners) {
|
||||
return;
|
||||
}
|
||||
|
||||
// put all [listener,event]-pairs into delivery queue
|
||||
// then emit all event. an inner/nested event might be
|
||||
// the driver of this
|
||||
if (!this._asyncDeliveryQueue) {
|
||||
this._asyncDeliveryQueue = [];
|
||||
this._asyncDeliveryQueue = new LinkedList();
|
||||
}
|
||||
|
||||
for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) {
|
||||
const thenables: Promise<void>[] = [];
|
||||
this._asyncDeliveryQueue.push([e.value, eventFn(thenables, typeof e.value === 'function' ? e.value : e.value[0]), thenables]);
|
||||
this._asyncDeliveryQueue.push([e.value, data]);
|
||||
}
|
||||
|
||||
while (this._asyncDeliveryQueue.length > 0) {
|
||||
const [listener, event, thenables] = this._asyncDeliveryQueue.shift()!;
|
||||
while (this._asyncDeliveryQueue.size > 0 && !token.isCancellationRequested) {
|
||||
|
||||
const [listener, data] = this._asyncDeliveryQueue.shift()!;
|
||||
const thenables: Promise<any>[] = [];
|
||||
|
||||
const event = <T>{
|
||||
...data,
|
||||
waitUntil: (p: Promise<any>): void => {
|
||||
if (Object.isFrozen(thenables)) {
|
||||
throw new Error('waitUntil can NOT be called asynchronous');
|
||||
}
|
||||
if (promiseJoin) {
|
||||
p = promiseJoin(p, typeof listener === 'function' ? listener : listener[0]);
|
||||
}
|
||||
thenables.push(p);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
if (typeof listener === 'function') {
|
||||
listener.call(undefined, event);
|
||||
@@ -688,7 +701,7 @@ export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {
|
||||
// freeze thenables-collection to enforce sync-calls to
|
||||
// wait until and then wait for all thenables to resolve
|
||||
Object.freeze(thenables);
|
||||
await Promise.all(thenables);
|
||||
await Promise.all(thenables).catch(e => onUnexpectedError(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,7 +543,7 @@ export function fuzzyScore(pattern: string, patternLow: string, patternStart: nu
|
||||
const patternLen = pattern.length > _maxLen ? _maxLen : pattern.length;
|
||||
const wordLen = word.length > _maxLen ? _maxLen : word.length;
|
||||
|
||||
if (patternStart >= patternLen || wordStart >= wordLen || patternLen > wordLen) {
|
||||
if (patternStart >= patternLen || wordStart >= wordLen || (patternLen - patternStart) > (wordLen - wordStart)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -172,13 +172,28 @@ export module Iterator {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function chain<T>(iterator: Iterator<T>): ChainableIterator<T> {
|
||||
return new ChainableIterator(iterator);
|
||||
}
|
||||
}
|
||||
|
||||
export class ChainableIterator<T> implements Iterator<T> {
|
||||
|
||||
constructor(private it: Iterator<T>) { }
|
||||
|
||||
next(): IteratorResult<T> { return this.it.next(); }
|
||||
map<R>(fn: (t: T) => R): ChainableIterator<R> { return new ChainableIterator(Iterator.map(this.it, fn)); }
|
||||
filter(fn: (t: T) => boolean): ChainableIterator<T> { return new ChainableIterator(Iterator.filter(this.it, fn)); }
|
||||
}
|
||||
|
||||
export type ISequence<T> = Iterator<T> | T[];
|
||||
|
||||
export function getSequenceIterator<T>(arg: Iterator<T> | T[]): Iterator<T> {
|
||||
export function getSequenceIterator<T>(arg: ISequence<T> | undefined): Iterator<T> {
|
||||
if (Array.isArray(arg)) {
|
||||
return Iterator.fromArray(arg);
|
||||
} else if (!arg) {
|
||||
return Iterator.empty();
|
||||
} else {
|
||||
return arg;
|
||||
}
|
||||
@@ -271,7 +286,7 @@ export interface INavigator<T> extends INextIterator<T> {
|
||||
|
||||
export class MappedNavigator<T, R> extends MappedIterator<T, R> implements INavigator<R> {
|
||||
|
||||
constructor(protected navigator: INavigator<T>, fn: (item: T) => R) {
|
||||
constructor(protected navigator: INavigator<T>, fn: (item: T | null) => R) {
|
||||
super(navigator, fn);
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ export interface Location {
|
||||
export interface ParseOptions {
|
||||
disallowComments?: boolean;
|
||||
allowTrailingComma?: boolean;
|
||||
allowEmptyContent?: boolean;
|
||||
}
|
||||
|
||||
export namespace ParseOptions {
|
||||
@@ -785,7 +786,7 @@ export function getLocation(text: string, position: number): Location {
|
||||
if (position < offset) {
|
||||
throw earlyReturnException;
|
||||
}
|
||||
setPreviousNode(value, offset, length, getLiteralNodeType(value));
|
||||
setPreviousNode(value, offset, length, getNodeType(value));
|
||||
|
||||
if (position <= offset + length) {
|
||||
throw earlyReturnException;
|
||||
@@ -848,7 +849,7 @@ export function parse(text: string, errors: ParseError[] = [], options: ParseOpt
|
||||
function onValue(value: any) {
|
||||
if (Array.isArray(currentParent)) {
|
||||
(<any[]>currentParent).push(value);
|
||||
} else if (currentProperty) {
|
||||
} else if (currentProperty !== null) {
|
||||
currentParent[currentProperty] = value;
|
||||
}
|
||||
}
|
||||
@@ -927,7 +928,7 @@ export function parseTree(text: string, errors: ParseError[] = [], options: Pars
|
||||
ensurePropertyComplete(offset + length);
|
||||
},
|
||||
onLiteralValue: (value: any, offset: number, length: number) => {
|
||||
onValue({ type: getLiteralNodeType(value), offset, length, parent: currentParent, value });
|
||||
onValue({ type: getNodeType(value), offset, length, parent: currentParent, value });
|
||||
ensurePropertyComplete(offset + length);
|
||||
},
|
||||
onSeparator: (sep: string, offset: number, length: number) => {
|
||||
@@ -1287,7 +1288,11 @@ export function visit(text: string, visitor: JSONVisitor, options: ParseOptions
|
||||
|
||||
scanNext();
|
||||
if (_scanner.getToken() === SyntaxKind.EOF) {
|
||||
return true;
|
||||
if (options.allowEmptyContent) {
|
||||
return true;
|
||||
}
|
||||
handleError(ParseErrorCode.ValueExpected, [], []);
|
||||
return false;
|
||||
}
|
||||
if (!parseValue()) {
|
||||
handleError(ParseErrorCode.ValueExpected, [], []);
|
||||
@@ -1333,11 +1338,19 @@ export function stripComments(text: string, replaceCh?: string): string {
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
function getLiteralNodeType(value: any): NodeType {
|
||||
export function getNodeType(value: any): NodeType {
|
||||
switch (typeof value) {
|
||||
case 'boolean': return 'boolean';
|
||||
case 'number': return 'number';
|
||||
case 'string': return 'string';
|
||||
case 'object': {
|
||||
if (!value) {
|
||||
return 'null';
|
||||
} else if (Array.isArray(value)) {
|
||||
return 'array';
|
||||
}
|
||||
return 'object';
|
||||
}
|
||||
default: return 'null';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,40 +84,36 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo
|
||||
return withFormatting(text, edit, formattingOptions);
|
||||
}
|
||||
} else if (parent.type === 'array' && typeof lastSegment === 'number' && Array.isArray(parent.children)) {
|
||||
const insertIndex = lastSegment;
|
||||
if (insertIndex === -1) {
|
||||
if (value !== undefined) {
|
||||
// Insert
|
||||
const newProperty = `${JSON.stringify(value)}`;
|
||||
let edit: Edit;
|
||||
if (parent.children.length === 0) {
|
||||
edit = { offset: parent.offset + 1, length: 0, content: newProperty };
|
||||
if (parent.children.length === 0 || lastSegment === 0) {
|
||||
edit = { offset: parent.offset + 1, length: 0, content: parent.children.length === 0 ? newProperty : newProperty + ',' };
|
||||
} else {
|
||||
const previous = parent.children[parent.children.length - 1];
|
||||
const index = lastSegment === -1 || lastSegment > parent.children.length ? parent.children.length : lastSegment;
|
||||
const previous = parent.children[index - 1];
|
||||
edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty };
|
||||
}
|
||||
return withFormatting(text, edit, formattingOptions);
|
||||
} else {
|
||||
if (value === undefined && parent.children.length >= 0) {
|
||||
//Removal
|
||||
const removalIndex = lastSegment;
|
||||
const toRemove = parent.children[removalIndex];
|
||||
let edit: Edit;
|
||||
if (parent.children.length === 1) {
|
||||
// only item
|
||||
edit = { offset: parent.offset + 1, length: parent.length - 2, content: '' };
|
||||
} else if (parent.children.length - 1 === removalIndex) {
|
||||
// last item
|
||||
const previous = parent.children[removalIndex - 1];
|
||||
const offset = previous.offset + previous.length;
|
||||
const parentEndOffset = parent.offset + parent.length;
|
||||
edit = { offset, length: parentEndOffset - 2 - offset, content: '' };
|
||||
} else {
|
||||
edit = { offset: toRemove.offset, length: parent.children[removalIndex + 1].offset - toRemove.offset, content: '' };
|
||||
}
|
||||
return withFormatting(text, edit, formattingOptions);
|
||||
//Removal
|
||||
const removalIndex = lastSegment;
|
||||
const toRemove = parent.children[removalIndex];
|
||||
let edit: Edit;
|
||||
if (parent.children.length === 1) {
|
||||
// only item
|
||||
edit = { offset: parent.offset + 1, length: parent.length - 2, content: '' };
|
||||
} else if (parent.children.length - 1 === removalIndex) {
|
||||
// last item
|
||||
const previous = parent.children[removalIndex - 1];
|
||||
const offset = previous.offset + previous.length;
|
||||
const parentEndOffset = parent.offset + parent.length;
|
||||
edit = { offset, length: parentEndOffset - 2 - offset, content: '' };
|
||||
} else {
|
||||
throw new Error('Array modification not supported yet');
|
||||
edit = { offset: toRemove.offset, length: parent.children[removalIndex + 1].offset - toRemove.offset, content: '' };
|
||||
}
|
||||
return withFormatting(text, edit, formattingOptions);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Can not add ${typeof lastSegment !== 'number' ? 'index' : 'property'} to parent of type ${parent.type}`);
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'null' | 'array' | 'object';
|
||||
|
||||
export interface IJSONSchema {
|
||||
id?: string;
|
||||
$id?: string;
|
||||
$schema?: string;
|
||||
type?: string | string[];
|
||||
type?: JSONSchemaType | JSONSchemaType[];
|
||||
title?: string;
|
||||
default?: any;
|
||||
definitions?: IJSONSchemaMap;
|
||||
|
||||
@@ -54,6 +54,11 @@ export class Lazy<T> {
|
||||
return this._value!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wrapped value without forcing evaluation.
|
||||
*/
|
||||
get rawValue(): T | undefined { return this._value; }
|
||||
|
||||
/**
|
||||
* Create a new lazy value that is the result of applying `f` to the wrapped value.
|
||||
*
|
||||
|
||||
@@ -206,43 +206,6 @@ export class MutableDisposable<T extends IDisposable> implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class that stores a disposable that is not currently "owned" by anyone.
|
||||
*
|
||||
* Example use cases:
|
||||
*
|
||||
* - Express that a function/method will take ownership of a disposable parameter.
|
||||
* - Express that a function returns a disposable that the caller must explicitly take ownership of.
|
||||
*/
|
||||
export class UnownedDisposable<T extends IDisposable> extends Disposable {
|
||||
private _hasBeenAcquired = false;
|
||||
private _value?: T;
|
||||
|
||||
public constructor(value: T) {
|
||||
super();
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
public acquire(): T {
|
||||
if (this._hasBeenAcquired) {
|
||||
throw new Error('This disposable has already been acquired');
|
||||
}
|
||||
this._hasBeenAcquired = true;
|
||||
const value = this._value!;
|
||||
this._value = undefined;
|
||||
return value;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose();
|
||||
if (!this._hasBeenAcquired) {
|
||||
this._hasBeenAcquired = true;
|
||||
this._value!.dispose();
|
||||
this._value = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface IReference<T> extends IDisposable {
|
||||
readonly object: T;
|
||||
}
|
||||
|
||||
@@ -262,7 +262,14 @@ export function suggestFilename(mode: string | undefined, prefix: string): strin
|
||||
.filter(assoc => startsWith(assoc, '.'));
|
||||
|
||||
if (extensionsWithDotFirst.length > 0) {
|
||||
return prefix + extensionsWithDotFirst[0];
|
||||
const candidateExtension = extensionsWithDotFirst[0];
|
||||
if (endsWith(prefix, candidateExtension)) {
|
||||
// do not add the prefix if it already exists
|
||||
// https://github.com/microsoft/vscode/issues/83603
|
||||
return prefix;
|
||||
}
|
||||
|
||||
return prefix + candidateExtension;
|
||||
}
|
||||
|
||||
return extensions[0] || prefix;
|
||||
|
||||
@@ -69,11 +69,11 @@ function validateString(value: string, name: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function isPathSeparator(code: number) {
|
||||
function isPathSeparator(code: number | undefined) {
|
||||
return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
|
||||
}
|
||||
|
||||
function isPosixPathSeparator(code: number) {
|
||||
function isPosixPathSeparator(code: number | undefined) {
|
||||
return code === CHAR_FORWARD_SLASH;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ let _isMacintosh = false;
|
||||
let _isLinux = false;
|
||||
let _isNative = false;
|
||||
let _isWeb = false;
|
||||
let _isIOS = false;
|
||||
let _locale: string | undefined = undefined;
|
||||
let _language: string = LANGUAGE_DEFAULT;
|
||||
let _translationsConfigFile: string | undefined = undefined;
|
||||
@@ -41,6 +42,7 @@ declare const global: any;
|
||||
interface INavigator {
|
||||
userAgent: string;
|
||||
language: string;
|
||||
maxTouchPoints?: number;
|
||||
}
|
||||
declare const navigator: INavigator;
|
||||
declare const self: any;
|
||||
@@ -52,6 +54,7 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
|
||||
_userAgent = navigator.userAgent;
|
||||
_isWindows = _userAgent.indexOf('Windows') >= 0;
|
||||
_isMacintosh = _userAgent.indexOf('Macintosh') >= 0;
|
||||
_isIOS = _userAgent.indexOf('Macintosh') >= 0 && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0;
|
||||
_isLinux = _userAgent.indexOf('Linux') >= 0;
|
||||
_isWeb = true;
|
||||
_locale = navigator.language;
|
||||
@@ -106,6 +109,7 @@ export const isMacintosh = _isMacintosh;
|
||||
export const isLinux = _isLinux;
|
||||
export const isNative = _isNative;
|
||||
export const isWeb = _isWeb;
|
||||
export const isIOS = _isIOS;
|
||||
export const platform = _platform;
|
||||
export const userAgent = _userAgent;
|
||||
|
||||
@@ -189,7 +193,7 @@ export const setImmediate: ISetImmediate = (function defineSetImmediate() {
|
||||
id: myId,
|
||||
callback: callback
|
||||
});
|
||||
globals.postMessage({ vscodeSetImmediateId: myId });
|
||||
globals.postMessage({ vscodeSetImmediateId: myId }, '*');
|
||||
};
|
||||
}
|
||||
if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
|
||||
|
||||
487
src/vs/base/common/stream.ts
Normal file
@@ -0,0 +1,487 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* The payload that flows in readable stream events.
|
||||
*/
|
||||
export type ReadableStreamEventPayload<T> = T | Error | 'end';
|
||||
|
||||
export interface ReadableStreamEvents<T> {
|
||||
|
||||
/**
|
||||
* The 'data' event is emitted whenever the stream is
|
||||
* relinquishing ownership of a chunk of data to a consumer.
|
||||
*/
|
||||
on(event: 'data', callback: (data: T) => void): void;
|
||||
|
||||
/**
|
||||
* Emitted when any error occurs.
|
||||
*/
|
||||
on(event: 'error', callback: (err: Error) => void): void;
|
||||
|
||||
/**
|
||||
* The 'end' event is emitted when there is no more data
|
||||
* to be consumed from the stream. The 'end' event will
|
||||
* not be emitted unless the data is completely consumed.
|
||||
*/
|
||||
on(event: 'end', callback: () => void): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A interface that emulates the API shape of a node.js readable
|
||||
* stream for use in desktop and web environments.
|
||||
*/
|
||||
export interface ReadableStream<T> extends ReadableStreamEvents<T> {
|
||||
|
||||
/**
|
||||
* Stops emitting any events until resume() is called.
|
||||
*/
|
||||
pause(): void;
|
||||
|
||||
/**
|
||||
* Starts emitting events again after pause() was called.
|
||||
*/
|
||||
resume(): void;
|
||||
|
||||
/**
|
||||
* Destroys the stream and stops emitting any event.
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A interface that emulates the API shape of a node.js readable
|
||||
* for use in desktop and web environments.
|
||||
*/
|
||||
export interface Readable<T> {
|
||||
|
||||
/**
|
||||
* Read data from the underlying source. Will return
|
||||
* null to indicate that no more data can be read.
|
||||
*/
|
||||
read(): T | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A interface that emulates the API shape of a node.js writeable
|
||||
* stream for use in desktop and web environments.
|
||||
*/
|
||||
export interface WriteableStream<T> extends ReadableStream<T> {
|
||||
|
||||
/**
|
||||
* Writing data to the stream will trigger the on('data')
|
||||
* event listener if the stream is flowing and buffer the
|
||||
* data otherwise until the stream is flowing.
|
||||
*/
|
||||
write(data: T): void;
|
||||
|
||||
/**
|
||||
* Signals an error to the consumer of the stream via the
|
||||
* on('error') handler if the stream is flowing.
|
||||
*/
|
||||
error(error: Error): void;
|
||||
|
||||
/**
|
||||
* Signals the end of the stream to the consumer. If the
|
||||
* result is not an error, will trigger the on('data') event
|
||||
* listener if the stream is flowing and buffer the data
|
||||
* otherwise until the stream is flowing.
|
||||
*
|
||||
* In case of an error, the on('error') event will be used
|
||||
* if the stream is flowing.
|
||||
*/
|
||||
end(result?: T | Error): void;
|
||||
}
|
||||
|
||||
export function isReadableStream<T>(obj: any): obj is ReadableStream<T> {
|
||||
const candidate: ReadableStream<T> = obj;
|
||||
|
||||
return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function');
|
||||
}
|
||||
|
||||
export interface IReducer<T> {
|
||||
(data: T[]): T;
|
||||
}
|
||||
|
||||
export interface IDataTransformer<Original, Transformed> {
|
||||
(data: Original): Transformed;
|
||||
}
|
||||
|
||||
export interface IErrorTransformer {
|
||||
(error: Error): Error;
|
||||
}
|
||||
|
||||
export interface ITransformer<Original, Transformed> {
|
||||
data: IDataTransformer<Original, Transformed>;
|
||||
error?: IErrorTransformer;
|
||||
}
|
||||
|
||||
export function newWriteableStream<T>(reducer: IReducer<T>): WriteableStream<T> {
|
||||
return new WriteableStreamImpl<T>(reducer);
|
||||
}
|
||||
|
||||
class WriteableStreamImpl<T> implements WriteableStream<T> {
|
||||
|
||||
private readonly state = {
|
||||
flowing: false,
|
||||
ended: false,
|
||||
destroyed: false
|
||||
};
|
||||
|
||||
private readonly buffer = {
|
||||
data: [] as T[],
|
||||
error: [] as Error[]
|
||||
};
|
||||
|
||||
private readonly listeners = {
|
||||
data: [] as { (data: T): void }[],
|
||||
error: [] as { (error: Error): void }[],
|
||||
end: [] as { (): void }[]
|
||||
};
|
||||
|
||||
constructor(private reducer: IReducer<T>) { }
|
||||
|
||||
pause(): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.flowing = false;
|
||||
}
|
||||
|
||||
resume(): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.state.flowing) {
|
||||
this.state.flowing = true;
|
||||
|
||||
// emit buffered events
|
||||
this.flowData();
|
||||
this.flowErrors();
|
||||
this.flowEnd();
|
||||
}
|
||||
}
|
||||
|
||||
write(data: T): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// flowing: directly send the data to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.data.forEach(listener => listener(data));
|
||||
}
|
||||
|
||||
// not yet flowing: buffer data until flowing
|
||||
else {
|
||||
this.buffer.data.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
error(error: Error): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// flowing: directly send the error to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.error.forEach(listener => listener(error));
|
||||
}
|
||||
|
||||
// not yet flowing: buffer errors until flowing
|
||||
else {
|
||||
this.buffer.error.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
end(result?: T | Error): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// end with data or error if provided
|
||||
if (result instanceof Error) {
|
||||
this.error(result);
|
||||
} else if (result) {
|
||||
this.write(result);
|
||||
}
|
||||
|
||||
// flowing: send end event to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.end.forEach(listener => listener());
|
||||
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
// not yet flowing: remember state
|
||||
else {
|
||||
this.state.ended = true;
|
||||
}
|
||||
}
|
||||
|
||||
on(event: 'data', callback: (data: T) => void): void;
|
||||
on(event: 'error', callback: (err: Error) => void): void;
|
||||
on(event: 'end', callback: () => void): void;
|
||||
on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case 'data':
|
||||
this.listeners.data.push(callback);
|
||||
|
||||
// switch into flowing mode as soon as the first 'data'
|
||||
// listener is added and we are not yet in flowing mode
|
||||
this.resume();
|
||||
|
||||
break;
|
||||
|
||||
case 'end':
|
||||
this.listeners.end.push(callback);
|
||||
|
||||
// emit 'end' event directly if we are flowing
|
||||
// and the end has already been reached
|
||||
//
|
||||
// finish() when it went through
|
||||
if (this.state.flowing && this.flowEnd()) {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
this.listeners.error.push(callback);
|
||||
|
||||
// emit buffered 'error' events unless done already
|
||||
// now that we know that we have at least one listener
|
||||
if (this.state.flowing) {
|
||||
this.flowErrors();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private flowData(): void {
|
||||
if (this.buffer.data.length > 0) {
|
||||
const fullDataBuffer = this.reducer(this.buffer.data);
|
||||
|
||||
this.listeners.data.forEach(listener => listener(fullDataBuffer));
|
||||
|
||||
this.buffer.data.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private flowErrors(): void {
|
||||
if (this.listeners.error.length > 0) {
|
||||
for (const error of this.buffer.error) {
|
||||
this.listeners.error.forEach(listener => listener(error));
|
||||
}
|
||||
|
||||
this.buffer.error.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private flowEnd(): boolean {
|
||||
if (this.state.ended) {
|
||||
this.listeners.end.forEach(listener => listener());
|
||||
|
||||
return this.listeners.end.length > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (!this.state.destroyed) {
|
||||
this.state.destroyed = true;
|
||||
this.state.ended = true;
|
||||
|
||||
this.buffer.data.length = 0;
|
||||
this.buffer.error.length = 0;
|
||||
|
||||
this.listeners.data.length = 0;
|
||||
this.listeners.error.length = 0;
|
||||
this.listeners.end.length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to fully read a T readable into a T.
|
||||
*/
|
||||
export function consumeReadable<T>(readable: Readable<T>, reducer: IReducer<T>): T {
|
||||
const chunks: T[] = [];
|
||||
|
||||
let chunk: T | null;
|
||||
while ((chunk = readable.read()) !== null) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
return reducer(chunks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to read a T readable up to a maximum of chunks. If the limit is
|
||||
* reached, will return a readable instead to ensure all data can still
|
||||
* be read.
|
||||
*/
|
||||
export function consumeReadableWithLimit<T>(readable: Readable<T>, reducer: IReducer<T>, maxChunks: number): T | Readable<T> {
|
||||
const chunks: T[] = [];
|
||||
|
||||
let chunk: T | null | undefined = undefined;
|
||||
while ((chunk = readable.read()) !== null && chunks.length < maxChunks) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
// If the last chunk is null, it means we reached the end of
|
||||
// the readable and return all the data at once
|
||||
if (chunk === null && chunks.length > 0) {
|
||||
return reducer(chunks);
|
||||
}
|
||||
|
||||
// Otherwise, we still have a chunk, it means we reached the maxChunks
|
||||
// value and as such we return a new Readable that first returns
|
||||
// the existing read chunks and then continues with reading from
|
||||
// the underlying readable.
|
||||
return {
|
||||
read: () => {
|
||||
|
||||
// First consume chunks from our array
|
||||
if (chunks.length > 0) {
|
||||
return chunks.shift()!;
|
||||
}
|
||||
|
||||
// Then ensure to return our last read chunk
|
||||
if (typeof chunk !== 'undefined') {
|
||||
const lastReadChunk = chunk;
|
||||
|
||||
// explicitly use undefined here to indicate that we consumed
|
||||
// the chunk, which could have either been null or valued.
|
||||
chunk = undefined;
|
||||
|
||||
return lastReadChunk;
|
||||
}
|
||||
|
||||
// Finally delegate back to the Readable
|
||||
return readable.read();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to fully read a T stream into a T.
|
||||
*/
|
||||
export function consumeStream<T>(stream: ReadableStream<T>, reducer: IReducer<T>): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: T[] = [];
|
||||
|
||||
stream.on('data', data => chunks.push(data));
|
||||
stream.on('error', error => reject(error));
|
||||
stream.on('end', () => resolve(reducer(chunks)));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to read a T stream up to a maximum of chunks. If the limit is
|
||||
* reached, will return a stream instead to ensure all data can still
|
||||
* be read.
|
||||
*/
|
||||
export function consumeStreamWithLimit<T>(stream: ReadableStream<T>, reducer: IReducer<T>, maxChunks: number): Promise<T | ReadableStream<T>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: T[] = [];
|
||||
|
||||
let wrapperStream: WriteableStream<T> | undefined = undefined;
|
||||
|
||||
stream.on('data', data => {
|
||||
|
||||
// If we reach maxChunks, we start to return a stream
|
||||
// and make sure that any data we have already read
|
||||
// is in it as well
|
||||
if (!wrapperStream && chunks.length === maxChunks) {
|
||||
wrapperStream = newWriteableStream(reducer);
|
||||
while (chunks.length) {
|
||||
wrapperStream.write(chunks.shift()!);
|
||||
}
|
||||
|
||||
wrapperStream.write(data);
|
||||
|
||||
return resolve(wrapperStream);
|
||||
}
|
||||
|
||||
if (wrapperStream) {
|
||||
wrapperStream.write(data);
|
||||
} else {
|
||||
chunks.push(data);
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('error', error => {
|
||||
if (wrapperStream) {
|
||||
wrapperStream.error(error);
|
||||
} else {
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
if (wrapperStream) {
|
||||
while (chunks.length) {
|
||||
wrapperStream.write(chunks.shift()!);
|
||||
}
|
||||
|
||||
wrapperStream.end();
|
||||
} else {
|
||||
return resolve(reducer(chunks));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a readable stream from an existing T.
|
||||
*/
|
||||
export function toStream<T>(t: T, reducer: IReducer<T>): ReadableStream<T> {
|
||||
const stream = newWriteableStream<T>(reducer);
|
||||
|
||||
stream.end(t);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to convert a T into a Readable<T>.
|
||||
*/
|
||||
export function toReadable<T>(t: T): Readable<T> {
|
||||
let consumed = false;
|
||||
|
||||
return {
|
||||
read: () => {
|
||||
if (consumed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
consumed = true;
|
||||
|
||||
return t;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to transform a readable stream into another stream.
|
||||
*/
|
||||
export function transform<Original, Transformed>(stream: ReadableStreamEvents<Original>, transformer: ITransformer<Original, Transformed>, reducer: IReducer<Transformed>): ReadableStream<Transformed> {
|
||||
const target = newWriteableStream<Transformed>(reducer);
|
||||
|
||||
stream.on('data', data => target.write(transformer.data(data)));
|
||||
stream.on('end', () => target.end());
|
||||
stream.on('error', error => target.error(transformer.error ? transformer.error(error) : error));
|
||||
|
||||
return target;
|
||||
}
|
||||
@@ -3,6 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .activitybar > .content .monaco-action-bar .action-label.extensions {
|
||||
-webkit-mask: url('extensions-activity-bar.svg') no-repeat 50% 50%;
|
||||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
export type styleFn = (colors: { [name: string]: Color | undefined }) => void;
|
||||
|
||||
export interface IThemable {
|
||||
style: styleFn;
|
||||
}
|
||||
@@ -93,6 +93,13 @@ export function isUndefinedOrNull(obj: any): obj is undefined | null {
|
||||
return isUndefined(obj) || obj === null;
|
||||
}
|
||||
|
||||
|
||||
export function assertType(condition: any, type?: string): asserts condition {
|
||||
if (!condition) {
|
||||
throw new Error(type ? `Unexpected type, expected '${type}'` : 'Unexpected type');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the argument passed in is neither undefined nor null.
|
||||
*/
|
||||
|
||||
@@ -10,10 +10,10 @@ const _schemePattern = /^\w[\w\d+.-]*$/;
|
||||
const _singleSlashStart = /^\//;
|
||||
const _doubleSlashStart = /^\/\//;
|
||||
|
||||
function _validateUri(ret: URI): void {
|
||||
function _validateUri(ret: URI, _strict?: boolean): void {
|
||||
|
||||
// scheme, must be set
|
||||
if (!ret.scheme) {
|
||||
if (!ret.scheme && _strict) {
|
||||
throw new Error(`[UriError]: Scheme is missing: {scheme: "", authority: "${ret.authority}", path: "${ret.path}", query: "${ret.query}", fragment: "${ret.fragment}"}`);
|
||||
}
|
||||
|
||||
@@ -41,11 +41,13 @@ function _validateUri(ret: URI): void {
|
||||
}
|
||||
}
|
||||
|
||||
// graceful behaviour when scheme is missing: fallback to using 'file'-scheme
|
||||
function _schemeFix(scheme: string): string {
|
||||
if (!scheme) {
|
||||
console.trace('BAD uri lacks scheme, falling back to file-scheme.');
|
||||
scheme = 'file';
|
||||
// for a while we allowed uris *without* schemes and this is the migration
|
||||
// for them, e.g. an uri without scheme and without strict-mode warns and falls
|
||||
// back to the file-scheme. that should cause the least carnage and still be a
|
||||
// clear warning
|
||||
function _schemeFix(scheme: string, _strict: boolean): string {
|
||||
if (!scheme && !_strict) {
|
||||
return 'file';
|
||||
}
|
||||
return scheme;
|
||||
}
|
||||
@@ -138,7 +140,7 @@ export class URI implements UriComponents {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string);
|
||||
protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string, _strict?: boolean);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -148,7 +150,7 @@ export class URI implements UriComponents {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string) {
|
||||
protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string, _strict: boolean = false) {
|
||||
|
||||
if (typeof schemeOrData === 'object') {
|
||||
this.scheme = schemeOrData.scheme || _empty;
|
||||
@@ -160,13 +162,13 @@ export class URI implements UriComponents {
|
||||
// that creates uri components.
|
||||
// _validateUri(this);
|
||||
} else {
|
||||
this.scheme = _schemeFix(schemeOrData);
|
||||
this.scheme = _schemeFix(schemeOrData, _strict);
|
||||
this.authority = authority || _empty;
|
||||
this.path = _referenceResolution(this.scheme, path || _empty);
|
||||
this.query = query || _empty;
|
||||
this.fragment = fragment || _empty;
|
||||
|
||||
_validateUri(this);
|
||||
_validateUri(this, _strict);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +207,7 @@ export class URI implements UriComponents {
|
||||
|
||||
// ---- modify to new -------------------------
|
||||
|
||||
with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null; }): URI {
|
||||
with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null }): URI {
|
||||
|
||||
if (!change) {
|
||||
return this;
|
||||
@@ -258,17 +260,18 @@ export class URI implements UriComponents {
|
||||
*
|
||||
* @param value A string which represents an URI (see `URI#toString`).
|
||||
*/
|
||||
static parse(value: string): URI {
|
||||
static parse(value: string, _strict: boolean = false): URI {
|
||||
const match = _regexp.exec(value);
|
||||
if (!match) {
|
||||
return new _URI(_empty, _empty, _empty, _empty, _empty);
|
||||
}
|
||||
return new _URI(
|
||||
match[2] || _empty,
|
||||
decodeURIComponent(match[4] || _empty),
|
||||
decodeURIComponent(match[5] || _empty),
|
||||
decodeURIComponent(match[7] || _empty),
|
||||
decodeURIComponent(match[9] || _empty)
|
||||
percentDecode(match[4] || _empty),
|
||||
percentDecode(match[5] || _empty),
|
||||
percentDecode(match[7] || _empty),
|
||||
percentDecode(match[9] || _empty),
|
||||
_strict
|
||||
);
|
||||
}
|
||||
|
||||
@@ -320,7 +323,7 @@ export class URI implements UriComponents {
|
||||
return new _URI('file', authority, path, _empty, _empty);
|
||||
}
|
||||
|
||||
static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string; }): URI {
|
||||
static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string }): URI {
|
||||
return new _URI(
|
||||
components.scheme,
|
||||
components.authority,
|
||||
@@ -445,7 +448,7 @@ class _URI extends URI {
|
||||
}
|
||||
|
||||
// reserved characters: https://tools.ietf.org/html/rfc3986#section-2.2
|
||||
const encodeTable: { [ch: number]: string; } = {
|
||||
const encodeTable: { [ch: number]: string } = {
|
||||
[CharCode.Colon]: '%3A', // gen-delims
|
||||
[CharCode.Slash]: '%2F',
|
||||
[CharCode.QuestionMark]: '%3F',
|
||||
@@ -646,3 +649,26 @@ function _asFormatted(uri: URI, skipEncoding: boolean): string {
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// --- decode
|
||||
|
||||
function decodeURIComponentGraceful(str: string): string {
|
||||
try {
|
||||
return decodeURIComponent(str);
|
||||
} catch {
|
||||
if (str.length > 3) {
|
||||
return str.substr(0, 3) + decodeURIComponentGraceful(str.substr(3));
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const _rEncodedAsHex = /(%[0-9A-Za-z][0-9A-Za-z])+/g;
|
||||
|
||||
function percentDecode(str: string): string {
|
||||
if (!str.match(_rEncodedAsHex)) {
|
||||
return str;
|
||||
}
|
||||
return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match));
|
||||
}
|
||||
|
||||
@@ -59,4 +59,4 @@ export class LineDecoder {
|
||||
end(): string | null {
|
||||
return this.remaining;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions
|
||||
});
|
||||
}
|
||||
|
||||
_final(callback: (error: Error | null) => void) {
|
||||
_final(callback: () => void) {
|
||||
|
||||
// normal finish
|
||||
if (this.decodeStream) {
|
||||
@@ -144,7 +144,7 @@ export function decode(buffer: Buffer, encoding: string): string {
|
||||
}
|
||||
|
||||
export function encode(content: string | Buffer, encoding: string, options?: { addBOM?: boolean }): Buffer {
|
||||
return iconv.encode(content, toNodeEncoding(encoding), options);
|
||||
return iconv.encode(content as string /* TODO report into upstream typings */, toNodeEncoding(encoding), options);
|
||||
}
|
||||
|
||||
export function encodingExists(encoding: string): boolean {
|
||||
@@ -167,7 +167,7 @@ function toNodeEncoding(enc: string | null): string {
|
||||
return enc;
|
||||
}
|
||||
|
||||
export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null, bytesRead: number): string | null {
|
||||
export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null, bytesRead: number): typeof UTF8_with_bom | typeof UTF16le | typeof UTF16be | null {
|
||||
if (!buffer || bytesRead < UTF16be_BOM.length) {
|
||||
return null;
|
||||
}
|
||||
@@ -193,33 +193,31 @@ export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null,
|
||||
|
||||
// UTF-8
|
||||
if (b0 === UTF8_BOM[0] && b1 === UTF8_BOM[1] && b2 === UTF8_BOM[2]) {
|
||||
return UTF8;
|
||||
return UTF8_with_bom;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const MINIMUM_THRESHOLD = 0.2;
|
||||
const IGNORE_ENCODINGS = ['ascii', 'utf-8', 'utf-16', 'utf-32'];
|
||||
|
||||
/**
|
||||
* Guesses the encoding from buffer.
|
||||
*/
|
||||
async function guessEncodingByBuffer(buffer: Buffer): Promise<string | null> {
|
||||
const jschardet = await import('jschardet');
|
||||
|
||||
jschardet.Constants.MINIMUM_THRESHOLD = MINIMUM_THRESHOLD;
|
||||
|
||||
const guessed = jschardet.detect(buffer);
|
||||
if (!guessed || !guessed.encoding) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const enc = guessed.encoding.toLowerCase();
|
||||
|
||||
// Ignore encodings that cannot guess correctly
|
||||
// (http://chardet.readthedocs.io/en/latest/supported-encodings.html)
|
||||
if (0 <= IGNORE_ENCODINGS.indexOf(enc)) {
|
||||
// Ignore 'ascii' as guessed encoding because that
|
||||
// is almost never what we want, rather fallback
|
||||
// to the configured encoding then. Otherwise,
|
||||
// opening a ascii-only file with auto guessing
|
||||
// enabled will put the file into 'ascii' mode
|
||||
// and thus typing any special characters is
|
||||
// not possible anymore.
|
||||
if (guessed.encoding.toLowerCase() === 'ascii') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||