Merge VS Code 1.31.1 (#4283)
@@ -14,7 +14,7 @@ class WindowManager {
|
||||
// --- Zoom Level
|
||||
private _zoomLevel: number = 0;
|
||||
private _lastZoomLevelChangeTime: number = 0;
|
||||
private readonly _onDidChangeZoomLevel: Emitter<number> = new Emitter<number>();
|
||||
private readonly _onDidChangeZoomLevel = new Emitter<number>();
|
||||
|
||||
public readonly onDidChangeZoomLevel: Event<number> = this._onDidChangeZoomLevel.event;
|
||||
public getZoomLevel(): number {
|
||||
@@ -58,7 +58,7 @@ class WindowManager {
|
||||
|
||||
// --- Fullscreen
|
||||
private _fullscreen: boolean;
|
||||
private readonly _onDidChangeFullscreen: Emitter<void> = new Emitter<void>();
|
||||
private readonly _onDidChangeFullscreen = new Emitter<void>();
|
||||
|
||||
public readonly onDidChangeFullscreen: Event<void> = this._onDidChangeFullscreen.event;
|
||||
public setFullscreen(fullscreen: boolean): void {
|
||||
@@ -75,7 +75,7 @@ class WindowManager {
|
||||
|
||||
// --- Accessibility
|
||||
private _accessibilitySupport = platform.AccessibilitySupport.Unknown;
|
||||
private readonly _onDidChangeAccessibilitySupport: Emitter<void> = new Emitter<void>();
|
||||
private readonly _onDidChangeAccessibilitySupport = new Emitter<void>();
|
||||
|
||||
public readonly onDidChangeAccessibilitySupport: Event<void> = this._onDidChangeAccessibilitySupport.event;
|
||||
public setAccessibilitySupport(accessibilitySupport: platform.AccessibilitySupport): void {
|
||||
@@ -146,7 +146,8 @@ export const isOpera = (userAgent.indexOf('Opera') >= 0);
|
||||
export const isFirefox = (userAgent.indexOf('Firefox') >= 0);
|
||||
export const isWebKit = (userAgent.indexOf('AppleWebKit') >= 0);
|
||||
export const isChrome = (userAgent.indexOf('Chrome') >= 0);
|
||||
export const isSafari = (userAgent.indexOf('Chrome') === -1) && (userAgent.indexOf('Safari') >= 0);
|
||||
export const isSafari = (!isChrome && (userAgent.indexOf('Safari') >= 0));
|
||||
export const isWebkitWebView = (!isChrome && !isSafari && isWebKit);
|
||||
export const isIPad = (userAgent.indexOf('iPad') >= 0);
|
||||
export const isEdgeWebView = isEdge && (userAgent.indexOf('WebView/') >= 0);
|
||||
|
||||
|
||||
@@ -17,17 +17,17 @@ export interface IContextMenuEvent {
|
||||
}
|
||||
|
||||
export class ContextSubMenu extends SubmenuAction {
|
||||
constructor(label: string, public entries: (ContextSubMenu | IAction)[]) {
|
||||
constructor(label: string, public entries: Array<ContextSubMenu | IAction>) {
|
||||
super(label, entries, 'contextsubmenu');
|
||||
}
|
||||
}
|
||||
|
||||
export interface IContextMenuDelegate {
|
||||
getAnchor(): HTMLElement | { x: number; y: number; width?: number; height?: number; };
|
||||
getActions(): (IAction | ContextSubMenu)[];
|
||||
getActionItem?(action: IAction): IActionItem;
|
||||
getActions(): Array<IAction | ContextSubMenu>;
|
||||
getActionItem?(action: IAction): IActionItem | null;
|
||||
getActionsContext?(event?: IContextMenuEvent): any;
|
||||
getKeyBinding?(action: IAction): ResolvedKeybinding;
|
||||
getKeyBinding?(action: IAction): ResolvedKeybinding | undefined;
|
||||
getMenuClassName?(): string;
|
||||
onHide?(didCancel: boolean): void;
|
||||
actionRunner?: IActionRunner;
|
||||
|
||||
@@ -83,4 +83,30 @@ export function applyDragImage(event: DragEvent, label: string, clazz: string):
|
||||
// Removes the element when the DND operation is done
|
||||
setTimeout(() => document.body.removeChild(dragImage), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDragAndDropData {
|
||||
update(dataTransfer: DataTransfer): void;
|
||||
getData(): any;
|
||||
}
|
||||
|
||||
export class DragAndDropData<T> implements IDragAndDropData {
|
||||
|
||||
constructor(private data: T) { }
|
||||
|
||||
update(): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
getData(): T {
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IStaticDND {
|
||||
CurrentDragAndDropData: IDragAndDropData | undefined;
|
||||
}
|
||||
|
||||
export const StaticDND: IStaticDND = {
|
||||
CurrentDragAndDropData: undefined
|
||||
};
|
||||
@@ -11,8 +11,9 @@ import { TimeoutTimer } from 'vs/base/common/async';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
|
||||
export function clearNode(node: HTMLElement): void {
|
||||
while (node.firstChild) {
|
||||
@@ -146,10 +147,10 @@ const _manualClassList = new class implements IDomClassList {
|
||||
|
||||
toggleClass(node: HTMLElement, className: string, shouldHaveIt?: boolean): void {
|
||||
this._findClassName(node, className);
|
||||
if (this._lastStart !== -1 && (shouldHaveIt === void 0 || !shouldHaveIt)) {
|
||||
if (this._lastStart !== -1 && (shouldHaveIt === undefined || !shouldHaveIt)) {
|
||||
this.removeClass(node, className);
|
||||
}
|
||||
if (this._lastStart === -1 && (shouldHaveIt === void 0 || shouldHaveIt)) {
|
||||
if (this._lastStart === -1 && (shouldHaveIt === undefined || shouldHaveIt)) {
|
||||
this.addClass(node, className);
|
||||
}
|
||||
}
|
||||
@@ -265,7 +266,7 @@ export let addStandardDisposableListener: IAddStandardDisposableListenerSignatur
|
||||
export function addDisposableNonBubblingMouseOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable {
|
||||
return addDisposableListener(node, 'mouseout', (e: MouseEvent) => {
|
||||
// Mouse out bubbles, so this is an attempt to ignore faux mouse outs coming from children elements
|
||||
let toElement: Node | null = <Node>(e.relatedTarget || e.toElement);
|
||||
let toElement: Node | null = <Node>(e.relatedTarget || e.target);
|
||||
while (toElement && toElement !== node) {
|
||||
toElement = toElement.parentNode;
|
||||
}
|
||||
@@ -994,7 +995,7 @@ export function prepend<T extends Node>(parent: HTMLElement, child: T): T {
|
||||
|
||||
const SELECTOR_REGEX = /([\w\-]+)?(#([\w\-]+))?((.([\w\-]+))*)/;
|
||||
|
||||
export function $<T extends HTMLElement>(description: string, attrs?: { [key: string]: any; }, ...children: (Node | string)[]): T {
|
||||
export function $<T extends HTMLElement>(description: string, attrs?: { [key: string]: any; }, ...children: Array<Node | string>): T {
|
||||
let match = SELECTOR_REGEX.exec(description);
|
||||
|
||||
if (!match) {
|
||||
@@ -1025,8 +1026,7 @@ export function $<T extends HTMLElement>(description: string, attrs?: { [key: st
|
||||
}
|
||||
});
|
||||
|
||||
children
|
||||
.filter(child => !!child)
|
||||
coalesce(children)
|
||||
.forEach(child => {
|
||||
if (child instanceof Node) {
|
||||
result.appendChild(child);
|
||||
@@ -1157,3 +1157,13 @@ export function windowOpenNoOpener(url: string): void {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function animate(fn: () => void): IDisposable {
|
||||
const step = () => {
|
||||
fn();
|
||||
stepDisposable = scheduleAtNextAnimationFrame(step);
|
||||
};
|
||||
|
||||
let stepDisposable = scheduleAtNextAnimationFrame(step);
|
||||
return toDisposable(() => stepDisposable.dispose());
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter, mapEvent } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
|
||||
export type EventHandler = HTMLElement | HTMLDocument | Window;
|
||||
|
||||
@@ -32,7 +32,7 @@ export interface CancellableEvent {
|
||||
}
|
||||
|
||||
export function stop<T extends CancellableEvent>(event: Event<T>): Event<T> {
|
||||
return mapEvent(event, e => {
|
||||
return Event.map(event, e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return e;
|
||||
|
||||
@@ -92,12 +92,12 @@ export class GlobalMouseMoveMonitor<R> extends Disposable {
|
||||
this.onStopCallback = onStopCallback;
|
||||
|
||||
let windowChain = IframeUtils.getSameOriginWindowChain();
|
||||
for (let i = 0; i < windowChain.length; i++) {
|
||||
this.hooks.push(dom.addDisposableThrottledListener(windowChain[i].window.document, 'mousemove',
|
||||
for (const element of windowChain) {
|
||||
this.hooks.push(dom.addDisposableThrottledListener(element.window.document, 'mousemove',
|
||||
(data: R) => this.mouseMoveCallback!(data),
|
||||
(lastEvent: R, currentEvent) => this.mouseMoveEventMerger!(lastEvent, currentEvent as MouseEvent)
|
||||
));
|
||||
this.hooks.push(dom.addDisposableListener(windowChain[i].window.document, 'mouseup', (e: MouseEvent) => this.stopMonitoring(true)));
|
||||
this.hooks.push(dom.addDisposableListener(element.window.document, 'mouseup', (e: MouseEvent) => this.stopMonitoring(true)));
|
||||
}
|
||||
|
||||
if (IframeUtils.hasDifferentOriginAncestor()) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { defaultGenerator } from 'vs/base/common/idGenerator';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { removeMarkdownEscapes, IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { removeMarkdownEscapes, IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import * as marked from 'vs/base/common/marked/marked';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -24,7 +24,7 @@ export interface RenderOptions {
|
||||
className?: string;
|
||||
inline?: boolean;
|
||||
actionHandler?: IContentActionHandler;
|
||||
codeBlockRenderer?: (modeId: string, value: string) => Thenable<string>;
|
||||
codeBlockRenderer?: (modeId: string, value: string) => Promise<string>;
|
||||
codeBlockRenderCallback?: () => void;
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions
|
||||
|
||||
// signal to code-block render that the
|
||||
// element has been created
|
||||
let signalInnerHTML: Function;
|
||||
let signalInnerHTML: () => void;
|
||||
const withInnerHTML = new Promise(c => signalInnerHTML = c);
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
@@ -225,7 +225,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions
|
||||
}
|
||||
|
||||
const markedOptions: marked.MarkedOptions = {
|
||||
sanitize: markdown instanceof MarkdownString ? markdown.sanitize : true,
|
||||
sanitize: true,
|
||||
renderer
|
||||
};
|
||||
|
||||
|
||||
@@ -111,8 +111,7 @@ export class IframeUtils {
|
||||
|
||||
let windowChain = this.getSameOriginWindowChain();
|
||||
|
||||
for (let i = 0; i < windowChain.length; i++) {
|
||||
let windowChainEl = windowChain[i];
|
||||
for (const windowChainEl of windowChain) {
|
||||
|
||||
if (windowChainEl.window === ancestorWindow) {
|
||||
break;
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'vs/css!./actionbar';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Disposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { SelectBox, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { SelectBox, ISelectOptionItem, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, IRunEvent } from 'vs/base/common/actions';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as types from 'vs/base/common/types';
|
||||
@@ -60,24 +60,24 @@ export class BaseActionItem extends Disposable implements IActionItem {
|
||||
}
|
||||
|
||||
private handleActionChangeEvent(event: IActionChangeEvent): void {
|
||||
if (event.enabled !== void 0) {
|
||||
if (event.enabled !== undefined) {
|
||||
this.updateEnabled();
|
||||
}
|
||||
|
||||
if (event.checked !== void 0) {
|
||||
if (event.checked !== undefined) {
|
||||
this.updateChecked();
|
||||
}
|
||||
|
||||
if (event.class !== void 0) {
|
||||
if (event.class !== undefined) {
|
||||
this.updateClass();
|
||||
}
|
||||
|
||||
if (event.label !== void 0) {
|
||||
if (event.label !== undefined) {
|
||||
this.updateLabel();
|
||||
this.updateTooltip();
|
||||
}
|
||||
|
||||
if (event.tooltip !== void 0) {
|
||||
if (event.tooltip !== undefined) {
|
||||
this.updateTooltip();
|
||||
}
|
||||
}
|
||||
@@ -228,7 +228,7 @@ export class Separator extends Action {
|
||||
export interface IActionItemOptions extends IBaseActionItemOptions {
|
||||
icon?: boolean;
|
||||
label?: boolean;
|
||||
keybinding?: string;
|
||||
keybinding?: string | null;
|
||||
}
|
||||
|
||||
export class ActionItem extends BaseActionItem {
|
||||
@@ -363,7 +363,7 @@ export interface ActionTrigger {
|
||||
}
|
||||
|
||||
export interface IActionItemProvider {
|
||||
(action: IAction): IActionItem;
|
||||
(action: IAction): IActionItem | null;
|
||||
}
|
||||
|
||||
export interface IActionBarOptions {
|
||||
@@ -376,7 +376,7 @@ export interface IActionBarOptions {
|
||||
triggerKeys?: ActionTrigger;
|
||||
}
|
||||
|
||||
let defaultOptions: IActionBarOptions = {
|
||||
const defaultOptions: IActionBarOptions = {
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
context: null,
|
||||
triggerKeys: {
|
||||
@@ -473,7 +473,7 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
}
|
||||
|
||||
this._register(DOM.addDisposableListener(this.domNode, DOM.EventType.KEY_DOWN, e => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
let eventHandled = true;
|
||||
|
||||
if (event.equals(previousKey)) {
|
||||
@@ -498,7 +498,7 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
}));
|
||||
|
||||
this._register(DOM.addDisposableListener(this.domNode, DOM.EventType.KEY_UP, e => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
|
||||
// Run action on Enter/Space
|
||||
if (this.isTriggerKeyEvent(event)) {
|
||||
@@ -560,7 +560,7 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
|
||||
private updateFocusedItem(): void {
|
||||
for (let i = 0; i < this.actionsList.children.length; i++) {
|
||||
let elem = this.actionsList.children[i];
|
||||
const elem = this.actionsList.children[i];
|
||||
if (DOM.isAncestor(document.activeElement, elem)) {
|
||||
this.focusedItem = i;
|
||||
break;
|
||||
@@ -577,11 +577,11 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
this.items.forEach(i => i.setActionContext(context));
|
||||
}
|
||||
|
||||
get actionRunner(): IActionRunner | undefined {
|
||||
get actionRunner(): IActionRunner {
|
||||
return this._actionRunner;
|
||||
}
|
||||
|
||||
set actionRunner(actionRunner: IActionRunner | undefined) {
|
||||
set actionRunner(actionRunner: IActionRunner) {
|
||||
if (actionRunner) {
|
||||
this._actionRunner = actionRunner;
|
||||
this.items.forEach(item => item.actionRunner = actionRunner);
|
||||
@@ -657,8 +657,8 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
|
||||
pull(index: number): void {
|
||||
if (index >= 0 && index < this.items.length) {
|
||||
this.items.splice(index, 1);
|
||||
this.actionsList.removeChild(this.actionsList.childNodes[index]);
|
||||
dispose(this.items.splice(index, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -679,7 +679,7 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
focus(selectFirst?: boolean): void;
|
||||
focus(arg?: any): void {
|
||||
let selectFirst: boolean = false;
|
||||
let index: number | undefined = void 0;
|
||||
let index: number | undefined = undefined;
|
||||
if (arg === undefined) {
|
||||
selectFirst = true;
|
||||
} else if (typeof arg === 'number') {
|
||||
@@ -701,12 +701,12 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
}
|
||||
}
|
||||
|
||||
private focusNext(): void {
|
||||
protected focusNext(): void {
|
||||
if (typeof this.focusedItem === 'undefined') {
|
||||
this.focusedItem = this.items.length - 1;
|
||||
}
|
||||
|
||||
let startIndex = this.focusedItem;
|
||||
const startIndex = this.focusedItem;
|
||||
let item: IActionItem;
|
||||
|
||||
do {
|
||||
@@ -721,12 +721,12 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
this.updateFocus();
|
||||
}
|
||||
|
||||
private focusPrevious(): void {
|
||||
protected focusPrevious(): void {
|
||||
if (typeof this.focusedItem === 'undefined') {
|
||||
this.focusedItem = 0;
|
||||
}
|
||||
|
||||
let startIndex = this.focusedItem;
|
||||
const startIndex = this.focusedItem;
|
||||
let item: IActionItem;
|
||||
|
||||
do {
|
||||
@@ -752,9 +752,8 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.items.length; i++) {
|
||||
let item = this.items[i];
|
||||
|
||||
let actionItem = item;
|
||||
const item = this.items[i];
|
||||
const actionItem = item;
|
||||
|
||||
if (i === this.focusedItem) {
|
||||
if (types.isFunction(actionItem.isEnabled)) {
|
||||
@@ -778,7 +777,7 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
}
|
||||
|
||||
// trigger action
|
||||
let actionItem = this.items[this.focusedItem];
|
||||
const actionItem = this.items[this.focusedItem];
|
||||
if (actionItem instanceof BaseActionItem) {
|
||||
const context = (actionItem._context === null || actionItem._context === undefined) ? event : actionItem._context;
|
||||
this.run(actionItem._action, context);
|
||||
@@ -793,7 +792,7 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
this._onDidCancel.fire();
|
||||
}
|
||||
|
||||
run(action: IAction, context?: any): Thenable<void> {
|
||||
run(action: IAction, context?: any): Promise<void> {
|
||||
return this._actionRunner.run(action, context);
|
||||
}
|
||||
|
||||
@@ -810,7 +809,7 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
export class SelectActionItem extends BaseActionItem {
|
||||
protected selectBox: SelectBox;
|
||||
|
||||
constructor(ctx: any, action: IAction, options: string[], selected: number, contextViewProvider: IContextViewProvider, selectBoxOptions?: ISelectBoxOptions) {
|
||||
constructor(ctx: any, action: IAction, options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, selectBoxOptions?: ISelectBoxOptions) {
|
||||
super(ctx, action);
|
||||
|
||||
this.selectBox = new SelectBox(options, selected, contextViewProvider, undefined, selectBoxOptions);
|
||||
@@ -819,8 +818,8 @@ export class SelectActionItem extends BaseActionItem {
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
setOptions(options: string[], selected?: number, disabled?: number): void {
|
||||
this.selectBox.setOptions(options, selected, disabled);
|
||||
setOptions(options: ISelectOptionItem[], selected?: number): void {
|
||||
this.selectBox.setOptions(options, selected);
|
||||
}
|
||||
|
||||
select(index: number): void {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Color } from 'vs/base/common/color';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { Event as BaseEvent, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Gesture } from 'vs/base/browser/touch';
|
||||
import { Gesture, EventType } from 'vs/base/browser/touch';
|
||||
|
||||
export interface IButtonOptions extends IButtonStyles {
|
||||
title?: boolean;
|
||||
@@ -65,14 +65,16 @@ export class Button extends Disposable {
|
||||
|
||||
Gesture.addTarget(this._element);
|
||||
|
||||
this._register(DOM.addDisposableListener(this._element, DOM.EventType.CLICK, e => {
|
||||
if (!this.enabled) {
|
||||
DOM.EventHelper.stop(e);
|
||||
return;
|
||||
}
|
||||
[DOM.EventType.CLICK, EventType.Tap].forEach(eventType => {
|
||||
this._register(DOM.addDisposableListener(this._element, eventType, e => {
|
||||
if (!this.enabled) {
|
||||
DOM.EventHelper.stop(e);
|
||||
return;
|
||||
}
|
||||
|
||||
this._onDidClick.fire(e);
|
||||
}));
|
||||
this._onDidClick.fire(e);
|
||||
}));
|
||||
});
|
||||
|
||||
this._register(DOM.addDisposableListener(this._element, DOM.EventType.KEY_DOWN, e => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { SplitView, Orientation, ISplitViewStyles, IView as ISplitViewView } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { $ } from 'vs/base/browser/dom';
|
||||
import { Event, mapEvent } from 'vs/base/common/event';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IView } from 'vs/base/browser/ui/grid/gridview';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
@@ -39,8 +39,8 @@ function toSplitViewView(view: IView, getHeight: () => number): ISplitViewView {
|
||||
element: view.element,
|
||||
get maximumSize() { return view.maximumWidth; },
|
||||
get minimumSize() { return view.minimumWidth; },
|
||||
onDidChange: mapEvent(view.onDidChange, e => e && e.width),
|
||||
layout: size => view.layout(size, getHeight())
|
||||
onDidChange: Event.map(view.onDidChange, e => e && e.width),
|
||||
layout: size => view.layout(size, getHeight(), Orientation.HORIZONTAL)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export class CenteredViewLayout {
|
||||
this.resizeMargins();
|
||||
}
|
||||
} else {
|
||||
this.view.layout(width, height);
|
||||
this.view.layout(width, height, Orientation.HORIZONTAL);
|
||||
}
|
||||
this.didLayout = true;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export const enum AnchorPosition {
|
||||
|
||||
export interface IDelegate {
|
||||
getAnchor(): HTMLElement | IAnchor;
|
||||
render(container: HTMLElement): IDisposable;
|
||||
render(container: HTMLElement): IDisposable | null;
|
||||
focus?(): void;
|
||||
layout?(): void;
|
||||
anchorAlignment?: AnchorAlignment; // default: left
|
||||
|
||||
@@ -9,23 +9,8 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.monaco-dropdown > .dropdown-label,
|
||||
.monaco-dropdown > .dropdown-action {
|
||||
.monaco-dropdown > .dropdown-label {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-dropdown > .dropdown-action {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.monaco-dropdown > .dropdown-action > .action-label:hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.monaco-dropdown > .dropdown-action,
|
||||
.monaco-dropdown > .dropdown-action > .action-label {
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
|
||||
export interface ILabelRenderer {
|
||||
(container: HTMLElement): IDisposable;
|
||||
(container: HTMLElement): IDisposable | null;
|
||||
}
|
||||
|
||||
export interface IBaseDropdownOptions {
|
||||
@@ -26,9 +26,9 @@ export interface IBaseDropdownOptions {
|
||||
|
||||
export class BaseDropdown extends ActionRunner {
|
||||
private _element: HTMLElement;
|
||||
private boxContainer: HTMLElement;
|
||||
private _label: HTMLElement;
|
||||
private contents: HTMLElement;
|
||||
private boxContainer?: HTMLElement;
|
||||
private _label?: HTMLElement;
|
||||
private contents?: HTMLElement;
|
||||
private visible: boolean;
|
||||
|
||||
constructor(container: HTMLElement, options: IBaseDropdownOptions) {
|
||||
@@ -40,18 +40,18 @@ export class BaseDropdown extends ActionRunner {
|
||||
|
||||
let labelRenderer = options.labelRenderer;
|
||||
if (!labelRenderer) {
|
||||
labelRenderer = (container: HTMLElement): IDisposable => {
|
||||
labelRenderer = (container: HTMLElement): IDisposable | null => {
|
||||
container.textContent = options.label || '';
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
[EventType.CLICK, EventType.MOUSE_DOWN, GestureEventType.Tap].forEach(event => {
|
||||
for (const event of [EventType.CLICK, EventType.MOUSE_DOWN, GestureEventType.Tap]) {
|
||||
this._register(addDisposableListener(this._label, event, e => EventHelper.stop(e, true))); // prevent default click behaviour to trigger
|
||||
});
|
||||
}
|
||||
|
||||
[EventType.MOUSE_DOWN, GestureEventType.Tap].forEach(event => {
|
||||
for (const event of [EventType.MOUSE_DOWN, GestureEventType.Tap]) {
|
||||
this._register(addDisposableListener(this._label, event, e => {
|
||||
if (e instanceof MouseEvent && e.detail > 1) {
|
||||
return; // prevent multiple clicks to open multiple context menus (https://github.com/Microsoft/vscode/issues/41363)
|
||||
@@ -63,7 +63,7 @@ export class BaseDropdown extends ActionRunner {
|
||||
this.show();
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
this._register(addDisposableListener(this._label, EventType.KEY_UP, e => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
@@ -90,12 +90,14 @@ export class BaseDropdown extends ActionRunner {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
get label(): HTMLElement {
|
||||
get label() {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
set tooltip(tooltip: string) {
|
||||
this._label.title = tooltip;
|
||||
if (this._label) {
|
||||
this._label.title = tooltip;
|
||||
}
|
||||
}
|
||||
|
||||
show(): void {
|
||||
@@ -116,17 +118,17 @@ export class BaseDropdown extends ActionRunner {
|
||||
|
||||
if (this.boxContainer) {
|
||||
this.boxContainer.remove();
|
||||
this.boxContainer = null;
|
||||
this.boxContainer = undefined;
|
||||
}
|
||||
|
||||
if (this.contents) {
|
||||
this.contents.remove();
|
||||
this.contents = null;
|
||||
this.contents = undefined;
|
||||
}
|
||||
|
||||
if (this._label) {
|
||||
this._label.remove();
|
||||
this._label = null;
|
||||
this._label = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,7 +182,7 @@ export class Dropdown extends BaseDropdown {
|
||||
}
|
||||
}
|
||||
|
||||
protected renderContents(container: HTMLElement): IDisposable {
|
||||
protected renderContents(container: HTMLElement): IDisposable | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -204,7 +206,7 @@ export class DropdownMenu extends BaseDropdown {
|
||||
private _contextMenuProvider: IContextMenuProvider;
|
||||
private _menuOptions: IMenuOptions;
|
||||
private _actions: IAction[];
|
||||
private actionProvider: IActionProvider;
|
||||
private actionProvider?: IActionProvider;
|
||||
private menuClassName: string;
|
||||
|
||||
constructor(container: HTMLElement, options: IDropdownMenuOptions) {
|
||||
@@ -246,10 +248,10 @@ export class DropdownMenu extends BaseDropdown {
|
||||
getActions: () => this.actions,
|
||||
getActionsContext: () => this.menuOptions ? this.menuOptions.context : null,
|
||||
getActionItem: action => this.menuOptions && this.menuOptions.actionItemProvider ? this.menuOptions.actionItemProvider(action) : null,
|
||||
getKeyBinding: action => this.menuOptions && this.menuOptions.getKeyBinding ? this.menuOptions.getKeyBinding(action) : null,
|
||||
getKeyBinding: action => this.menuOptions && this.menuOptions.getKeyBinding ? this.menuOptions.getKeyBinding(action) : undefined,
|
||||
getMenuClassName: () => this.menuClassName,
|
||||
onHide: () => this.onHide(),
|
||||
actionRunner: this.menuOptions ? this.menuOptions.actionRunner : null,
|
||||
actionRunner: this.menuOptions ? this.menuOptions.actionRunner : undefined,
|
||||
anchorAlignment: this.menuOptions.anchorAlignment
|
||||
});
|
||||
}
|
||||
@@ -268,14 +270,14 @@ export class DropdownMenuActionItem extends BaseActionItem {
|
||||
private menuActionsOrProvider: any;
|
||||
private dropdownMenu: DropdownMenu;
|
||||
private contextMenuProvider: IContextMenuProvider;
|
||||
private actionItemProvider: IActionItemProvider;
|
||||
private keybindings: (action: IAction) => ResolvedKeybinding;
|
||||
private actionItemProvider?: IActionItemProvider;
|
||||
private keybindings?: (action: IAction) => ResolvedKeybinding | undefined;
|
||||
private clazz: string;
|
||||
private anchorAlignmentProvider: (() => AnchorAlignment) | undefined;
|
||||
|
||||
constructor(action: IAction, menuActions: IAction[], contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string, anchorAlignmentProvider?: () => AnchorAlignment);
|
||||
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string, anchorAlignmentProvider?: () => AnchorAlignment);
|
||||
constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string, anchorAlignmentProvider?: () => AnchorAlignment) {
|
||||
constructor(action: IAction, menuActions: IAction[], contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string, anchorAlignmentProvider?: () => AnchorAlignment);
|
||||
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string, anchorAlignmentProvider?: () => AnchorAlignment);
|
||||
constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string, anchorAlignmentProvider?: () => AnchorAlignment) {
|
||||
super(null, action);
|
||||
|
||||
this.menuActionsOrProvider = menuActionsOrProvider;
|
||||
@@ -288,7 +290,7 @@ export class DropdownMenuActionItem extends BaseActionItem {
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable => {
|
||||
const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => {
|
||||
this.element = append(el, $('a.action-label.icon'));
|
||||
addClasses(this.element, this.clazz);
|
||||
|
||||
@@ -327,7 +329,7 @@ export class DropdownMenuActionItem extends BaseActionItem {
|
||||
this.dropdownMenu.menuOptions = {
|
||||
...this.dropdownMenu.menuOptions,
|
||||
get anchorAlignment(): AnchorAlignment {
|
||||
return that.anchorAlignmentProvider();
|
||||
return that.anchorAlignmentProvider!();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export interface IFindInputOptions extends IFindInputStyles {
|
||||
}
|
||||
|
||||
export interface IFindInputStyles extends IInputBoxStyles {
|
||||
inputActiveOptionBorder?: Color;
|
||||
inputActiveOptionBorder?: Color | null;
|
||||
}
|
||||
|
||||
const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input");
|
||||
@@ -44,24 +44,24 @@ export class FindInput extends Widget {
|
||||
private contextViewProvider: IContextViewProvider;
|
||||
private width: number;
|
||||
private placeholder: string;
|
||||
private validation: IInputValidator;
|
||||
private validation?: IInputValidator;
|
||||
private label: string;
|
||||
private fixFocusOnOptionClickEnabled = true;
|
||||
|
||||
private inputActiveOptionBorder: Color;
|
||||
private inputBackground: Color;
|
||||
private inputForeground: Color;
|
||||
private inputBorder: Color;
|
||||
private inputActiveOptionBorder?: Color | null;
|
||||
private inputBackground?: Color | null;
|
||||
private inputForeground?: Color | null;
|
||||
private inputBorder?: Color | null;
|
||||
|
||||
private inputValidationInfoBorder: Color;
|
||||
private inputValidationInfoBackground: Color;
|
||||
private inputValidationInfoForeground: Color;
|
||||
private inputValidationWarningBorder: Color;
|
||||
private inputValidationWarningBackground: Color;
|
||||
private inputValidationWarningForeground: Color;
|
||||
private inputValidationErrorBorder: Color;
|
||||
private inputValidationErrorBackground: Color;
|
||||
private inputValidationErrorForeground: Color;
|
||||
private inputValidationInfoBorder?: Color | null;
|
||||
private inputValidationInfoBackground?: Color | null;
|
||||
private inputValidationInfoForeground?: Color | null;
|
||||
private inputValidationWarningBorder?: Color | null;
|
||||
private inputValidationWarningBackground?: Color | null;
|
||||
private inputValidationWarningForeground?: Color | null;
|
||||
private inputValidationErrorBorder?: Color | null;
|
||||
private inputValidationErrorBackground?: Color | null;
|
||||
private inputValidationErrorForeground?: Color | null;
|
||||
|
||||
private regex: RegexCheckbox;
|
||||
private wholeWords: WholeWordsCheckbox;
|
||||
@@ -90,7 +90,7 @@ export class FindInput extends Widget {
|
||||
private _onRegexKeyDown = this._register(new Emitter<IKeyboardEvent>());
|
||||
public readonly onRegexKeyDown: Event<IKeyboardEvent> = this._onRegexKeyDown.event;
|
||||
|
||||
constructor(parent: HTMLElement, contextViewProvider: IContextViewProvider, private readonly _showOptionButtons: boolean, options?: IFindInputOptions) {
|
||||
constructor(parent: HTMLElement | null, contextViewProvider: IContextViewProvider, private readonly _showOptionButtons: boolean, options: IFindInputOptions) {
|
||||
super();
|
||||
this.contextViewProvider = contextViewProvider;
|
||||
this.width = options.width || 100;
|
||||
@@ -113,15 +113,9 @@ export class FindInput extends Widget {
|
||||
this.inputValidationErrorBackground = options.inputValidationErrorBackground;
|
||||
this.inputValidationErrorForeground = options.inputValidationErrorForeground;
|
||||
|
||||
this.regex = null;
|
||||
this.wholeWords = null;
|
||||
this.caseSensitive = null;
|
||||
this.domNode = null;
|
||||
this.inputBox = null;
|
||||
this.buildDomNode(options.appendCaseSensitiveLabel || '', options.appendWholeWordsLabel || '', options.appendRegexLabel || '', options.history || [], !!options.flexibleHeight);
|
||||
|
||||
this.buildDomNode(options.appendCaseSensitiveLabel || '', options.appendWholeWordsLabel || '', options.appendRegexLabel || '', options.history, options.flexibleHeight);
|
||||
|
||||
if (Boolean(parent)) {
|
||||
if (parent) {
|
||||
parent.appendChild(this.domNode);
|
||||
}
|
||||
|
||||
@@ -208,7 +202,7 @@ export class FindInput extends Widget {
|
||||
protected applyStyles(): void {
|
||||
if (this.domNode) {
|
||||
const checkBoxStyles: ICheckboxStyles = {
|
||||
inputActiveOptionBorder: this.inputActiveOptionBorder,
|
||||
inputActiveOptionBorder: this.inputActiveOptionBorder || undefined,
|
||||
};
|
||||
this.regex.style(checkBoxStyles);
|
||||
this.wholeWords.style(checkBoxStyles);
|
||||
@@ -298,7 +292,7 @@ export class FindInput extends Widget {
|
||||
placeholder: this.placeholder || '',
|
||||
ariaLabel: this.label || '',
|
||||
validationOptions: {
|
||||
validation: this.validation || null
|
||||
validation: this.validation
|
||||
},
|
||||
inputBackground: this.inputBackground,
|
||||
inputForeground: this.inputForeground,
|
||||
@@ -319,7 +313,7 @@ export class FindInput extends Widget {
|
||||
this.regex = this._register(new RegexCheckbox({
|
||||
appendTitle: appendRegexLabel,
|
||||
isChecked: false,
|
||||
inputActiveOptionBorder: this.inputActiveOptionBorder
|
||||
inputActiveOptionBorder: this.inputActiveOptionBorder || undefined
|
||||
}));
|
||||
this._register(this.regex.onChange(viaKeyboard => {
|
||||
this._onDidOptionChange.fire(viaKeyboard);
|
||||
@@ -336,7 +330,7 @@ export class FindInput extends Widget {
|
||||
this.wholeWords = this._register(new WholeWordsCheckbox({
|
||||
appendTitle: appendWholeWordsLabel,
|
||||
isChecked: false,
|
||||
inputActiveOptionBorder: this.inputActiveOptionBorder
|
||||
inputActiveOptionBorder: this.inputActiveOptionBorder || undefined
|
||||
}));
|
||||
this._register(this.wholeWords.onChange(viaKeyboard => {
|
||||
this._onDidOptionChange.fire(viaKeyboard);
|
||||
@@ -350,7 +344,7 @@ export class FindInput extends Widget {
|
||||
this.caseSensitive = this._register(new CaseSensitiveCheckbox({
|
||||
appendTitle: appendCaseSensitiveLabel,
|
||||
isChecked: false,
|
||||
inputActiveOptionBorder: this.inputActiveOptionBorder
|
||||
inputActiveOptionBorder: this.inputActiveOptionBorder || undefined
|
||||
}));
|
||||
this._register(this.caseSensitive.onChange(viaKeyboard => {
|
||||
this._onDidOptionChange.fire(viaKeyboard);
|
||||
@@ -370,7 +364,7 @@ export class FindInput extends Widget {
|
||||
if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) {
|
||||
let index = indexes.indexOf(<HTMLElement>document.activeElement);
|
||||
if (index >= 0) {
|
||||
let newIndex: number;
|
||||
let newIndex: number = -1;
|
||||
if (event.equals(KeyCode.RightArrow)) {
|
||||
newIndex = (index + 1) % indexes.length;
|
||||
} else if (event.equals(KeyCode.LeftArrow)) {
|
||||
@@ -405,19 +399,27 @@ export class FindInput extends Widget {
|
||||
}
|
||||
|
||||
public validate(): void {
|
||||
this.inputBox.validate();
|
||||
if (this.inputBox) {
|
||||
this.inputBox.validate();
|
||||
}
|
||||
}
|
||||
|
||||
public showMessage(message: InputBoxMessage): void {
|
||||
this.inputBox.showMessage(message);
|
||||
if (this.inputBox) {
|
||||
this.inputBox.showMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public clearMessage(): void {
|
||||
this.inputBox.hideMessage();
|
||||
if (this.inputBox) {
|
||||
this.inputBox.hideMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private clearValidation(): void {
|
||||
this.inputBox.hideMessage();
|
||||
if (this.inputBox) {
|
||||
this.inputBox.hideMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
||||
@@ -8,7 +8,9 @@ import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { tail2 as tail, equals } from 'vs/base/common/arrays';
|
||||
import { orthogonal, IView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles } from './gridview';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { $ } from 'vs/base/browser/dom';
|
||||
import { LayoutPriority } from 'vs/base/browser/ui/splitview/splitview';
|
||||
|
||||
export { Orientation } from './gridview';
|
||||
|
||||
@@ -186,6 +188,7 @@ export interface IGridStyles extends IGridViewStyles { }
|
||||
|
||||
export interface IGridOptions {
|
||||
styles?: IGridStyles;
|
||||
proportionalLayout?: boolean;
|
||||
}
|
||||
|
||||
export class Grid<T extends IView> implements IDisposable {
|
||||
@@ -646,3 +649,63 @@ export function createSerializedGrid(gridDescriptor: GridDescriptor): ISerialize
|
||||
height: height || 1
|
||||
};
|
||||
}
|
||||
|
||||
export class View implements IView {
|
||||
|
||||
readonly element = $('.grid-view-view');
|
||||
|
||||
private visible = false;
|
||||
private width: number | undefined;
|
||||
private height: number | undefined;
|
||||
private orientation: Orientation = Orientation.HORIZONTAL;
|
||||
|
||||
get minimumWidth(): number { return this.visible ? this.view.minimumWidth : 0; }
|
||||
get maximumWidth(): number { return this.visible ? this.view.maximumWidth : (this.orientation === Orientation.HORIZONTAL ? 0 : Number.POSITIVE_INFINITY); }
|
||||
get minimumHeight(): number { return this.visible ? this.view.minimumHeight : 0; }
|
||||
get maximumHeight(): number { return this.visible ? this.view.maximumHeight : (this.orientation === Orientation.VERTICAL ? 0 : Number.POSITIVE_INFINITY); }
|
||||
|
||||
private onDidChangeVisibility = new Emitter<{ width: number; height: number; } | undefined>();
|
||||
readonly onDidChange: Event<{ width: number; height: number; } | undefined>;
|
||||
|
||||
get priority(): LayoutPriority | undefined { return this.view.priority; }
|
||||
get snapSize(): number | undefined { return this.visible ? this.view.snapSize : undefined; }
|
||||
|
||||
constructor(private view: IView) {
|
||||
this.show();
|
||||
this.onDidChange = Event.any(this.onDidChangeVisibility.event, Event.filter(view.onDidChange, () => this.visible));
|
||||
}
|
||||
|
||||
show(): void {
|
||||
if (this.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.visible = true;
|
||||
|
||||
this.element.appendChild(this.view.element);
|
||||
this.onDidChangeVisibility.fire(typeof this.width === 'number' ? { width: this.width, height: this.height! } : undefined);
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.visible = false;
|
||||
|
||||
this.element.removeChild(this.view.element);
|
||||
this.onDidChangeVisibility.fire(undefined);
|
||||
}
|
||||
|
||||
layout(width: number, height: number, orientation: Orientation): void {
|
||||
this.orientation = orientation;
|
||||
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.view.layout(width, height, orientation);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./gridview';
|
||||
import { Event, anyEvent, Emitter, mapEvent, Relay } from 'vs/base/common/event';
|
||||
import { Event, Emitter, Relay } from 'vs/base/common/event';
|
||||
import { Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
|
||||
import { SplitView, IView as ISplitView, Sizing, LayoutPriority, ISplitViewStyles } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -21,10 +21,10 @@ export interface IView {
|
||||
readonly maximumWidth: number;
|
||||
readonly minimumHeight: number;
|
||||
readonly maximumHeight: number;
|
||||
readonly onDidChange: Event<{ width: number; height: number; }>;
|
||||
readonly onDidChange: Event<{ width: number; height: number; } | undefined>;
|
||||
readonly priority?: LayoutPriority;
|
||||
readonly snapSize?: number;
|
||||
layout(width: number, height: number): void;
|
||||
layout(width: number, height: number, orientation: Orientation): void;
|
||||
}
|
||||
|
||||
export function orthogonal(orientation: Orientation): Orientation {
|
||||
@@ -147,10 +147,10 @@ class BranchNode implements ISplitView, IDisposable {
|
||||
this._orthogonalSize = orthogonalSize;
|
||||
|
||||
this.element = $('.monaco-grid-branch-node');
|
||||
this.splitview = new SplitView(this.element, { orientation, styles });
|
||||
this.splitview = new SplitView(this.element, { orientation, styles, proportionalLayout });
|
||||
this.splitview.layout(size);
|
||||
|
||||
const onDidSashReset = mapEvent(this.splitview.onDidSashReset, i => [i]);
|
||||
const onDidSashReset = Event.map(this.splitview.onDidSashReset, i => [i]);
|
||||
this.splitviewSashResetDisposable = onDidSashReset(this._onDidSashReset.fire, this._onDidSashReset);
|
||||
}
|
||||
|
||||
@@ -300,15 +300,15 @@ class BranchNode implements ISplitView, IDisposable {
|
||||
}
|
||||
|
||||
private onDidChildrenChange(): void {
|
||||
const onDidChildrenChange = anyEvent(...this.children.map(c => c.onDidChange));
|
||||
const onDidChildrenChange = Event.map(Event.any(...this.children.map(c => c.onDidChange)), () => undefined);
|
||||
this.childrenChangeDisposable.dispose();
|
||||
this.childrenChangeDisposable = onDidChildrenChange(this._onDidChange.fire, this._onDidChange);
|
||||
|
||||
const onDidChildrenSashReset = anyEvent(...this.children.map((c, i) => mapEvent(c.onDidSashReset, location => [i, ...location])));
|
||||
const onDidChildrenSashReset = Event.any(...this.children.map((c, i) => Event.map(c.onDidSashReset, location => [i, ...location])));
|
||||
this.childrenSashResetDisposable.dispose();
|
||||
this.childrenSashResetDisposable = onDidChildrenSashReset(this._onDidSashReset.fire, this._onDidSashReset);
|
||||
|
||||
this._onDidChange.fire();
|
||||
this._onDidChange.fire(undefined);
|
||||
}
|
||||
|
||||
trySet2x2(other: BranchNode): IDisposable {
|
||||
@@ -348,8 +348,8 @@ class BranchNode implements ISplitView, IDisposable {
|
||||
mySash.linkedSash = otherSash;
|
||||
otherSash.linkedSash = mySash;
|
||||
|
||||
this._onDidChange.fire();
|
||||
other._onDidChange.fire();
|
||||
this._onDidChange.fire(undefined);
|
||||
other._onDidChange.fire(undefined);
|
||||
|
||||
return toDisposable(() => {
|
||||
mySash.linkedSash = otherSash.linkedSash = undefined;
|
||||
@@ -391,7 +391,7 @@ class LeafNode implements ISplitView, IDisposable {
|
||||
set linkedWidthNode(node: LeafNode | undefined) {
|
||||
this._onDidLinkedWidthNodeChange.input = node ? node._onDidViewChange : Event.None;
|
||||
this._linkedWidthNode = node;
|
||||
this._onDidSetLinkedNode.fire();
|
||||
this._onDidSetLinkedNode.fire(undefined);
|
||||
}
|
||||
|
||||
private _onDidLinkedHeightNodeChange = new Relay<number | undefined>();
|
||||
@@ -400,7 +400,7 @@ class LeafNode implements ISplitView, IDisposable {
|
||||
set linkedHeightNode(node: LeafNode | undefined) {
|
||||
this._onDidLinkedHeightNodeChange.input = node ? node._onDidViewChange : Event.None;
|
||||
this._linkedHeightNode = node;
|
||||
this._onDidSetLinkedNode.fire();
|
||||
this._onDidSetLinkedNode.fire(undefined);
|
||||
}
|
||||
|
||||
private _onDidSetLinkedNode = new Emitter<number | undefined>();
|
||||
@@ -414,8 +414,8 @@ class LeafNode implements ISplitView, IDisposable {
|
||||
) {
|
||||
this._orthogonalSize = orthogonalSize;
|
||||
|
||||
this._onDidViewChange = mapEvent(this.view.onDidChange, this.orientation === Orientation.HORIZONTAL ? e => e && e.width : e => e && e.height);
|
||||
this.onDidChange = anyEvent(this._onDidViewChange, this._onDidSetLinkedNode.event, this._onDidLinkedWidthNodeChange.event, this._onDidLinkedHeightNodeChange.event);
|
||||
this._onDidViewChange = Event.map(this.view.onDidChange, e => e && (this.orientation === Orientation.VERTICAL ? e.width : e.height));
|
||||
this.onDidChange = Event.any(this._onDidViewChange, this._onDidSetLinkedNode.event, this._onDidLinkedWidthNodeChange.event, this._onDidLinkedHeightNodeChange.event);
|
||||
}
|
||||
|
||||
get width(): number {
|
||||
@@ -480,12 +480,12 @@ class LeafNode implements ISplitView, IDisposable {
|
||||
|
||||
layout(size: number): void {
|
||||
this._size = size;
|
||||
return this.view.layout(this.width, this.height);
|
||||
return this.view.layout(this.width, this.height, orthogonal(this.orientation));
|
||||
}
|
||||
|
||||
orthogonalLayout(size: number): void {
|
||||
this._orthogonalSize = size;
|
||||
return this.view.layout(this.width, this.height);
|
||||
return this.view.layout(this.width, this.height, orthogonal(this.orientation));
|
||||
}
|
||||
|
||||
dispose(): void { }
|
||||
@@ -547,7 +547,7 @@ export class GridView implements IDisposable {
|
||||
this._root = root;
|
||||
this.element.appendChild(root.element);
|
||||
this.onDidSashResetRelay.input = root.onDidSashReset;
|
||||
this._onDidChange.input = mapEvent(root.onDidChange, () => undefined); // TODO
|
||||
this._onDidChange.input = Event.map(root.onDidChange, () => undefined); // TODO
|
||||
}
|
||||
|
||||
get orientation(): Orientation {
|
||||
@@ -802,8 +802,7 @@ export class GridView implements IDisposable {
|
||||
const children: GridNode[] = [];
|
||||
let offset = 0;
|
||||
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
const child = node.children[i];
|
||||
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 }
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
|
||||
@@ -14,7 +13,7 @@ export interface IHighlight {
|
||||
end: number;
|
||||
}
|
||||
|
||||
export class HighlightedLabel implements IDisposable {
|
||||
export class HighlightedLabel {
|
||||
|
||||
private domNode: HTMLElement;
|
||||
private text: string;
|
||||
@@ -33,7 +32,7 @@ export class HighlightedLabel implements IDisposable {
|
||||
return this.domNode;
|
||||
}
|
||||
|
||||
set(text: string, highlights: IHighlight[] = [], title: string = '', escapeNewLines?: boolean) {
|
||||
set(text: string | undefined, highlights: IHighlight[] = [], title: string = '', escapeNewLines?: boolean) {
|
||||
if (!text) {
|
||||
text = '';
|
||||
}
|
||||
@@ -58,12 +57,10 @@ export class HighlightedLabel implements IDisposable {
|
||||
private render() {
|
||||
dom.clearNode(this.domNode);
|
||||
|
||||
let htmlContent: string[] = [],
|
||||
highlight: IHighlight,
|
||||
pos = 0;
|
||||
let htmlContent: string[] = [];
|
||||
let pos = 0;
|
||||
|
||||
for (let i = 0; i < this.highlights.length; i++) {
|
||||
highlight = this.highlights[i];
|
||||
for (const highlight of this.highlights) {
|
||||
if (highlight.end === highlight.start) {
|
||||
continue;
|
||||
}
|
||||
@@ -93,11 +90,6 @@ export class HighlightedLabel implements IDisposable {
|
||||
this.didEverRender = true;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.text = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
this.highlights = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
}
|
||||
|
||||
static escapeNewLines(text: string, highlights: IHighlight[]): string {
|
||||
|
||||
let total = 0;
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'vs/css!./iconlabel';
|
||||
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 { IDisposable, combinedDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IIconLabelCreationOptions {
|
||||
supportHighlights?: boolean;
|
||||
@@ -100,13 +100,13 @@ export class IconLabel extends Disposable {
|
||||
this.labelDescriptionContainer = this._register(new FastLabelNode(dom.append(this.domNode.element, dom.$('.monaco-icon-label-description-container'))));
|
||||
|
||||
if (options && options.supportHighlights) {
|
||||
this.labelNode = this._register(new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')), !options.donotSupportOcticons));
|
||||
this.labelNode = new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')), !options.donotSupportOcticons);
|
||||
} else {
|
||||
this.labelNode = this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name'))));
|
||||
}
|
||||
|
||||
if (options && options.supportDescriptionHighlights) {
|
||||
this.descriptionNodeFactory = () => this._register(new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')), !options.donotSupportOcticons));
|
||||
this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')), !options.donotSupportOcticons);
|
||||
} else {
|
||||
this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description'))));
|
||||
}
|
||||
@@ -116,13 +116,7 @@ export class IconLabel extends Disposable {
|
||||
return this.domNode.element;
|
||||
}
|
||||
|
||||
onClick(callback: (event: MouseEvent) => void): IDisposable {
|
||||
return combinedDisposable([
|
||||
dom.addDisposableListener(this.labelDescriptionContainer.element, dom.EventType.CLICK, (e: MouseEvent) => callback(e)),
|
||||
]);
|
||||
}
|
||||
|
||||
setValue(label?: string, description?: string, options?: IIconLabelValueOptions): void {
|
||||
setLabel(label?: string, description?: string, options?: IIconLabelValueOptions): void {
|
||||
const classes = ['monaco-icon-label'];
|
||||
if (options) {
|
||||
if (options.extraClasses) {
|
||||
@@ -138,7 +132,7 @@ export class IconLabel extends Disposable {
|
||||
this.domNode.title = options && options.title ? options.title : '';
|
||||
|
||||
if (this.labelNode instanceof HighlightedLabel) {
|
||||
this.labelNode.set(label || '', options ? options.matches : void 0, options && options.title ? options.title : void 0, options && options.labelEscapeNewLines);
|
||||
this.labelNode.set(label || '', options ? options.matches : undefined, options && options.title ? options.title : undefined, options && options.labelEscapeNewLines);
|
||||
} else {
|
||||
this.labelNode.textContent = label || '';
|
||||
}
|
||||
@@ -149,7 +143,7 @@ export class IconLabel extends Disposable {
|
||||
}
|
||||
|
||||
if (this.descriptionNode instanceof HighlightedLabel) {
|
||||
this.descriptionNode.set(description || '', options ? options.descriptionMatches : void 0);
|
||||
this.descriptionNode.set(description || '', options ? options.descriptionMatches : undefined);
|
||||
if (options && options.descriptionTitle) {
|
||||
this.descriptionNode.element.title = options.descriptionTitle;
|
||||
} else {
|
||||
@@ -163,4 +157,3 @@ export class IconLabel extends Disposable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,46 +23,46 @@ import { IHistoryNavigationWidget } from 'vs/base/browser/history';
|
||||
const $ = dom.$;
|
||||
|
||||
export interface IInputOptions extends IInputBoxStyles {
|
||||
placeholder?: string;
|
||||
ariaLabel?: string;
|
||||
type?: string;
|
||||
validationOptions?: IInputValidationOptions;
|
||||
flexibleHeight?: boolean;
|
||||
actions?: IAction[];
|
||||
readonly placeholder?: string;
|
||||
readonly ariaLabel?: string;
|
||||
readonly type?: string;
|
||||
readonly validationOptions?: IInputValidationOptions;
|
||||
readonly flexibleHeight?: boolean;
|
||||
readonly actions?: IAction[];
|
||||
|
||||
// {{SQL CARBON EDIT}} Candidate for addition to vscode
|
||||
min?: string;
|
||||
max?: string;
|
||||
useDefaultValidation?: boolean;
|
||||
readonly min?: string;
|
||||
readonly max?: string;
|
||||
readonly useDefaultValidation?: boolean;
|
||||
}
|
||||
|
||||
export interface IInputBoxStyles {
|
||||
inputBackground?: Color;
|
||||
inputForeground?: Color;
|
||||
inputBorder?: Color;
|
||||
inputValidationInfoBorder?: Color;
|
||||
inputValidationInfoBackground?: Color;
|
||||
inputValidationInfoForeground?: Color;
|
||||
inputValidationWarningBorder?: Color;
|
||||
inputValidationWarningBackground?: Color;
|
||||
inputValidationWarningForeground?: Color;
|
||||
inputValidationErrorBorder?: Color;
|
||||
inputValidationErrorBackground?: Color;
|
||||
inputValidationErrorForeground?: Color;
|
||||
readonly inputBackground?: Color | null;
|
||||
readonly inputForeground?: Color | null;
|
||||
readonly inputBorder?: Color | null;
|
||||
readonly inputValidationInfoBorder?: Color | null;
|
||||
readonly inputValidationInfoBackground?: Color | null;
|
||||
readonly inputValidationInfoForeground?: Color | null;
|
||||
readonly inputValidationWarningBorder?: Color | null;
|
||||
readonly inputValidationWarningBackground?: Color | null;
|
||||
readonly inputValidationWarningForeground?: Color | null;
|
||||
readonly inputValidationErrorBorder?: Color | null;
|
||||
readonly inputValidationErrorBackground?: Color | null;
|
||||
readonly inputValidationErrorForeground?: Color | null;
|
||||
}
|
||||
|
||||
export interface IInputValidator {
|
||||
(value: string): IMessage;
|
||||
(value: string): IMessage | null;
|
||||
}
|
||||
|
||||
export interface IMessage {
|
||||
content: string;
|
||||
formatContent?: boolean; // defaults to false
|
||||
type?: MessageType;
|
||||
readonly content: string;
|
||||
readonly formatContent?: boolean; // defaults to false
|
||||
readonly type?: MessageType;
|
||||
}
|
||||
|
||||
export interface IInputValidationOptions {
|
||||
validation: IInputValidator;
|
||||
validation?: IInputValidator;
|
||||
}
|
||||
|
||||
export const enum MessageType {
|
||||
@@ -89,34 +89,35 @@ export const defaultOpts = {
|
||||
};
|
||||
|
||||
export class InputBox extends Widget {
|
||||
private contextViewProvider: IContextViewProvider;
|
||||
private contextViewProvider?: IContextViewProvider;
|
||||
element: HTMLElement;
|
||||
private input: HTMLInputElement;
|
||||
private mirror: HTMLElement;
|
||||
private actionbar: ActionBar;
|
||||
private actionbar?: ActionBar;
|
||||
private options: IInputOptions;
|
||||
private message: IMessage;
|
||||
private message: IMessage | null;
|
||||
private placeholder: string;
|
||||
private ariaLabel: string;
|
||||
private validation: IInputValidator;
|
||||
private state = 'idle';
|
||||
private cachedHeight: number;
|
||||
private validation?: IInputValidator;
|
||||
private state: string | null = 'idle';
|
||||
private cachedHeight: number | null;
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// {{SQL CARBON EDIT}} - Add showValidationMessage and set inputBackground, inputForeground, and inputBorder as protected
|
||||
protected showValidationMessage: boolean;
|
||||
protected inputBackground: Color;
|
||||
protected inputForeground: Color;
|
||||
protected inputBorder: Color;
|
||||
protected inputBackground?: Color | null;
|
||||
protected inputForeground?: Color | null;
|
||||
protected inputBorder?: Color | null;
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
|
||||
private inputValidationInfoBorder: Color;
|
||||
private inputValidationInfoBackground: Color;
|
||||
private inputValidationInfoForeground: Color;
|
||||
private inputValidationWarningBorder: Color;
|
||||
private inputValidationWarningBackground: Color;
|
||||
private inputValidationWarningForeground: Color;
|
||||
private inputValidationErrorBorder: Color;
|
||||
private inputValidationErrorBackground: Color;
|
||||
private inputValidationErrorForeground: Color;
|
||||
private inputValidationInfoBorder?: Color | null;
|
||||
private inputValidationInfoBackground?: Color | null;
|
||||
private inputValidationInfoForeground?: Color | null;
|
||||
private inputValidationWarningBorder?: Color | null;
|
||||
private inputValidationWarningBackground?: Color | null;
|
||||
private inputValidationWarningForeground?: Color | null;
|
||||
private inputValidationErrorBorder?: Color | null;
|
||||
private inputValidationErrorBackground?: Color | null;
|
||||
private inputValidationErrorForeground?: Color | null;
|
||||
|
||||
private _onDidChange = this._register(new Emitter<string>());
|
||||
public readonly onDidChange: Event<string> = this._onDidChange.event;
|
||||
@@ -124,7 +125,7 @@ export class InputBox extends Widget {
|
||||
private _onDidHeightChange = this._register(new Emitter<number>());
|
||||
public readonly onDidHeightChange: Event<number> = this._onDidHeightChange.event;
|
||||
|
||||
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, options?: IInputOptions) {
|
||||
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options?: IInputOptions) {
|
||||
super();
|
||||
|
||||
this.contextViewProvider = contextViewProvider;
|
||||
@@ -392,7 +393,7 @@ export class InputBox extends Widget {
|
||||
return errorMsg ? errorMsg.type !== MessageType.ERROR : true;
|
||||
}
|
||||
|
||||
private stylesForType(type: MessageType): { border: Color; background: Color; foreground: Color } {
|
||||
private stylesForType(type: MessageType | undefined): { border: Color | undefined | null; background: Color | undefined | null; foreground: Color | undefined | null } {
|
||||
switch (type) {
|
||||
case MessageType.INFO: return { border: this.inputValidationInfoBorder, background: this.inputValidationInfoBackground, foreground: this.inputValidationInfoForeground };
|
||||
case MessageType.WARNING: return { border: this.inputValidationWarningBorder, background: this.inputValidationWarningBackground, foreground: this.inputValidationWarningForeground };
|
||||
@@ -400,7 +401,7 @@ export class InputBox extends Widget {
|
||||
}
|
||||
}
|
||||
|
||||
private classForType(type: MessageType): string {
|
||||
private classForType(type: MessageType | undefined): string {
|
||||
switch (type) {
|
||||
case MessageType.INFO: return 'info';
|
||||
case MessageType.WARNING: return 'warning';
|
||||
@@ -423,6 +424,10 @@ export class InputBox extends Widget {
|
||||
getAnchor: () => this.element,
|
||||
anchorAlignment: AnchorAlignment.RIGHT,
|
||||
render: (container: HTMLElement) => {
|
||||
if (!this.message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
div = dom.append(container, $('.monaco-inputbox-container'));
|
||||
layout();
|
||||
|
||||
@@ -465,7 +470,7 @@ export class InputBox extends Widget {
|
||||
this.validate();
|
||||
this.updateMirror();
|
||||
|
||||
if (this.state === 'open') {
|
||||
if (this.state === 'open' && this.contextViewProvider) {
|
||||
this.contextViewProvider.layout();
|
||||
}
|
||||
}
|
||||
@@ -541,15 +546,13 @@ export class InputBox extends Widget {
|
||||
public dispose(): void {
|
||||
this._hideMessage();
|
||||
|
||||
this.element = null;
|
||||
this.input = null;
|
||||
this.contextViewProvider = null;
|
||||
this.element = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
this.input = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
this.contextViewProvider = undefined;
|
||||
this.message = null;
|
||||
this.placeholder = null;
|
||||
this.ariaLabel = null;
|
||||
this.validation = null;
|
||||
this.validation = undefined;
|
||||
this.state = null;
|
||||
this.actionbar = null;
|
||||
this.actionbar = undefined;
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
@@ -563,7 +566,7 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge
|
||||
|
||||
private readonly history: HistoryNavigator<string>;
|
||||
|
||||
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, options: IHistoryInputOptions) {
|
||||
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options: IHistoryInputOptions) {
|
||||
super(container, contextViewProvider, options);
|
||||
this.history = new HistoryNavigator<string>(options.history, 100);
|
||||
}
|
||||
@@ -614,7 +617,7 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge
|
||||
this.history.clear();
|
||||
}
|
||||
|
||||
private getCurrentValue(): string {
|
||||
private getCurrentValue(): string | null {
|
||||
let currentValue = this.history.current();
|
||||
if (!currentValue) {
|
||||
currentValue = this.history.last();
|
||||
@@ -623,11 +626,11 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
private getPreviousValue(): string {
|
||||
private getPreviousValue(): string | null {
|
||||
return this.history.previous() || this.history.first();
|
||||
}
|
||||
|
||||
private getNextValue(): string {
|
||||
private getNextValue(): string | null {
|
||||
return this.history.next() || this.history.last();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./keybindingLabel';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { equals } from 'vs/base/common/objects';
|
||||
import { OperatingSystem } from 'vs/base/common/platform';
|
||||
import { ResolvedKeybinding, ResolvedKeybindingPart } from 'vs/base/common/keyCodes';
|
||||
import { UILabelProvider } from 'vs/base/common/keybindingLabels';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
@@ -26,14 +26,18 @@ export interface Matches {
|
||||
chordPart: PartMatches;
|
||||
}
|
||||
|
||||
export class KeybindingLabel implements IDisposable {
|
||||
export interface KeybindingLabelOptions {
|
||||
renderUnboundKeybindings: boolean;
|
||||
}
|
||||
|
||||
export class KeybindingLabel {
|
||||
|
||||
private domNode: HTMLElement;
|
||||
private keybinding: ResolvedKeybinding;
|
||||
private matches: Matches;
|
||||
private matches: Matches | undefined;
|
||||
private didEverRender: boolean;
|
||||
|
||||
constructor(container: HTMLElement, private os: OperatingSystem) {
|
||||
constructor(container: HTMLElement, private os: OperatingSystem, private options?: KeybindingLabelOptions) {
|
||||
this.domNode = dom.append(container, $('.monaco-keybinding'));
|
||||
this.didEverRender = false;
|
||||
container.appendChild(this.domNode);
|
||||
@@ -43,7 +47,7 @@ export class KeybindingLabel implements IDisposable {
|
||||
return this.domNode;
|
||||
}
|
||||
|
||||
set(keybinding: ResolvedKeybinding, matches: Matches) {
|
||||
set(keybinding: ResolvedKeybinding, matches?: Matches) {
|
||||
if (this.didEverRender && this.keybinding === keybinding && KeybindingLabel.areSame(this.matches, matches)) {
|
||||
return;
|
||||
}
|
||||
@@ -66,6 +70,8 @@ export class KeybindingLabel implements IDisposable {
|
||||
this.renderPart(this.domNode, chordPart, this.matches ? this.matches.chordPart : null);
|
||||
}
|
||||
this.domNode.title = this.keybinding.getAriaLabel() || '';
|
||||
} else if (this.options && this.options.renderUnboundKeybindings) {
|
||||
this.renderUnbound(this.domNode);
|
||||
}
|
||||
|
||||
this.didEverRender = true;
|
||||
@@ -98,10 +104,11 @@ export class KeybindingLabel implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
private renderUnbound(parent: HTMLElement): void {
|
||||
dom.append(parent, $('span.monaco-keybinding-key', undefined, localize('unbound', "Unbound")));
|
||||
}
|
||||
|
||||
private static areSame(a: Matches, b: Matches): boolean {
|
||||
private static areSame(a: Matches | undefined, b: Matches | undefined): boolean {
|
||||
if (a === b || (!a && !b)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-list.horizontal-scrolling .monaco-list-rows {
|
||||
width: auto;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.monaco-list-row {
|
||||
position: absolute;
|
||||
-moz-box-sizing: border-box;
|
||||
@@ -52,4 +57,138 @@
|
||||
/* Focus */
|
||||
.monaco-list.element-focused, .monaco-list.selection-single, .monaco-list.selection-multiple {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
/* Dnd */
|
||||
.monaco-list-drag-image {
|
||||
display: inline-block;
|
||||
padding: 1px 7px;
|
||||
border-radius: 10px;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* Type filter */
|
||||
|
||||
.monaco-list-type-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
border-radius: 2px;
|
||||
padding: 0px 3px;
|
||||
max-width: calc(100% - 10px);
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-align: right;
|
||||
box-sizing: border-box;
|
||||
cursor: all-scroll;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
height: 20px;
|
||||
z-index: 1;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter.dragging {
|
||||
transition: top 0.2s, left 0.2s;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter.ne {
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter.nw {
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter > .controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
transition: width 0.2s;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter.dragging > .controls,
|
||||
.monaco-list-type-filter:hover > .controls {
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter > .controls > * {
|
||||
box-sizing: border-box;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 0 0 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter > .controls > .filter {
|
||||
-webkit-appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url("media/no-filter.svg");
|
||||
background-position: 50% 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter > .controls > .filter:checked {
|
||||
background-image: url("media/filter.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.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");
|
||||
}
|
||||
|
||||
.monaco-list-type-filter-message {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 40px 1em 1em 1em;
|
||||
text-align: center;
|
||||
white-space: normal;
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter-message:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Electron */
|
||||
|
||||
.monaco-list-type-filter {
|
||||
cursor: -webkit-grab;
|
||||
}
|
||||
|
||||
.monaco-list-type-filter.dragging {
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { GestureEvent } from 'vs/base/browser/touch';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
|
||||
export interface IListVirtualDelegate<T> {
|
||||
getHeight(element: T): number;
|
||||
@@ -15,7 +17,7 @@ export interface IListRenderer<T, TTemplateData> {
|
||||
templateId: string;
|
||||
renderTemplate(container: HTMLElement): TTemplateData;
|
||||
renderElement(element: T, index: number, templateData: TTemplateData): void;
|
||||
disposeElement(element: T, index: number, templateData: TTemplateData): void;
|
||||
disposeElement?(element: T, index: number, templateData: TTemplateData): void;
|
||||
disposeTemplate(templateData: TTemplateData): void;
|
||||
}
|
||||
|
||||
@@ -43,6 +45,12 @@ export interface IListGestureEvent<T> {
|
||||
index: number | undefined;
|
||||
}
|
||||
|
||||
export interface IListDragEvent<T> {
|
||||
browserEvent: DragEvent;
|
||||
element: T | undefined;
|
||||
index: number | undefined;
|
||||
}
|
||||
|
||||
export interface IListContextMenuEvent<T> {
|
||||
browserEvent: UIEvent;
|
||||
element: T | undefined;
|
||||
@@ -52,4 +60,47 @@ export interface IListContextMenuEvent<T> {
|
||||
|
||||
export interface IIdentityProvider<T> {
|
||||
getId(element: T): { toString(): string; };
|
||||
}
|
||||
}
|
||||
|
||||
export enum ListAriaRootRole {
|
||||
/** default tree structure role */
|
||||
TREE = 'tree',
|
||||
|
||||
/** role='tree' can interfere with screenreaders reading nested elements inside the tree row. Use FORM in that case. */
|
||||
FORM = 'form'
|
||||
}
|
||||
|
||||
export interface IKeyboardNavigationLabelProvider<T> {
|
||||
|
||||
/**
|
||||
* Return a keyboard navigation label which will be used by the
|
||||
* list for filtering/navigating. Return `undefined` to make an
|
||||
* element always match.
|
||||
*/
|
||||
getKeyboardNavigationLabel(element: T): { toString(): string | undefined; } | undefined;
|
||||
mightProducePrintableCharacter?(event: IKeyboardEvent): boolean;
|
||||
}
|
||||
|
||||
export const enum ListDragOverEffect {
|
||||
Copy,
|
||||
Move
|
||||
}
|
||||
|
||||
export interface IListDragOverReaction {
|
||||
accept: boolean;
|
||||
effect?: ListDragOverEffect;
|
||||
feedback?: number[]; // use -1 for entire list
|
||||
}
|
||||
|
||||
export const ListDragOverReactions = {
|
||||
reject(): IListDragOverReaction { return { accept: false }; },
|
||||
accept(): IListDragOverReaction { return { accept: true }; },
|
||||
};
|
||||
|
||||
export interface IListDragAndDrop<T> {
|
||||
getDragURI(element: T): string | null;
|
||||
getDragLabel?(elements: T[]): 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;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { range } from 'vs/base/common/arrays';
|
||||
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent } from './list';
|
||||
import { List, IListStyles, IListOptions } from './listWidget';
|
||||
import { IPagedModel } from 'vs/base/common/paging';
|
||||
import { Event, mapEvent } from 'vs/base/common/event';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
|
||||
export interface IPagedRenderer<TElement, TTemplateData> extends IListRenderer<TElement, TTemplateData> {
|
||||
@@ -58,10 +58,6 @@ class PagedRenderer<TElement, TTemplateData> implements IListRenderer<number, IT
|
||||
promise.then(entry => this.renderer.renderElement(entry, index, data.data!));
|
||||
}
|
||||
|
||||
disposeElement(): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
disposeTemplate(data: ITemplateData<TTemplateData>): void {
|
||||
if (data.disposable) {
|
||||
data.disposable.dispose();
|
||||
@@ -118,23 +114,23 @@ export class PagedList<T> implements IDisposable {
|
||||
}
|
||||
|
||||
get onFocusChange(): Event<IListEvent<T>> {
|
||||
return mapEvent(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
|
||||
return Event.map(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
|
||||
}
|
||||
|
||||
get onOpen(): Event<IListEvent<T>> {
|
||||
return mapEvent(this.list.onOpen, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
|
||||
return Event.map(this.list.onDidOpen, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
|
||||
}
|
||||
|
||||
get onSelectionChange(): Event<IListEvent<T>> {
|
||||
return mapEvent(this.list.onSelectionChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
|
||||
return Event.map(this.list.onSelectionChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
|
||||
}
|
||||
|
||||
get onPin(): Event<IListEvent<T>> {
|
||||
return mapEvent(this.list.onPin, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
|
||||
return Event.map(this.list.onPin, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
|
||||
}
|
||||
|
||||
get onContextMenu(): Event<IListContextMenuEvent<T>> {
|
||||
return mapEvent(this.list.onContextMenu, ({ element, index, anchor, browserEvent }) => (typeof element === 'undefined' ? { element, index, anchor, browserEvent } : { element: this._model.get(element), index, anchor, browserEvent }));
|
||||
return Event.map(this.list.onContextMenu, ({ element, index, anchor, browserEvent }) => (typeof element === 'undefined' ? { element, index, anchor, browserEvent } : { element: this._model.get(element), index, anchor, browserEvent }));
|
||||
}
|
||||
|
||||
get model(): IPagedModel<T> {
|
||||
@@ -194,8 +190,8 @@ export class PagedList<T> implements IDisposable {
|
||||
return this.list.getSelection();
|
||||
}
|
||||
|
||||
layout(height?: number): void {
|
||||
this.list.layout(height);
|
||||
layout(height?: number, width?: number): void {
|
||||
this.list.layout(height, width);
|
||||
}
|
||||
|
||||
reveal(index: number, relativeTop?: number): void {
|
||||
|
||||
@@ -4,34 +4,24 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getOrDefault } from 'vs/base/common/objects';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Gesture, EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Event, mapEvent, filterEvent, Emitter, latch } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollEvent, ScrollbarVisibility, INewScrollDimensions } from 'vs/base/common/scrollable';
|
||||
import { RangeMap, shift } from './rangeMap';
|
||||
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent } from './list';
|
||||
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent, IListDragEvent, IListDragAndDrop, ListDragOverEffect } from './list';
|
||||
import { RowCache, IRow } from './rowCache';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { DragMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { Range, IRange } from 'vs/base/common/range';
|
||||
|
||||
function canUseTranslate3d(): boolean {
|
||||
if (browser.isFirefox) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (browser.getZoomLevel() !== 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
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';
|
||||
|
||||
interface IItem<T> {
|
||||
readonly id: string;
|
||||
@@ -39,25 +29,117 @@ interface IItem<T> {
|
||||
readonly templateId: string;
|
||||
row: IRow | null;
|
||||
size: number;
|
||||
width: number | undefined;
|
||||
hasDynamicHeight: boolean;
|
||||
renderWidth: number | undefined;
|
||||
lastDynamicHeightWidth: number | undefined;
|
||||
uri: string | undefined;
|
||||
dropTarget: boolean;
|
||||
dragStartDisposable: IDisposable;
|
||||
}
|
||||
|
||||
export interface IListViewOptions {
|
||||
export interface IListViewDragAndDrop<T> extends IListDragAndDrop<T> {
|
||||
getDragElements(element: T): T[];
|
||||
}
|
||||
|
||||
export interface IListViewOptions<T> {
|
||||
readonly dnd?: IListViewDragAndDrop<T>;
|
||||
readonly useShadows?: boolean;
|
||||
readonly verticalScrollMode?: ScrollbarVisibility;
|
||||
readonly setRowLineHeight?: boolean;
|
||||
readonly supportDynamicHeights?: boolean;
|
||||
readonly mouseSupport?: boolean;
|
||||
readonly horizontalScrolling?: boolean;
|
||||
}
|
||||
|
||||
const DefaultOptions = {
|
||||
useShadows: true,
|
||||
verticalScrollMode: ScrollbarVisibility.Auto,
|
||||
setRowLineHeight: true,
|
||||
supportDynamicHeights: false
|
||||
supportDynamicHeights: false,
|
||||
dnd: {
|
||||
getDragElements(e) { return [e]; },
|
||||
getDragURI() { return null; },
|
||||
onDragStart(): void { },
|
||||
onDragOver() { return false; },
|
||||
drop() { }
|
||||
},
|
||||
horizontalScrolling: false
|
||||
};
|
||||
|
||||
export class ElementsDragAndDropData<T> implements IDragAndDropData {
|
||||
|
||||
readonly elements: T[];
|
||||
|
||||
constructor(elements: T[]) {
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
update(): void { }
|
||||
|
||||
getData(): any {
|
||||
return this.elements;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExternalElementsDragAndDropData<T> implements IDragAndDropData {
|
||||
|
||||
readonly elements: T[];
|
||||
|
||||
constructor(elements: T[]) {
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
update(): void { }
|
||||
|
||||
getData(): any {
|
||||
return this.elements;
|
||||
}
|
||||
}
|
||||
|
||||
export class DesktopDragAndDropData implements IDragAndDropData {
|
||||
|
||||
readonly types: any[];
|
||||
readonly files: any[];
|
||||
|
||||
constructor() {
|
||||
this.types = [];
|
||||
this.files = [];
|
||||
}
|
||||
|
||||
update(dataTransfer: DataTransfer): void {
|
||||
if (dataTransfer.types) {
|
||||
this.types.splice(0, this.types.length, ...dataTransfer.types);
|
||||
}
|
||||
|
||||
if (dataTransfer.files) {
|
||||
this.files.splice(0, this.files.length);
|
||||
|
||||
for (let i = 0; i < dataTransfer.files.length; i++) {
|
||||
const file = dataTransfer.files.item(i);
|
||||
|
||||
if (file && (file.size || file.type)) {
|
||||
this.files.push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getData(): any {
|
||||
return {
|
||||
types: this.types,
|
||||
files: this.files
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function equalsDragFeedback(f1: number[] | undefined, f2: number[] | undefined): boolean {
|
||||
if (Array.isArray(f1) && Array.isArray(f2)) {
|
||||
return equals(f1, f2!);
|
||||
}
|
||||
|
||||
return f1 === f2;
|
||||
}
|
||||
|
||||
export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
readonly domNode: HTMLElement;
|
||||
@@ -75,24 +157,50 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
private scrollableElement: ScrollableElement;
|
||||
private _scrollHeight: number;
|
||||
private scrollableElementUpdateDisposable: IDisposable | null = null;
|
||||
private scrollableElementWidthDelayer = new Delayer<void>(50);
|
||||
private splicing = false;
|
||||
private dragAndDropScrollInterval: number;
|
||||
private dragAndDropScrollTimeout: number;
|
||||
private dragAndDropMouseY: number;
|
||||
private dragOverAnimationDisposable: IDisposable | undefined;
|
||||
private dragOverAnimationStopDisposable: IDisposable = Disposable.None;
|
||||
private dragOverMouseY: number;
|
||||
private setRowLineHeight: boolean;
|
||||
private supportDynamicHeights: boolean;
|
||||
private horizontalScrolling: boolean;
|
||||
private scrollWidth: number | undefined;
|
||||
private canUseTranslate3d: boolean | undefined = undefined;
|
||||
|
||||
private dnd: IListViewDragAndDrop<T>;
|
||||
private canDrop: boolean = false;
|
||||
private currentDragData: IDragAndDropData | undefined;
|
||||
private currentDragFeedback: number[] | undefined;
|
||||
private currentDragFeedbackDisposable: IDisposable = Disposable.None;
|
||||
private onDragLeaveTimeout: IDisposable = Disposable.None;
|
||||
|
||||
private disposables: IDisposable[];
|
||||
|
||||
private _onDidChangeContentHeight = new Emitter<number>();
|
||||
readonly onDidChangeContentHeight: Event<number> = latch(this._onDidChangeContentHeight.event);
|
||||
readonly onDidChangeContentHeight: Event<number> = Event.latch(this._onDidChangeContentHeight.event);
|
||||
get contentHeight(): number { return this.rangeMap.size; }
|
||||
|
||||
readonly onDidScroll: Event<void>;
|
||||
|
||||
// private _onDragStart = new Emitter<{ element: T, uri: string, event: DragEvent }>();
|
||||
// readonly onDragStart = this._onDragStart.event;
|
||||
|
||||
// readonly onDragOver: Event<IListDragEvent<T>>;
|
||||
// readonly onDragLeave: Event<void>;
|
||||
// readonly onDrop: Event<IListDragEvent<T>>;
|
||||
// readonly onDragEnd: Event<void>;
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
private virtualDelegate: IListVirtualDelegate<T>,
|
||||
renderers: IListRenderer<any /* TODO@joao */, any>[],
|
||||
options: IListViewOptions = DefaultOptions
|
||||
options: IListViewOptions<T> = DefaultOptions
|
||||
) {
|
||||
if (options.horizontalScrolling && options.supportDynamicHeights) {
|
||||
throw new Error('Horizontal scrolling and dynamic heights not supported simultaneously');
|
||||
}
|
||||
|
||||
this.items = [];
|
||||
this.itemId = 0;
|
||||
this.rangeMap = new RangeMap();
|
||||
@@ -110,13 +218,16 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
this.domNode.className = 'monaco-list';
|
||||
DOM.toggleClass(this.domNode, 'mouse-support', typeof options.mouseSupport === 'boolean' ? options.mouseSupport : true);
|
||||
|
||||
this.horizontalScrolling = getOrDefault(options, o => o.horizontalScrolling, DefaultOptions.horizontalScrolling);
|
||||
DOM.toggleClass(this.domNode, 'horizontal-scrolling', this.horizontalScrolling);
|
||||
|
||||
this.rowsContainer = document.createElement('div');
|
||||
this.rowsContainer.className = 'monaco-list-rows';
|
||||
Gesture.addTarget(this.rowsContainer);
|
||||
|
||||
this.scrollableElement = new ScrollableElement(this.rowsContainer, {
|
||||
alwaysConsumeMouseWheel: true,
|
||||
horizontal: ScrollbarVisibility.Hidden,
|
||||
horizontal: this.horizontalScrolling ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden,
|
||||
vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
|
||||
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows)
|
||||
});
|
||||
@@ -126,6 +237,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
this.disposables = [this.rangeMap, this.gesture, this.scrollableElement, this.cache];
|
||||
|
||||
this.onDidScroll = Event.signal(this.scrollableElement.onScroll);
|
||||
this.scrollableElement.onScroll(this.onScroll, this, this.disposables);
|
||||
domEvent(this.rowsContainer, TouchEventType.Change)(this.onTouchChange, this, this.disposables);
|
||||
|
||||
@@ -134,11 +246,14 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
domEvent(this.scrollableElement.getDomNode(), 'scroll')
|
||||
(e => (e.target as HTMLElement).scrollTop = 0, null, this.disposables);
|
||||
|
||||
const onDragOver = mapEvent(domEvent(this.rowsContainer, 'dragover'), e => new DragMouseEvent(e));
|
||||
onDragOver(this.onDragOver, this, this.disposables);
|
||||
Event.map(domEvent(this.domNode, 'dragover'), e => this.toDragEvent(e))(this.onDragOver, this, this.disposables);
|
||||
Event.map(domEvent(this.domNode, 'drop'), e => this.toDragEvent(e))(this.onDrop, this, this.disposables);
|
||||
domEvent(this.domNode, 'dragleave')(this.onDragLeave, this, this.disposables);
|
||||
domEvent(window, 'dragend')(this.onDragEnd, this, this.disposables);
|
||||
|
||||
this.setRowLineHeight = getOrDefault(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight);
|
||||
this.supportDynamicHeights = getOrDefault(options, o => o.supportDynamicHeights, DefaultOptions.supportDynamicHeights);
|
||||
this.dnd = getOrDefault<IListViewOptions<T>, IListViewDragAndDrop<T>>(options, o => o.dnd, DefaultOptions.dnd);
|
||||
|
||||
this.layout();
|
||||
}
|
||||
@@ -176,9 +291,13 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
element,
|
||||
templateId: this.virtualDelegate.getTemplateId(element),
|
||||
size: this.virtualDelegate.getHeight(element),
|
||||
width: undefined,
|
||||
hasDynamicHeight: !!this.virtualDelegate.hasDynamicHeight && this.virtualDelegate.hasDynamicHeight(element),
|
||||
renderWidth: undefined,
|
||||
row: null
|
||||
lastDynamicHeightWidth: undefined,
|
||||
row: null,
|
||||
uri: undefined,
|
||||
dropTarget: false,
|
||||
dragStartDisposable: Disposable.None
|
||||
}));
|
||||
|
||||
let deleted: IItem<T>[];
|
||||
@@ -222,7 +341,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
this.updateScrollHeight();
|
||||
this.eventuallyUpdateScrollDimensions();
|
||||
|
||||
if (this.supportDynamicHeights) {
|
||||
this.rerender(this.scrollTop, this.renderHeight);
|
||||
@@ -231,18 +350,62 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
return deleted.map(i => i.element);
|
||||
}
|
||||
|
||||
private updateScrollHeight(): void {
|
||||
private eventuallyUpdateScrollDimensions(): void {
|
||||
this._scrollHeight = this.contentHeight;
|
||||
this.rowsContainer.style.height = `${this._scrollHeight}px`;
|
||||
|
||||
if (!this.scrollableElementUpdateDisposable) {
|
||||
this.scrollableElementUpdateDisposable = DOM.scheduleAtNextAnimationFrame(() => {
|
||||
this.scrollableElement.setScrollDimensions({ scrollHeight: this._scrollHeight });
|
||||
this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight });
|
||||
this.updateScrollWidth();
|
||||
this.scrollableElementUpdateDisposable = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private eventuallyUpdateScrollWidth(): void {
|
||||
if (!this.horizontalScrolling) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.scrollableElementWidthDelayer.trigger(() => this.updateScrollWidth());
|
||||
}
|
||||
|
||||
private updateScrollWidth(): void {
|
||||
if (!this.horizontalScrolling) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.items.length === 0) {
|
||||
this.scrollableElement.setScrollDimensions({ scrollWidth: 0 });
|
||||
}
|
||||
|
||||
let scrollWidth = 0;
|
||||
|
||||
for (const item of this.items) {
|
||||
if (typeof item.width !== 'undefined') {
|
||||
scrollWidth = Math.max(scrollWidth, item.width);
|
||||
}
|
||||
}
|
||||
|
||||
this.scrollWidth = scrollWidth;
|
||||
this.scrollableElement.setScrollDimensions({ scrollWidth: scrollWidth + 10 });
|
||||
}
|
||||
|
||||
updateWidth(index: number): void {
|
||||
if (!this.horizontalScrolling || typeof this.scrollWidth === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const item = this.items[index];
|
||||
this.measureItemWidth(item);
|
||||
|
||||
if (typeof item.width !== 'undefined' && item.width > this.scrollWidth) {
|
||||
this.scrollWidth = item.width;
|
||||
this.scrollableElement.setScrollDimensions({ scrollWidth: this.scrollWidth + 10 });
|
||||
}
|
||||
}
|
||||
|
||||
get length(): number {
|
||||
return this.items.length;
|
||||
}
|
||||
@@ -277,31 +440,37 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
return this.rangeMap.indexAfter(position);
|
||||
}
|
||||
|
||||
layout(height?: number): void {
|
||||
layout(height?: number, width?: number): void {
|
||||
let scrollDimensions: INewScrollDimensions = {
|
||||
height: height || DOM.getContentHeight(this.domNode)
|
||||
height: typeof height === 'number' ? height : DOM.getContentHeight(this.domNode)
|
||||
};
|
||||
|
||||
if (this.scrollableElementUpdateDisposable) {
|
||||
this.scrollableElementUpdateDisposable.dispose();
|
||||
this.scrollableElementUpdateDisposable = null;
|
||||
scrollDimensions.scrollHeight = this._scrollHeight;
|
||||
scrollDimensions.scrollHeight = this.scrollHeight;
|
||||
}
|
||||
|
||||
this.scrollableElement.setScrollDimensions(scrollDimensions);
|
||||
}
|
||||
|
||||
layoutWidth(width: number): void {
|
||||
this.renderWidth = width;
|
||||
if (typeof width !== 'undefined') {
|
||||
this.renderWidth = width;
|
||||
|
||||
if (this.supportDynamicHeights) {
|
||||
this.rerender(this.scrollTop, this.renderHeight);
|
||||
if (this.supportDynamicHeights) {
|
||||
this.rerender(this.scrollTop, this.renderHeight);
|
||||
}
|
||||
|
||||
if (this.horizontalScrolling) {
|
||||
this.scrollableElement.setScrollDimensions({
|
||||
width: typeof width === 'number' ? width : DOM.getContentWidth(this.domNode)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render
|
||||
|
||||
private render(renderTop: number, renderHeight: number): void {
|
||||
private render(renderTop: number, renderHeight: number, renderLeft: number, scrollWidth: number): void {
|
||||
const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
|
||||
const renderRange = this.getRenderRange(renderTop, renderHeight);
|
||||
|
||||
@@ -321,14 +490,33 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
if (canUseTranslate3d() && !isWindows /* Windows: translate3d breaks subpixel-antialias (ClearType) unless a background is defined */) {
|
||||
const transform = `translate3d(0px, -${renderTop}px, 0px)`;
|
||||
const canUseTranslate3d = !isWindows && !browser.isFirefox && browser.getZoomLevel() === 0;
|
||||
|
||||
if (canUseTranslate3d) {
|
||||
const transform = `translate3d(-${renderLeft}px, -${renderTop}px, 0px)`;
|
||||
this.rowsContainer.style.transform = transform;
|
||||
this.rowsContainer.style.webkitTransform = transform;
|
||||
|
||||
if (canUseTranslate3d !== this.canUseTranslate3d) {
|
||||
this.rowsContainer.style.left = '0';
|
||||
this.rowsContainer.style.top = '0';
|
||||
}
|
||||
} else {
|
||||
this.rowsContainer.style.left = `-${renderLeft}px`;
|
||||
this.rowsContainer.style.top = `-${renderTop}px`;
|
||||
|
||||
if (canUseTranslate3d !== this.canUseTranslate3d) {
|
||||
this.rowsContainer.style.transform = '';
|
||||
this.rowsContainer.style.webkitTransform = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (this.horizontalScrolling) {
|
||||
this.rowsContainer.style.width = `${Math.max(scrollWidth, this.renderWidth)}px`;
|
||||
}
|
||||
|
||||
this.canUseTranslate3d = canUseTranslate3d;
|
||||
|
||||
this.lastRenderTop = renderTop;
|
||||
this.lastRenderHeight = renderHeight;
|
||||
}
|
||||
@@ -353,7 +541,48 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
this.updateItemInDOM(item, index);
|
||||
|
||||
const renderer = this.renderers.get(item.templateId);
|
||||
renderer.renderElement(item.element, index, item.row.templateData);
|
||||
|
||||
if (!renderer) {
|
||||
throw new Error(`No renderer found for template id ${item.templateId}`);
|
||||
}
|
||||
|
||||
if (renderer) {
|
||||
renderer.renderElement(item.element, index, item.row.templateData);
|
||||
}
|
||||
|
||||
const uri = this.dnd.getDragURI(item.element);
|
||||
item.dragStartDisposable.dispose();
|
||||
item.row.domNode!.draggable = !!uri;
|
||||
|
||||
if (uri) {
|
||||
const onDragStart = domEvent(item.row.domNode!, 'dragstart');
|
||||
item.dragStartDisposable = onDragStart(event => this.onDragStart(item.element, uri, event));
|
||||
}
|
||||
|
||||
if (this.horizontalScrolling) {
|
||||
this.measureItemWidth(item);
|
||||
this.eventuallyUpdateScrollWidth();
|
||||
}
|
||||
}
|
||||
|
||||
private measureItemWidth(item: IItem<T>): void {
|
||||
if (!item.row || !item.row.domNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
item.row.domNode.style.width = 'fit-content';
|
||||
item.width = DOM.getContentWidth(item.row.domNode);
|
||||
const style = window.getComputedStyle(item.row.domNode);
|
||||
|
||||
if (style.paddingLeft) {
|
||||
item.width += parseFloat(style.paddingLeft);
|
||||
}
|
||||
|
||||
if (style.paddingRight) {
|
||||
item.width += parseFloat(style.paddingRight);
|
||||
}
|
||||
|
||||
item.row.domNode.style.width = '';
|
||||
}
|
||||
|
||||
private updateItemInDOM(item: IItem<T>, index: number): void {
|
||||
@@ -368,18 +597,24 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
item.row!.domNode!.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false');
|
||||
item.row!.domNode!.setAttribute('aria-setsize', `${this.length}`);
|
||||
item.row!.domNode!.setAttribute('aria-posinset', `${index + 1}`);
|
||||
DOM.toggleClass(item.row!.domNode!, 'drop-target', item.dropTarget);
|
||||
}
|
||||
|
||||
private removeItemFromDOM(index: number): void {
|
||||
const item = this.items[index];
|
||||
const renderer = this.renderers.get(item.templateId);
|
||||
item.dragStartDisposable.dispose();
|
||||
|
||||
if (renderer.disposeElement) {
|
||||
const renderer = this.renderers.get(item.templateId);
|
||||
if (renderer && renderer.disposeElement) {
|
||||
renderer.disposeElement(item.element, index, item.row!.templateData);
|
||||
}
|
||||
|
||||
this.cache.release(item.row!);
|
||||
item.row = null;
|
||||
|
||||
if (this.horizontalScrolling) {
|
||||
this.eventuallyUpdateScrollWidth();
|
||||
}
|
||||
}
|
||||
|
||||
getScrollTop(): number {
|
||||
@@ -391,7 +626,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
if (this.scrollableElementUpdateDisposable) {
|
||||
this.scrollableElementUpdateDisposable.dispose();
|
||||
this.scrollableElementUpdateDisposable = null;
|
||||
this.scrollableElement.setScrollDimensions({ scrollHeight: this._scrollHeight });
|
||||
this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight });
|
||||
}
|
||||
|
||||
this.scrollableElement.setScrollPosition({ scrollTop });
|
||||
@@ -406,22 +641,22 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
|
||||
get scrollHeight(): number {
|
||||
return this._scrollHeight;
|
||||
return this._scrollHeight + (this.horizontalScrolling ? 10 : 0);
|
||||
}
|
||||
|
||||
// Events
|
||||
|
||||
@memoize get onMouseClick(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'click'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onMouseDblClick(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'dblclick'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onMouseMiddleClick(): Event<IListMouseEvent<T>> { return filterEvent(mapEvent(domEvent(this.domNode, 'auxclick'), e => this.toMouseEvent(e as MouseEvent)), e => e.browserEvent.button === 1); }
|
||||
@memoize get onMouseUp(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'mouseup'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onMouseDown(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'mousedown'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onMouseOver(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'mouseover'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onMouseMove(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'mousemove'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onMouseOut(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'mouseout'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onContextMenu(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'contextmenu'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onTouchStart(): Event<IListTouchEvent<T>> { return mapEvent(domEvent(this.domNode, 'touchstart'), e => this.toTouchEvent(e)); }
|
||||
@memoize get onTap(): Event<IListGestureEvent<T>> { return mapEvent(domEvent(this.rowsContainer, TouchEventType.Tap), e => this.toGestureEvent(e)); }
|
||||
@memoize get onMouseClick(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'click'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onMouseDblClick(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'dblclick'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onMouseMiddleClick(): Event<IListMouseEvent<T>> { return Event.filter(Event.map(domEvent(this.domNode, 'auxclick'), e => this.toMouseEvent(e as MouseEvent)), e => e.browserEvent.button === 1); }
|
||||
@memoize get onMouseUp(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mouseup'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onMouseDown(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mousedown'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onMouseOver(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mouseover'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onMouseMove(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mousemove'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onMouseOut(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mouseout'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onContextMenu(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'contextmenu'), e => this.toMouseEvent(e)); }
|
||||
@memoize get onTouchStart(): Event<IListTouchEvent<T>> { return Event.map(domEvent(this.domNode, 'touchstart'), e => this.toTouchEvent(e)); }
|
||||
@memoize get onTap(): Event<IListGestureEvent<T>> { return Event.map(domEvent(this.rowsContainer, TouchEventType.Tap), e => this.toGestureEvent(e)); }
|
||||
|
||||
private toMouseEvent(browserEvent: MouseEvent): IListMouseEvent<T> {
|
||||
const index = this.getItemIndexFromEventTarget(browserEvent.target || null);
|
||||
@@ -444,9 +679,16 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
return { browserEvent, index, element };
|
||||
}
|
||||
|
||||
private toDragEvent(browserEvent: DragEvent): IListDragEvent<T> {
|
||||
const index = this.getItemIndexFromEventTarget(browserEvent.target || null);
|
||||
const item = typeof index === 'undefined' ? undefined : this.items[index];
|
||||
const element = item && item.element;
|
||||
return { browserEvent, index, element };
|
||||
}
|
||||
|
||||
private onScroll(e: ScrollEvent): void {
|
||||
try {
|
||||
this.render(e.scrollTop, e.height);
|
||||
this.render(e.scrollTop, e.height, e.scrollLeft, e.scrollWidth);
|
||||
|
||||
if (this.supportDynamicHeights) {
|
||||
this.rerender(e.scrollTop, e.height);
|
||||
@@ -464,55 +706,218 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
this.scrollTop -= event.translationY;
|
||||
}
|
||||
|
||||
private onDragOver(event: DragMouseEvent): void {
|
||||
this.setupDragAndDropScrollInterval();
|
||||
this.dragAndDropMouseY = event.posy;
|
||||
}
|
||||
// DND
|
||||
|
||||
private setupDragAndDropScrollInterval(): void {
|
||||
const viewTop = DOM.getTopLeftOffset(this.domNode).top;
|
||||
private onDragStart(element: T, uri: string, event: DragEvent): void {
|
||||
if (!event.dataTransfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.dragAndDropScrollInterval) {
|
||||
this.dragAndDropScrollInterval = window.setInterval(() => {
|
||||
if (this.dragAndDropMouseY === undefined) {
|
||||
return;
|
||||
}
|
||||
const elements = this.dnd.getDragElements(element);
|
||||
|
||||
var diff = this.dragAndDropMouseY - viewTop;
|
||||
var scrollDiff = 0;
|
||||
var upperLimit = this.renderHeight - 35;
|
||||
event.dataTransfer.effectAllowed = 'copyMove';
|
||||
event.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify([uri]));
|
||||
|
||||
if (diff < 35) {
|
||||
scrollDiff = Math.max(-14, 0.2 * (diff - 35));
|
||||
} else if (diff > upperLimit) {
|
||||
scrollDiff = Math.min(14, 0.2 * (diff - upperLimit));
|
||||
}
|
||||
if (event.dataTransfer.setDragImage) {
|
||||
let label: string | undefined;
|
||||
|
||||
this.scrollTop += scrollDiff;
|
||||
}, 10);
|
||||
if (this.dnd.getDragLabel) {
|
||||
label = this.dnd.getDragLabel(elements);
|
||||
}
|
||||
|
||||
this.cancelDragAndDropScrollTimeout();
|
||||
if (typeof label === 'undefined') {
|
||||
label = String(elements.length);
|
||||
}
|
||||
|
||||
this.dragAndDropScrollTimeout = window.setTimeout(() => {
|
||||
this.cancelDragAndDropScrollInterval();
|
||||
this.dragAndDropScrollTimeout = -1;
|
||||
}, 1000);
|
||||
const dragImage = DOM.$('.monaco-list-drag-image');
|
||||
dragImage.textContent = label;
|
||||
document.body.appendChild(dragImage);
|
||||
event.dataTransfer.setDragImage(dragImage, -10, -10);
|
||||
setTimeout(() => document.body.removeChild(dragImage), 0);
|
||||
}
|
||||
|
||||
this.currentDragData = new ElementsDragAndDropData(elements);
|
||||
StaticDND.CurrentDragAndDropData = new ExternalElementsDragAndDropData(elements);
|
||||
|
||||
if (this.dnd.onDragStart) {
|
||||
this.dnd.onDragStart(this.currentDragData, event);
|
||||
}
|
||||
}
|
||||
|
||||
private cancelDragAndDropScrollInterval(): void {
|
||||
if (this.dragAndDropScrollInterval) {
|
||||
window.clearInterval(this.dragAndDropScrollInterval);
|
||||
this.dragAndDropScrollInterval = -1;
|
||||
private onDragOver(event: IListDragEvent<T>): boolean {
|
||||
this.onDragLeaveTimeout.dispose();
|
||||
|
||||
if (StaticDND.CurrentDragAndDropData && StaticDND.CurrentDragAndDropData.getData() === 'vscode-ui') {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.cancelDragAndDropScrollTimeout();
|
||||
this.setupDragAndDropScrollTopAnimation(event.browserEvent);
|
||||
|
||||
if (!event.browserEvent.dataTransfer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Drag over from outside
|
||||
if (!this.currentDragData) {
|
||||
if (StaticDND.CurrentDragAndDropData) {
|
||||
// Drag over from another list
|
||||
this.currentDragData = StaticDND.CurrentDragAndDropData;
|
||||
|
||||
} else {
|
||||
// Drag over from the desktop
|
||||
if (!event.browserEvent.dataTransfer.types) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.currentDragData = new DesktopDragAndDropData();
|
||||
}
|
||||
}
|
||||
|
||||
const result = this.dnd.onDragOver(this.currentDragData, event.element, event.index, event.browserEvent);
|
||||
this.canDrop = typeof result === 'boolean' ? result : result.accept;
|
||||
|
||||
if (!this.canDrop) {
|
||||
this.currentDragFeedback = undefined;
|
||||
this.currentDragFeedbackDisposable.dispose();
|
||||
return false;
|
||||
}
|
||||
|
||||
event.browserEvent.dataTransfer.dropEffect = (typeof result !== 'boolean' && result.effect === ListDragOverEffect.Copy) ? 'copy' : 'move';
|
||||
|
||||
let feedback: number[];
|
||||
|
||||
if (typeof result !== 'boolean' && result.feedback) {
|
||||
feedback = result.feedback;
|
||||
} else {
|
||||
if (typeof event.index === 'undefined') {
|
||||
feedback = [-1];
|
||||
} else {
|
||||
feedback = [event.index];
|
||||
}
|
||||
}
|
||||
|
||||
// sanitize feedback list
|
||||
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;
|
||||
}
|
||||
|
||||
this.currentDragFeedback = feedback;
|
||||
this.currentDragFeedbackDisposable.dispose();
|
||||
|
||||
if (feedback[0] === -1) { // entire list feedback
|
||||
DOM.addClass(this.domNode, 'drop-target');
|
||||
this.currentDragFeedbackDisposable = toDisposable(() => DOM.removeClass(this.domNode, 'drop-target'));
|
||||
} else {
|
||||
for (const index of feedback) {
|
||||
const item = this.items[index]!;
|
||||
item.dropTarget = true;
|
||||
|
||||
if (item.row && item.row.domNode) {
|
||||
DOM.addClass(item.row.domNode, 'drop-target');
|
||||
}
|
||||
}
|
||||
|
||||
this.currentDragFeedbackDisposable = toDisposable(() => {
|
||||
for (const index of feedback) {
|
||||
const item = this.items[index]!;
|
||||
item.dropTarget = false;
|
||||
|
||||
if (item.row && item.row.domNode) {
|
||||
DOM.removeClass(item.row.domNode, 'drop-target');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private cancelDragAndDropScrollTimeout(): void {
|
||||
if (this.dragAndDropScrollTimeout) {
|
||||
window.clearTimeout(this.dragAndDropScrollTimeout);
|
||||
this.dragAndDropScrollTimeout = -1;
|
||||
private onDragLeave(): void {
|
||||
this.onDragLeaveTimeout.dispose();
|
||||
this.onDragLeaveTimeout = disposableTimeout(() => this.clearDragOverFeedback(), 100);
|
||||
}
|
||||
|
||||
private onDrop(event: IListDragEvent<T>): void {
|
||||
if (!this.canDrop) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dragData = this.currentDragData;
|
||||
this.teardownDragAndDropScrollTopAnimation();
|
||||
this.clearDragOverFeedback();
|
||||
this.currentDragData = undefined;
|
||||
StaticDND.CurrentDragAndDropData = undefined;
|
||||
|
||||
if (!dragData || !event.browserEvent.dataTransfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.browserEvent.preventDefault();
|
||||
dragData.update(event.browserEvent.dataTransfer);
|
||||
this.dnd.drop(dragData, event.element, event.index, event.browserEvent);
|
||||
}
|
||||
|
||||
private onDragEnd(): void {
|
||||
this.canDrop = false;
|
||||
this.teardownDragAndDropScrollTopAnimation();
|
||||
this.clearDragOverFeedback();
|
||||
this.currentDragData = undefined;
|
||||
StaticDND.CurrentDragAndDropData = undefined;
|
||||
}
|
||||
|
||||
private clearDragOverFeedback(): void {
|
||||
this.currentDragFeedback = undefined;
|
||||
this.currentDragFeedbackDisposable.dispose();
|
||||
this.currentDragFeedbackDisposable = Disposable.None;
|
||||
}
|
||||
|
||||
// DND scroll top animation
|
||||
|
||||
private setupDragAndDropScrollTopAnimation(event: DragEvent): void {
|
||||
if (!this.dragOverAnimationDisposable) {
|
||||
const viewTop = DOM.getTopLeftOffset(this.domNode).top;
|
||||
this.dragOverAnimationDisposable = DOM.animate(this.animateDragAndDropScrollTop.bind(this, viewTop));
|
||||
}
|
||||
|
||||
this.dragOverAnimationStopDisposable.dispose();
|
||||
this.dragOverAnimationStopDisposable = disposableTimeout(() => {
|
||||
if (this.dragOverAnimationDisposable) {
|
||||
this.dragOverAnimationDisposable.dispose();
|
||||
this.dragOverAnimationDisposable = undefined;
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
this.dragOverMouseY = event.pageY;
|
||||
}
|
||||
|
||||
private animateDragAndDropScrollTop(viewTop: number): void {
|
||||
if (this.dragOverMouseY === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const diff = this.dragOverMouseY - viewTop;
|
||||
const upperLimit = this.renderHeight - 35;
|
||||
|
||||
if (diff < 35) {
|
||||
this.scrollTop += Math.max(-14, Math.floor(0.3 * (diff - 35)));
|
||||
} else if (diff > upperLimit) {
|
||||
this.scrollTop += Math.min(14, Math.floor(0.3 * (diff - upperLimit)));
|
||||
}
|
||||
}
|
||||
|
||||
private teardownDragAndDropScrollTopAnimation(): void {
|
||||
this.dragOverAnimationStopDisposable.dispose();
|
||||
|
||||
if (this.dragOverAnimationDisposable) {
|
||||
this.dragOverAnimationDisposable.dispose();
|
||||
this.dragOverAnimationDisposable = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,12 +959,15 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
// Let's remember the second element's position, this helps in scrolling up
|
||||
// and preserving a linear upwards scroll movement
|
||||
let secondElementIndex: number | undefined;
|
||||
let secondElementTopDelta: number | undefined;
|
||||
let anchorElementIndex: number | undefined;
|
||||
let anchorElementTopDelta: number | undefined;
|
||||
|
||||
if (previousRenderRange.end - previousRenderRange.start > 1) {
|
||||
secondElementIndex = previousRenderRange.start + 1;
|
||||
secondElementTopDelta = this.elementTop(secondElementIndex) - renderTop;
|
||||
if (renderTop === this.elementTop(previousRenderRange.start)) {
|
||||
anchorElementIndex = previousRenderRange.start;
|
||||
anchorElementTopDelta = 0;
|
||||
} else if (previousRenderRange.end - previousRenderRange.start > 1) {
|
||||
anchorElementIndex = previousRenderRange.start + 1;
|
||||
anchorElementTopDelta = this.elementTop(anchorElementIndex) - renderTop;
|
||||
}
|
||||
|
||||
let heightDiff = 0;
|
||||
@@ -582,7 +990,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
if (!didChange) {
|
||||
if (heightDiff !== 0) {
|
||||
this.updateScrollHeight();
|
||||
this.eventuallyUpdateScrollDimensions();
|
||||
}
|
||||
|
||||
const unrenderRanges = Range.relativeComplement(previousRenderRange, renderRange);
|
||||
@@ -595,14 +1003,25 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
const renderRanges = Range.relativeComplement(renderRange, previousRenderRange);
|
||||
|
||||
for (const range of renderRanges) {
|
||||
for (let i = range.start; i < range.end; i++) {
|
||||
const afterIndex = i + 1;
|
||||
const beforeRow = afterIndex < this.items.length ? this.items[afterIndex].row : null;
|
||||
const beforeElement = beforeRow ? beforeRow.domNode : null;
|
||||
this.insertItemInDOM(i, beforeElement);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = renderRange.start; i < renderRange.end; i++) {
|
||||
if (this.items[i].row) {
|
||||
this.updateItemInDOM(this.items[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof secondElementIndex === 'number') {
|
||||
this.scrollTop = this.elementTop(secondElementIndex) - secondElementTopDelta!;
|
||||
if (typeof anchorElementIndex === 'number') {
|
||||
this.scrollTop = this.elementTop(anchorElementIndex) - anchorElementTopDelta!;
|
||||
}
|
||||
|
||||
this._onDidChangeContentHeight.fire(this.contentHeight);
|
||||
@@ -614,7 +1033,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
private probeDynamicHeight(index: number): number {
|
||||
const item = this.items[index];
|
||||
|
||||
if (!item.hasDynamicHeight || item.renderWidth === this.renderWidth) {
|
||||
if (!item.hasDynamicHeight || item.lastDynamicHeightWidth === this.renderWidth) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -624,9 +1043,11 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
row.domNode!.style.height = '';
|
||||
this.rowsContainer.appendChild(row.domNode!);
|
||||
renderer.renderElement(item.element, index, row.templateData);
|
||||
if (renderer) {
|
||||
renderer.renderElement(item.element, index, row.templateData);
|
||||
}
|
||||
item.size = row.domNode!.offsetHeight;
|
||||
item.renderWidth = this.renderWidth;
|
||||
item.lastDynamicHeightWidth = this.renderWidth;
|
||||
this.rowsContainer.removeChild(row.domNode!);
|
||||
this.cache.release(row);
|
||||
|
||||
@@ -660,7 +1081,9 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
for (const item of this.items) {
|
||||
if (item.row) {
|
||||
const renderer = this.renderers.get(item.row.templateId);
|
||||
renderer.disposeTemplate(item.row.templateData);
|
||||
if (renderer) {
|
||||
renderer.disposeTemplate(item.row.templateData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,17 +13,19 @@ import * as DOM from 'vs/base/browser/dom';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { Gesture } from 'vs/base/browser/touch';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { Event, Emitter, EventBufferer, chain, mapEvent, anyEvent } from 'vs/base/common/event';
|
||||
import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { Event, Emitter, EventBufferer } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider } from './list';
|
||||
import { ListView, IListViewOptions } from './listView';
|
||||
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListAriaRootRole } from './list';
|
||||
import { ListView, IListViewOptions, IListViewDragAndDrop } from './listView';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice';
|
||||
import { clamp } from 'vs/base/common/numbers';
|
||||
import { matchesPrefix } from 'vs/base/common/filters';
|
||||
import { IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
|
||||
interface ITraitChangeEvent {
|
||||
indexes: number[];
|
||||
@@ -66,15 +68,10 @@ class TraitRenderer<T> implements IListRenderer<T, ITraitTemplateData>
|
||||
this.trait.renderIndex(index, templateData);
|
||||
}
|
||||
|
||||
disposeElement(): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
splice(start: number, deleteCount: number, insertCount: number): void {
|
||||
const rendered: IRenderedContainer[] = [];
|
||||
|
||||
for (let i = 0; i < this.renderedElements.length; i++) {
|
||||
const renderedElement = this.renderedElements[i];
|
||||
for (const renderedElement of this.renderedElements) {
|
||||
|
||||
if (renderedElement.index < start) {
|
||||
rendered.push(renderedElement);
|
||||
@@ -216,7 +213,7 @@ class TraitSpliceable<T> implements ISpliceable<T> {
|
||||
|
||||
splice(start: number, deleteCount: number, elements: T[]): void {
|
||||
if (!this.identityProvider) {
|
||||
return this.trait.splice(start, deleteCount, elements.map(e => false));
|
||||
return this.trait.splice(start, deleteCount, elements.map(() => false));
|
||||
}
|
||||
|
||||
const pastElementsWithTrait = this.trait.get().map(i => this.identityProvider!.getId(this.view.element(i)).toString());
|
||||
@@ -245,7 +242,7 @@ class KeyboardController<T> implements IDisposable {
|
||||
|
||||
this.openController = options.openController || DefaultOpenController;
|
||||
|
||||
const onKeyDown = chain(domEvent(view.domNode, 'keydown'))
|
||||
const onKeyDown = Event.chain(domEvent(view.domNode, 'keydown'))
|
||||
.filter(e => !isInputElement(e.target as HTMLElement))
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
|
||||
@@ -322,6 +319,106 @@ class KeyboardController<T> implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
enum TypeLabelControllerState {
|
||||
Idle,
|
||||
Typing
|
||||
}
|
||||
|
||||
export function mightProducePrintableCharacter(event: IKeyboardEvent): boolean {
|
||||
if (event.ctrlKey || event.metaKey || event.altKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (event.keyCode >= KeyCode.KEY_A && event.keyCode <= KeyCode.KEY_Z)
|
||||
|| (event.keyCode >= KeyCode.KEY_0 && event.keyCode <= KeyCode.KEY_9)
|
||||
|| (event.keyCode >= KeyCode.US_SEMICOLON && event.keyCode <= KeyCode.US_QUOTE);
|
||||
}
|
||||
|
||||
class TypeLabelController<T> implements IDisposable {
|
||||
|
||||
private enabled = false;
|
||||
private state: TypeLabelControllerState = TypeLabelControllerState.Idle;
|
||||
private enabledDisposables: IDisposable[] = [];
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private list: List<T>,
|
||||
private view: ListView<T>,
|
||||
private keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider<T>
|
||||
) {
|
||||
list.onDidUpdateOptions(this.onDidUpdateListOptions, this, this.disposables);
|
||||
this.onDidUpdateListOptions(list.options);
|
||||
}
|
||||
|
||||
private onDidUpdateListOptions(options: IListOptions<T>): void {
|
||||
const enableKeyboardNavigation = typeof options.enableKeyboardNavigation === 'undefined' ? true : !!options.enableKeyboardNavigation;
|
||||
|
||||
if (enableKeyboardNavigation) {
|
||||
this.enable();
|
||||
} else {
|
||||
this.disable();
|
||||
}
|
||||
}
|
||||
|
||||
private enable(): void {
|
||||
if (this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const onChar = Event.chain(domEvent(this.view.domNode, 'keydown'))
|
||||
.filter(e => !isInputElement(e.target as HTMLElement))
|
||||
.map(event => new StandardKeyboardEvent(event))
|
||||
.filter(this.keyboardNavigationLabelProvider.mightProducePrintableCharacter ? e => this.keyboardNavigationLabelProvider.mightProducePrintableCharacter!(e) : e => mightProducePrintableCharacter(e))
|
||||
.map(event => event.browserEvent.key)
|
||||
.event;
|
||||
|
||||
const onClear = Event.debounce<string, null>(onChar, () => null, 800);
|
||||
const onInput = Event.reduce<string | null, string | null>(Event.any(onChar, onClear), (r, i) => i === null ? null : ((r || '') + i));
|
||||
|
||||
onInput(this.onInput, this, this.enabledDisposables);
|
||||
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
private disable(): void {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.enabledDisposables = dispose(this.enabledDisposables);
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
private onInput(word: string | null): void {
|
||||
if (!word) {
|
||||
this.state = TypeLabelControllerState.Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
const focus = this.list.getFocus();
|
||||
const start = focus.length > 0 ? focus[0] : 0;
|
||||
const delta = this.state === TypeLabelControllerState.Idle ? 1 : 0;
|
||||
this.state = TypeLabelControllerState.Typing;
|
||||
|
||||
for (let i = 0; i < this.list.length; i++) {
|
||||
const index = (start + i + delta) % this.list.length;
|
||||
const label = this.keyboardNavigationLabelProvider.getKeyboardNavigationLabel(this.view.element(index));
|
||||
const labelStr = label && label.toString();
|
||||
|
||||
if (typeof labelStr === 'undefined' || matchesPrefix(word, labelStr)) {
|
||||
this.list.setFocus([index]);
|
||||
this.list.reveal(index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disable();
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
class DOMFocusController<T> implements IDisposable {
|
||||
|
||||
private disposables: IDisposable[] = [];
|
||||
@@ -332,7 +429,7 @@ class DOMFocusController<T> implements IDisposable {
|
||||
) {
|
||||
this.disposables = [];
|
||||
|
||||
const onKeyDown = chain(domEvent(view.domNode, 'keydown'))
|
||||
const onKeyDown = Event.chain(domEvent(view.domNode, 'keydown'))
|
||||
.filter(e => !isInputElement(e.target as HTMLElement))
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
|
||||
@@ -408,14 +505,14 @@ const DefaultOpenController: IOpenController = {
|
||||
class MouseController<T> implements IDisposable {
|
||||
|
||||
private multipleSelectionSupport: boolean;
|
||||
private multipleSelectionController: IMultipleSelectionController<T>;
|
||||
readonly multipleSelectionController: IMultipleSelectionController<T>;
|
||||
private openController: IOpenController;
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private list: List<T>,
|
||||
private view: ListView<T>,
|
||||
private options: IListOptions<T> = {}
|
||||
options: IListOptions<T> = {}
|
||||
) {
|
||||
this.multipleSelectionSupport = !(options.multipleSelectionSupport === false);
|
||||
|
||||
@@ -426,7 +523,9 @@ class MouseController<T> implements IDisposable {
|
||||
this.openController = options.openController || DefaultOpenController;
|
||||
|
||||
view.onMouseDown(this.onMouseDown, this, this.disposables);
|
||||
view.onContextMenu(this.onContextMenu, this, this.disposables);
|
||||
view.onMouseClick(this.onPointer, this, this.disposables);
|
||||
list.onMouseMiddleClick(this.onPointer, this, this.disposables);
|
||||
view.onMouseDblClick(this.onDoubleClick, this, this.disposables);
|
||||
view.onTouchStart(this.onMouseDown, this, this.disposables);
|
||||
view.onTap(this.onPointer, this, this.disposables);
|
||||
@@ -454,12 +553,20 @@ class MouseController<T> implements IDisposable {
|
||||
}
|
||||
|
||||
private onMouseDown(e: IListMouseEvent<T> | IListTouchEvent<T>): void {
|
||||
if (this.options.focusOnMouseDown === false) {
|
||||
e.browserEvent.preventDefault();
|
||||
e.browserEvent.stopPropagation();
|
||||
} else if (document.activeElement !== e.browserEvent.target) {
|
||||
if (document.activeElement !== e.browserEvent.target) {
|
||||
this.view.domNode.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private onContextMenu(e: IListMouseEvent<T> | IListTouchEvent<T>): void {
|
||||
const focus = typeof e.index === 'undefined' ? [] : [e.index];
|
||||
this.list.setFocus(focus, e.browserEvent);
|
||||
}
|
||||
|
||||
private onPointer(e: IListMouseEvent<T>): void {
|
||||
if (isInputElement(e.browserEvent.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let reference = this.list.getFocus()[0];
|
||||
const selection = this.list.getSelection();
|
||||
@@ -477,15 +584,13 @@ class MouseController<T> implements IDisposable {
|
||||
return this.changeSelection(e, reference);
|
||||
}
|
||||
|
||||
if (selection.every(s => s !== focus)) {
|
||||
this.list.setFocus([focus], e.browserEvent);
|
||||
}
|
||||
|
||||
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
|
||||
return this.changeSelection(e, reference);
|
||||
}
|
||||
|
||||
if (this.options.selectOnMouseDown && !isMouseRightClick(e.browserEvent)) {
|
||||
this.list.setFocus([focus], e.browserEvent);
|
||||
|
||||
if (!isMouseRightClick(e.browserEvent)) {
|
||||
this.list.setSelection([focus], e.browserEvent);
|
||||
|
||||
if (this.openController.shouldOpen(e.browserEvent)) {
|
||||
@@ -494,22 +599,11 @@ class MouseController<T> implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
private onPointer(e: IListMouseEvent<T>): void {
|
||||
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
|
||||
private onDoubleClick(e: IListMouseEvent<T>): void {
|
||||
if (isInputElement(e.browserEvent.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.options.selectOnMouseDown) {
|
||||
const focus = this.list.getFocus();
|
||||
this.list.setSelection(focus, e.browserEvent);
|
||||
|
||||
if (this.openController.shouldOpen(e.browserEvent)) {
|
||||
this.list.open(focus, e.browserEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onDoubleClick(e: IListMouseEvent<T>): void {
|
||||
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
|
||||
return;
|
||||
}
|
||||
@@ -576,9 +670,14 @@ export interface IAccessibilityProvider<T> {
|
||||
* Returning null will not disable ARIA for the element. Instead it is up to the screen reader
|
||||
* to compute a meaningful label based on the contents of the element in the DOM
|
||||
*
|
||||
* See also: https://www.w3.org/TR/wai-aria/states_and_properties#aria-label
|
||||
* See also: https://www.w3.org/TR/wai-aria/#aria-label
|
||||
*/
|
||||
getAriaLabel(element: T): string | null;
|
||||
|
||||
/**
|
||||
* https://www.w3.org/TR/wai-aria/#aria-level
|
||||
*/
|
||||
getAriaLevel?(element: T): number | undefined;
|
||||
}
|
||||
|
||||
export class DefaultStyleController implements IStyleController {
|
||||
@@ -608,11 +707,17 @@ export class DefaultStyleController implements IStyleController {
|
||||
}
|
||||
|
||||
if (styles.listFocusAndSelectionBackground) {
|
||||
content.push(`.monaco-list${suffix}:focus .monaco-list-row.selected.focused { background-color: ${styles.listFocusAndSelectionBackground}; }`);
|
||||
content.push(`
|
||||
.monaco-list-drag-image,
|
||||
.monaco-list${suffix}:focus .monaco-list-row.selected.focused { background-color: ${styles.listFocusAndSelectionBackground}; }
|
||||
`);
|
||||
}
|
||||
|
||||
if (styles.listFocusAndSelectionForeground) {
|
||||
content.push(`.monaco-list${suffix}:focus .monaco-list-row.selected.focused { color: ${styles.listFocusAndSelectionForeground}; }`);
|
||||
content.push(`
|
||||
.monaco-list-drag-image,
|
||||
.monaco-list${suffix}:focus .monaco-list-row.selected.focused { color: ${styles.listFocusAndSelectionForeground}; }
|
||||
`);
|
||||
}
|
||||
|
||||
if (styles.listInactiveFocusBackground) {
|
||||
@@ -630,11 +735,11 @@ export class DefaultStyleController implements IStyleController {
|
||||
}
|
||||
|
||||
if (styles.listHoverBackground) {
|
||||
content.push(`.monaco-list${suffix} .monaco-list-row:hover { background-color: ${styles.listHoverBackground}; }`);
|
||||
content.push(`.monaco-list${suffix}:not(.drop-target) .monaco-list-row:hover:not(.selected):not(.focused) { background-color: ${styles.listHoverBackground}; }`);
|
||||
}
|
||||
|
||||
if (styles.listHoverForeground) {
|
||||
content.push(`.monaco-list${suffix} .monaco-list-row:hover { color: ${styles.listHoverForeground}; }`);
|
||||
content.push(`.monaco-list${suffix} .monaco-list-row:hover:not(.selected):not(.focused) { color: ${styles.listHoverForeground}; }`);
|
||||
}
|
||||
|
||||
if (styles.listSelectionOutline) {
|
||||
@@ -642,7 +747,10 @@ export class DefaultStyleController implements IStyleController {
|
||||
}
|
||||
|
||||
if (styles.listFocusOutline) {
|
||||
content.push(`.monaco-list${suffix}:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`);
|
||||
content.push(`
|
||||
.monaco-list-drag-image,
|
||||
.monaco-list${suffix}:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }
|
||||
`);
|
||||
}
|
||||
|
||||
if (styles.listInactiveFocusOutline) {
|
||||
@@ -653,6 +761,29 @@ export class DefaultStyleController implements IStyleController {
|
||||
content.push(`.monaco-list${suffix} .monaco-list-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`);
|
||||
}
|
||||
|
||||
if (styles.listDropBackground) {
|
||||
content.push(`
|
||||
.monaco-list${suffix}.drop-target,
|
||||
.monaco-list${suffix} .monaco-list-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; }
|
||||
`);
|
||||
}
|
||||
|
||||
if (styles.listFilterWidgetBackground) {
|
||||
content.push(`.monaco-list-type-filter { background-color: ${styles.listFilterWidgetBackground} }`);
|
||||
}
|
||||
|
||||
if (styles.listFilterWidgetOutline) {
|
||||
content.push(`.monaco-list-type-filter { border: 1px solid ${styles.listFilterWidgetOutline}; }`);
|
||||
}
|
||||
|
||||
if (styles.listFilterWidgetNoMatchesOutline) {
|
||||
content.push(`.monaco-list-type-filter.no-matches { border: 1px solid ${styles.listFilterWidgetNoMatchesOutline}; }`);
|
||||
}
|
||||
|
||||
if (styles.listMatchesShadow) {
|
||||
content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`);
|
||||
}
|
||||
|
||||
const newStyles = content.join('\n');
|
||||
if (newStyles !== this.styleElement.innerHTML) {
|
||||
this.styleElement.innerHTML = newStyles;
|
||||
@@ -660,19 +791,27 @@ export class DefaultStyleController implements IStyleController {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IListOptions<T> extends IListViewOptions, IListStyles {
|
||||
identityProvider?: IIdentityProvider<T>;
|
||||
ariaLabel?: string;
|
||||
mouseSupport?: boolean;
|
||||
selectOnMouseDown?: boolean;
|
||||
focusOnMouseDown?: boolean;
|
||||
keyboardSupport?: boolean;
|
||||
verticalScrollMode?: ScrollbarVisibility;
|
||||
multipleSelectionSupport?: boolean;
|
||||
multipleSelectionController?: IMultipleSelectionController<T>;
|
||||
openController?: IOpenController;
|
||||
styleController?: IStyleController;
|
||||
accessibilityProvider?: IAccessibilityProvider<T>;
|
||||
export interface IListOptions<T> extends IListStyles {
|
||||
readonly identityProvider?: IIdentityProvider<T>;
|
||||
readonly dnd?: IListDragAndDrop<T>;
|
||||
readonly enableKeyboardNavigation?: boolean;
|
||||
readonly keyboardNavigationLabelProvider?: IKeyboardNavigationLabelProvider<T>;
|
||||
readonly ariaRole?: ListAriaRootRole;
|
||||
readonly ariaLabel?: string;
|
||||
readonly keyboardSupport?: boolean;
|
||||
readonly multipleSelectionSupport?: boolean;
|
||||
readonly multipleSelectionController?: IMultipleSelectionController<T>;
|
||||
readonly openController?: IOpenController;
|
||||
readonly styleController?: IStyleController;
|
||||
readonly accessibilityProvider?: IAccessibilityProvider<T>;
|
||||
|
||||
// list view options
|
||||
readonly useShadows?: boolean;
|
||||
readonly verticalScrollMode?: ScrollbarVisibility;
|
||||
readonly setRowLineHeight?: boolean;
|
||||
readonly supportDynamicHeights?: boolean;
|
||||
readonly mouseSupport?: boolean;
|
||||
readonly horizontalScrolling?: boolean;
|
||||
}
|
||||
|
||||
export interface IListStyles {
|
||||
@@ -692,6 +831,10 @@ export interface IListStyles {
|
||||
listInactiveFocusOutline?: Color;
|
||||
listSelectionOutline?: Color;
|
||||
listHoverOutline?: Color;
|
||||
listFilterWidgetBackground?: Color;
|
||||
listFilterWidgetOutline?: Color;
|
||||
listFilterWidgetNoMatchesOutline?: Color;
|
||||
listMatchesShadow?: Color;
|
||||
}
|
||||
|
||||
const defaultStyles: IListStyles = {
|
||||
@@ -705,10 +848,17 @@ const defaultStyles: IListStyles = {
|
||||
listDropBackground: Color.fromHex('#383B3D')
|
||||
};
|
||||
|
||||
const DefaultOptions: IListOptions<any> = {
|
||||
const DefaultOptions = {
|
||||
keyboardSupport: true,
|
||||
mouseSupport: true,
|
||||
multipleSelectionSupport: true
|
||||
multipleSelectionSupport: true,
|
||||
dnd: {
|
||||
getDragURI() { return null; },
|
||||
onDragStart(): void { },
|
||||
onDragOver() { return false; },
|
||||
drop() { }
|
||||
},
|
||||
ariaRootRole: ListAriaRootRole.TREE
|
||||
};
|
||||
|
||||
// TODO@Joao: move these utils into a SortedArray class
|
||||
@@ -819,7 +969,11 @@ class PipelineRenderer<T> implements IListRenderer<T, any> {
|
||||
let i = 0;
|
||||
|
||||
for (const renderer of this.renderers) {
|
||||
renderer.disposeElement(element, index, templateData[i++]);
|
||||
if (renderer.disposeElement) {
|
||||
renderer.disposeElement(element, index, templateData[i]);
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -836,9 +990,7 @@ class AccessibiltyRenderer<T> implements IListRenderer<T, HTMLElement> {
|
||||
|
||||
templateId: string = 'a18n';
|
||||
|
||||
constructor(private accessibilityProvider: IAccessibilityProvider<T>) {
|
||||
|
||||
}
|
||||
constructor(private accessibilityProvider: IAccessibilityProvider<T>) { }
|
||||
|
||||
renderTemplate(container: HTMLElement): HTMLElement {
|
||||
return container;
|
||||
@@ -852,10 +1004,14 @@ class AccessibiltyRenderer<T> implements IListRenderer<T, HTMLElement> {
|
||||
} else {
|
||||
container.removeAttribute('aria-label');
|
||||
}
|
||||
}
|
||||
|
||||
disposeElement(element: T, index: number, container: HTMLElement): void {
|
||||
// noop
|
||||
const ariaLevel = this.accessibilityProvider.getAriaLevel && this.accessibilityProvider.getAriaLevel(element);
|
||||
|
||||
if (typeof ariaLevel === 'number') {
|
||||
container.setAttribute('aria-level', `${ariaLevel}`);
|
||||
} else {
|
||||
container.removeAttribute('aria-level');
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: any): void {
|
||||
@@ -863,6 +1019,47 @@ class AccessibiltyRenderer<T> implements IListRenderer<T, HTMLElement> {
|
||||
}
|
||||
}
|
||||
|
||||
class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
|
||||
|
||||
constructor(private list: List<T>, private dnd: IListDragAndDrop<T>) { }
|
||||
|
||||
getDragElements(element: T): T[] {
|
||||
const selection = this.list.getSelectedElements();
|
||||
const elements = selection.indexOf(element) > -1 ? selection : [element];
|
||||
return elements;
|
||||
}
|
||||
|
||||
getDragURI(element: T): string | null {
|
||||
return this.dnd.getDragURI(element);
|
||||
}
|
||||
|
||||
getDragLabel?(elements: T[]): string | undefined {
|
||||
if (this.dnd.getDragLabel) {
|
||||
return this.dnd.getDragLabel(elements);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
|
||||
if (this.dnd.onDragStart) {
|
||||
this.dnd.onDragStart(data, originalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
onDragOver(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): boolean | IListDragOverReaction {
|
||||
return this.dnd.onDragOver(data, targetElement, targetIndex, originalEvent);
|
||||
}
|
||||
|
||||
drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void {
|
||||
this.dnd.drop(data, targetElement, targetIndex, originalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IListOptionsUpdate {
|
||||
readonly enableKeyboardNavigation?: boolean;
|
||||
}
|
||||
|
||||
export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
private static InstanceCount = 0;
|
||||
@@ -873,26 +1070,36 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
private eventBufferer = new EventBufferer();
|
||||
private view: ListView<T>;
|
||||
private spliceable: ISpliceable<T>;
|
||||
protected disposables: IDisposable[];
|
||||
private styleElement: HTMLStyleElement;
|
||||
private styleController: IStyleController;
|
||||
private mouseController: MouseController<T> | undefined;
|
||||
|
||||
get multipleSelectionController(): IMultipleSelectionController<T> {
|
||||
return (this.mouseController && this.mouseController.multipleSelectionController) || DefaultMultipleSelectionContoller;
|
||||
}
|
||||
|
||||
private _onDidUpdateOptions = new Emitter<IListOptions<T>>();
|
||||
readonly onDidUpdateOptions = this._onDidUpdateOptions.event;
|
||||
|
||||
protected disposables: IDisposable[];
|
||||
|
||||
@memoize get onFocusChange(): Event<IListEvent<T>> {
|
||||
return mapEvent(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e));
|
||||
return Event.map(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e));
|
||||
}
|
||||
|
||||
@memoize get onSelectionChange(): Event<IListEvent<T>> {
|
||||
return mapEvent(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e));
|
||||
return Event.map(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e));
|
||||
}
|
||||
|
||||
private _onOpen = new Emitter<IListEvent<T>>();
|
||||
readonly onOpen: Event<IListEvent<T>> = this._onOpen.event;
|
||||
private _onDidOpen = new Emitter<IListEvent<T>>();
|
||||
readonly onDidOpen: Event<IListEvent<T>> = this._onDidOpen.event;
|
||||
|
||||
private _onPin = new Emitter<number[]>();
|
||||
@memoize get onPin(): Event<IListEvent<T>> {
|
||||
return mapEvent(this._onPin.event, indexes => this.toListEvent({ indexes }));
|
||||
return Event.map(this._onPin.event, indexes => this.toListEvent({ indexes }));
|
||||
}
|
||||
|
||||
get onDidScroll(): Event<void> { return this.view.onDidScroll; }
|
||||
get onMouseClick(): Event<IListMouseEvent<T>> { return this.view.onMouseClick; }
|
||||
get onMouseDblClick(): Event<IListMouseEvent<T>> { return this.view.onMouseDblClick; }
|
||||
get onMouseMiddleClick(): Event<IListMouseEvent<T>> { return this.view.onMouseMiddleClick; }
|
||||
@@ -906,7 +1113,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
private didJustPressContextMenuKey: boolean = false;
|
||||
@memoize get onContextMenu(): Event<IListContextMenuEvent<T>> {
|
||||
const fromKeydown = chain(domEvent(this.view.domNode, 'keydown'))
|
||||
const fromKeydown = Event.chain(domEvent(this.view.domNode, 'keydown'))
|
||||
.map(e => new StandardKeyboardEvent(e))
|
||||
.filter(e => this.didJustPressContextMenuKey = e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10))
|
||||
.filter(e => { e.preventDefault(); e.stopPropagation(); return false; })
|
||||
@@ -918,7 +1125,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
})
|
||||
.event;
|
||||
|
||||
const fromKeyup = chain(domEvent(this.view.domNode, 'keyup'))
|
||||
const fromKeyup = Event.chain(domEvent(this.view.domNode, 'keyup'))
|
||||
.filter(() => {
|
||||
const didJustPressContextMenuKey = this.didJustPressContextMenuKey;
|
||||
this.didJustPressContextMenuKey = false;
|
||||
@@ -934,12 +1141,12 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
.filter(({ anchor }) => !!anchor)
|
||||
.event;
|
||||
|
||||
const fromMouse = chain(this.view.onContextMenu)
|
||||
const fromMouse = Event.chain(this.view.onContextMenu)
|
||||
.filter(() => !this.didJustPressContextMenuKey)
|
||||
.map(({ element, index, browserEvent }) => ({ element, index, anchor: { x: browserEvent.clientX + 1, y: browserEvent.clientY }, browserEvent }))
|
||||
.event;
|
||||
|
||||
return anyEvent<IListContextMenuEvent<T>>(fromKeydown, fromKeyup, fromMouse);
|
||||
return Event.any<IListContextMenuEvent<T>>(fromKeydown, fromKeyup, fromMouse);
|
||||
}
|
||||
|
||||
get onKeyDown(): Event<KeyboardEvent> { return domEvent(this.view.domNode, 'keydown'); }
|
||||
@@ -956,60 +1163,86 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
container: HTMLElement,
|
||||
virtualDelegate: IListVirtualDelegate<T>,
|
||||
renderers: IListRenderer<any /* TODO@joao */, any>[],
|
||||
options: IListOptions<T> = DefaultOptions
|
||||
private _options: IListOptions<T> = DefaultOptions
|
||||
) {
|
||||
this.focus = new FocusTrait(i => this.getElementDomId(i));
|
||||
this.selection = new Trait('selected');
|
||||
|
||||
mixin(options, defaultStyles, false);
|
||||
mixin(_options, defaultStyles, false);
|
||||
|
||||
const baseRenderers: IListRenderer<T, ITraitTemplateData>[] = [this.focus.renderer, this.selection.renderer];
|
||||
|
||||
if (options.accessibilityProvider) {
|
||||
baseRenderers.push(new AccessibiltyRenderer<T>(options.accessibilityProvider));
|
||||
if (_options.accessibilityProvider) {
|
||||
baseRenderers.push(new AccessibiltyRenderer<T>(_options.accessibilityProvider));
|
||||
}
|
||||
|
||||
renderers = renderers.map(r => new PipelineRenderer(r.templateId, [...baseRenderers, r]));
|
||||
|
||||
this.view = new ListView(container, virtualDelegate, renderers, options);
|
||||
this.view.domNode.setAttribute('role', 'tree');
|
||||
const viewOptions: IListViewOptions<T> = {
|
||||
..._options,
|
||||
dnd: _options.dnd && new ListViewDragAndDrop(this, _options.dnd)
|
||||
};
|
||||
|
||||
this.view = new ListView(container, virtualDelegate, renderers, viewOptions);
|
||||
|
||||
if (typeof _options.ariaRole !== 'string') {
|
||||
this.view.domNode.setAttribute('role', ListAriaRootRole.TREE);
|
||||
} else {
|
||||
this.view.domNode.setAttribute('role', _options.ariaRole);
|
||||
}
|
||||
|
||||
DOM.addClass(this.view.domNode, this.idPrefix);
|
||||
this.view.domNode.tabIndex = 0;
|
||||
|
||||
this.styleElement = DOM.createStyleSheet(this.view.domNode);
|
||||
|
||||
this.styleController = options.styleController || new DefaultStyleController(this.styleElement, this.idPrefix);
|
||||
this.styleController = _options.styleController || new DefaultStyleController(this.styleElement, this.idPrefix);
|
||||
|
||||
this.spliceable = new CombinedSpliceable([
|
||||
new TraitSpliceable(this.focus, this.view, options.identityProvider),
|
||||
new TraitSpliceable(this.selection, this.view, options.identityProvider),
|
||||
new TraitSpliceable(this.focus, this.view, _options.identityProvider),
|
||||
new TraitSpliceable(this.selection, this.view, _options.identityProvider),
|
||||
this.view
|
||||
]);
|
||||
|
||||
this.disposables = [this.focus, this.selection, this.view, this._onDidDispose];
|
||||
|
||||
this.onDidFocus = mapEvent(domEvent(this.view.domNode, 'focus', true), () => null!);
|
||||
this.onDidBlur = mapEvent(domEvent(this.view.domNode, 'blur', true), () => null!);
|
||||
this.onDidFocus = Event.map(domEvent(this.view.domNode, 'focus', true), () => null!);
|
||||
this.onDidBlur = Event.map(domEvent(this.view.domNode, 'blur', true), () => null!);
|
||||
|
||||
this.disposables.push(new DOMFocusController(this, this.view));
|
||||
|
||||
if (typeof options.keyboardSupport !== 'boolean' || options.keyboardSupport) {
|
||||
const controller = new KeyboardController(this, this.view, options);
|
||||
if (typeof _options.keyboardSupport !== 'boolean' || _options.keyboardSupport) {
|
||||
const controller = new KeyboardController(this, this.view, _options);
|
||||
this.disposables.push(controller);
|
||||
}
|
||||
|
||||
if (typeof options.mouseSupport === 'boolean' ? options.mouseSupport : true) {
|
||||
this.disposables.push(new MouseController(this, this.view, options));
|
||||
if (_options.keyboardNavigationLabelProvider) {
|
||||
const controller = new TypeLabelController(this, this.view, _options.keyboardNavigationLabelProvider);
|
||||
this.disposables.push(controller);
|
||||
}
|
||||
|
||||
if (typeof _options.mouseSupport === 'boolean' ? _options.mouseSupport : true) {
|
||||
this.mouseController = new MouseController(this, this.view, _options);
|
||||
this.disposables.push(this.mouseController);
|
||||
}
|
||||
|
||||
this.onFocusChange(this._onFocusChange, this, this.disposables);
|
||||
this.onSelectionChange(this._onSelectionChange, this, this.disposables);
|
||||
|
||||
if (options.ariaLabel) {
|
||||
this.view.domNode.setAttribute('aria-label', localize('aria list', "{0}. Use the navigation keys to navigate.", options.ariaLabel));
|
||||
if (_options.ariaLabel) {
|
||||
this.view.domNode.setAttribute('aria-label', localize('aria list', "{0}. Use the navigation keys to navigate.", _options.ariaLabel));
|
||||
}
|
||||
|
||||
this.style(options);
|
||||
this.style(_options);
|
||||
}
|
||||
|
||||
updateOptions(optionsUpdate: IListOptionsUpdate = {}): void {
|
||||
this._options = { ...this._options, ...optionsUpdate };
|
||||
this._onDidUpdateOptions.fire(this._options);
|
||||
}
|
||||
|
||||
get options(): IListOptions<T> {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
splice(start: number, deleteCount: number, elements: T[] = []): void {
|
||||
@@ -1028,6 +1261,14 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
this.eventBufferer.bufferEvents(() => this.spliceable.splice(start, deleteCount, elements));
|
||||
}
|
||||
|
||||
updateWidth(index: number): void {
|
||||
this.view.updateWidth(index);
|
||||
}
|
||||
|
||||
element(index: number): T {
|
||||
return this.view.element(index);
|
||||
}
|
||||
|
||||
get length(): number {
|
||||
return this.view.length;
|
||||
}
|
||||
@@ -1060,12 +1301,8 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
this.view.domNode.focus();
|
||||
}
|
||||
|
||||
layout(height?: number): void {
|
||||
this.view.layout(height);
|
||||
}
|
||||
|
||||
layoutWidth(width: number): void {
|
||||
this.view.layoutWidth(width);
|
||||
layout(height?: number, width?: number): void {
|
||||
this.view.layout(height, width);
|
||||
}
|
||||
|
||||
setSelection(indexes: number[], browserEvent?: UIEvent): void {
|
||||
@@ -1098,41 +1335,54 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
this.focus.set(indexes, browserEvent);
|
||||
}
|
||||
|
||||
focusNext(n = 1, loop = false, browserEvent?: UIEvent): void {
|
||||
focusNext(n = 1, loop = false, browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
|
||||
if (this.length === 0) { return; }
|
||||
|
||||
const focus = this.focus.get();
|
||||
let index = focus.length > 0 ? focus[0] + n : 0;
|
||||
this.setFocus(loop ? [index % this.length] : [Math.min(index, this.length - 1)], browserEvent);
|
||||
const index = this.findNextIndex(focus.length > 0 ? focus[0] + n : 0, loop, filter);
|
||||
|
||||
if (index > -1) {
|
||||
this.setFocus([index], browserEvent);
|
||||
}
|
||||
}
|
||||
|
||||
focusPrevious(n = 1, loop = false, browserEvent?: UIEvent): void {
|
||||
focusPrevious(n = 1, loop = false, browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
|
||||
if (this.length === 0) { return; }
|
||||
|
||||
const focus = this.focus.get();
|
||||
let index = focus.length > 0 ? focus[0] - n : 0;
|
||||
if (loop && index < 0) { index = (this.length + (index % this.length)) % this.length; }
|
||||
this.setFocus([Math.max(index, 0)], browserEvent);
|
||||
const index = this.findPreviousIndex(focus.length > 0 ? focus[0] - n : 0, loop, filter);
|
||||
|
||||
if (index > -1) {
|
||||
this.setFocus([index], browserEvent);
|
||||
}
|
||||
}
|
||||
|
||||
focusNextPage(browserEvent?: UIEvent): void {
|
||||
focusNextPage(browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
|
||||
let lastPageIndex = this.view.indexAt(this.view.getScrollTop() + this.view.renderHeight);
|
||||
lastPageIndex = lastPageIndex === 0 ? 0 : lastPageIndex - 1;
|
||||
const lastPageElement = this.view.element(lastPageIndex);
|
||||
const currentlyFocusedElement = this.getFocusedElements()[0];
|
||||
|
||||
if (currentlyFocusedElement !== lastPageElement) {
|
||||
this.setFocus([lastPageIndex], browserEvent);
|
||||
const lastGoodPageIndex = this.findPreviousIndex(lastPageIndex, false, filter);
|
||||
|
||||
if (lastGoodPageIndex > -1 && currentlyFocusedElement !== this.view.element(lastGoodPageIndex)) {
|
||||
this.setFocus([lastGoodPageIndex], browserEvent);
|
||||
} else {
|
||||
this.setFocus([lastPageIndex], browserEvent);
|
||||
}
|
||||
} else {
|
||||
const previousScrollTop = this.view.getScrollTop();
|
||||
this.view.setScrollTop(previousScrollTop + this.view.renderHeight - this.view.elementHeight(lastPageIndex));
|
||||
|
||||
if (this.view.getScrollTop() !== previousScrollTop) {
|
||||
// Let the scroll event listener run
|
||||
setTimeout(() => this.focusNextPage(browserEvent), 0);
|
||||
setTimeout(() => this.focusNextPage(browserEvent, filter), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
focusPreviousPage(browserEvent?: UIEvent): void {
|
||||
focusPreviousPage(browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
|
||||
let firstPageIndex: number;
|
||||
const scrollTop = this.view.getScrollTop();
|
||||
|
||||
@@ -1146,26 +1396,78 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
const currentlyFocusedElement = this.getFocusedElements()[0];
|
||||
|
||||
if (currentlyFocusedElement !== firstPageElement) {
|
||||
this.setFocus([firstPageIndex], browserEvent);
|
||||
const firstGoodPageIndex = this.findNextIndex(firstPageIndex, false, filter);
|
||||
|
||||
if (firstGoodPageIndex > -1 && currentlyFocusedElement !== this.view.element(firstGoodPageIndex)) {
|
||||
this.setFocus([firstGoodPageIndex], browserEvent);
|
||||
} else {
|
||||
this.setFocus([firstPageIndex], browserEvent);
|
||||
}
|
||||
} else {
|
||||
const previousScrollTop = scrollTop;
|
||||
this.view.setScrollTop(scrollTop - this.view.renderHeight);
|
||||
|
||||
if (this.view.getScrollTop() !== previousScrollTop) {
|
||||
// Let the scroll event listener run
|
||||
setTimeout(() => this.focusPreviousPage(browserEvent), 0);
|
||||
setTimeout(() => this.focusPreviousPage(browserEvent, filter), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
focusLast(browserEvent?: UIEvent): void {
|
||||
focusLast(browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
|
||||
if (this.length === 0) { return; }
|
||||
this.setFocus([this.length - 1], browserEvent);
|
||||
|
||||
const index = this.findPreviousIndex(this.length - 1, false, filter);
|
||||
|
||||
if (index > -1) {
|
||||
this.setFocus([index], browserEvent);
|
||||
}
|
||||
}
|
||||
|
||||
focusFirst(browserEvent?: UIEvent): void {
|
||||
focusFirst(browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
|
||||
if (this.length === 0) { return; }
|
||||
this.setFocus([0], browserEvent);
|
||||
|
||||
const index = this.findNextIndex(0, false, filter);
|
||||
|
||||
if (index > -1) {
|
||||
this.setFocus([index], browserEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private findNextIndex(index: number, loop = false, filter?: (element: T) => boolean): number {
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
if (index >= this.length && !loop) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
index = index % this.length;
|
||||
|
||||
if (!filter || filter(this.element(index))) {
|
||||
return index;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private findPreviousIndex(index: number, loop = false, filter?: (element: T) => boolean): number {
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
if (index < 0 && !loop) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
index = (this.length + (index % this.length)) % this.length;
|
||||
|
||||
if (!filter || filter(this.element(index))) {
|
||||
return index;
|
||||
}
|
||||
|
||||
index--;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
getFocus(): number[] {
|
||||
@@ -1242,7 +1544,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
this._onOpen.fire({ indexes, elements: indexes.map(i => this.view.element(i)), browserEvent });
|
||||
this._onDidOpen.fire({ indexes, elements: indexes.map(i => this.view.element(i)), browserEvent });
|
||||
}
|
||||
|
||||
pin(indexes: number[]): void {
|
||||
@@ -1288,7 +1590,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
this._onDidDispose.fire();
|
||||
this.disposables = dispose(this.disposables);
|
||||
|
||||
this._onOpen.dispose();
|
||||
this._onDidOpen.dispose();
|
||||
this._onPin.dispose();
|
||||
this._onDidDispose.dispose();
|
||||
}
|
||||
|
||||
1
src/vs/base/browser/ui/list/media/close-dark.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#e8e8e8" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
|
After Width: | Height: | Size: 307 B |
33
src/vs/base/browser/ui/list/media/close-hc.svg
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg7320"
|
||||
version="1.1"
|
||||
enable-background="new 3 3 16 16"
|
||||
viewBox="3 3 16 16"
|
||||
height="16"
|
||||
width="16">
|
||||
<metadata
|
||||
id="metadata7326">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs7324" />
|
||||
<polygon
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
id="polygon7318"
|
||||
points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"
|
||||
fill="#424242" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1006 B |
1
src/vs/base/browser/ui/list/media/close.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#424242" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
|
After Width: | Height: | Size: 307 B |
5
src/vs/base/browser/ui/list/media/filter-dark.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.0002 10H6.00024V12H10.0002V10Z" fill="#E8E8E8"/>
|
||||
<path d="M11.9998 7H3.99976V9H11.9998V7Z" fill="#E8E8E8"/>
|
||||
<path d="M14 4H2V6H14V4Z" fill="#E8E8E8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 267 B |
5
src/vs/base/browser/ui/list/media/filter-hc.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.0002 10H6.00024V12H10.0002V10Z" fill="white"/>
|
||||
<path d="M11.9998 7H3.99976V9H11.9998V7Z" fill="white"/>
|
||||
<path d="M14 4H2V6H14V4Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 261 B |
5
src/vs/base/browser/ui/list/media/filter.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.0002 10H6.00024V12H10.0002V10Z" fill="#424242"/>
|
||||
<path d="M11.9998 7H3.99976V9H11.9998V7Z" fill="#424242"/>
|
||||
<path d="M14 4H2V6H14V4Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 267 B |
5
src/vs/base/browser/ui/list/media/no-filter-dark.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 9.99951H2V11.9995H6V9.99951Z" fill="#E8E8E8"/>
|
||||
<path d="M10 7H2V9H10V7Z" fill="#E8E8E8"/>
|
||||
<path d="M14 4H2V6H14V4Z" fill="#E8E8E8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 248 B |
5
src/vs/base/browser/ui/list/media/no-filter-hc.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 9.99951H2V11.9995H6V9.99951Z" fill="white"/>
|
||||
<path d="M10 7H2V9H10V7Z" fill="white"/>
|
||||
<path d="M14 4H2V6H14V4Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 242 B |
5
src/vs/base/browser/ui/list/media/no-filter.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 9.99951H2V11.9995H6V9.99951Z" fill="#4B4B4B"/>
|
||||
<path d="M10 7H2V9H10V7Z" fill="#4B4B4B"/>
|
||||
<path d="M14 4H2V6H14V4Z" fill="#4B4B4B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 248 B |
@@ -38,7 +38,7 @@ export class RowCache<T> implements IDisposable {
|
||||
|
||||
if (!result) {
|
||||
const domNode = $('.monaco-list-row');
|
||||
const renderer = this.renderers.get(templateId);
|
||||
const renderer = this.getRenderer(templateId);
|
||||
const templateData = renderer.renderTemplate(domNode);
|
||||
result = { domNode, templateId, templateData };
|
||||
}
|
||||
@@ -86,7 +86,7 @@ export class RowCache<T> implements IDisposable {
|
||||
|
||||
this.cache.forEach((cachedRows, templateId) => {
|
||||
for (const cachedRow of cachedRows) {
|
||||
const renderer = this.renderers.get(templateId);
|
||||
const renderer = this.getRenderer(templateId);
|
||||
renderer.disposeTemplate(cachedRow.templateData);
|
||||
cachedRow.domNode = null;
|
||||
cachedRow.templateData = null;
|
||||
@@ -101,4 +101,12 @@ export class RowCache<T> implements IDisposable {
|
||||
this.cache.clear();
|
||||
this.renderers = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
}
|
||||
|
||||
private getRenderer(templateId: string): IListRenderer<T, any> {
|
||||
const renderer = this.renderers.get(templateId);
|
||||
if (!renderer) {
|
||||
throw new Error(`No renderer found for ${templateId}`);
|
||||
}
|
||||
return renderer;
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,7 @@
|
||||
/* Context Menu */
|
||||
|
||||
.context-view.monaco-menu-container {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
outline: 0;
|
||||
border: none;
|
||||
-webkit-animation: fadeIn 0.083s linear;
|
||||
@@ -154,7 +154,6 @@
|
||||
flex-shrink: 1;
|
||||
box-sizing: border-box;
|
||||
height: 30px;
|
||||
-webkit-app-region: no-drag;
|
||||
overflow: hidden;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@@ -183,7 +182,7 @@
|
||||
}
|
||||
|
||||
.menubar .menubar-menu-items-holder.monaco-menu-container {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
outline: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,10 @@ import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
|
||||
export const MENU_MNEMONIC_REGEX: RegExp = /\(&{1,2}(.)\)|&{1,2}(.)/;
|
||||
export const MENU_ESCAPED_MNEMONIC_REGEX: RegExp = /(?:&){1,2}(.)/;
|
||||
@@ -26,7 +27,7 @@ export interface IMenuOptions {
|
||||
context?: any;
|
||||
actionItemProvider?: IActionItemProvider;
|
||||
actionRunner?: IActionRunner;
|
||||
getKeyBinding?: (action: IAction) => ResolvedKeybinding;
|
||||
getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined;
|
||||
ariaLabel?: string;
|
||||
enableMnemonics?: boolean;
|
||||
anchorAlignment?: AnchorAlignment;
|
||||
@@ -44,7 +45,7 @@ export interface IMenuStyles {
|
||||
}
|
||||
|
||||
export class SubmenuAction extends Action {
|
||||
constructor(label: string, public entries: (SubmenuAction | IAction)[], cssClass?: string) {
|
||||
constructor(label: string, public entries: Array<SubmenuAction | IAction>, cssClass?: string) {
|
||||
super(!!cssClass ? cssClass : 'submenu', label, '', true);
|
||||
}
|
||||
}
|
||||
@@ -59,6 +60,7 @@ export class Menu extends ActionBar {
|
||||
private menuDisposables: IDisposable[];
|
||||
private scrollableElement: DomScrollableElement;
|
||||
private menuElement: HTMLElement;
|
||||
private scrollTopHold: number | undefined;
|
||||
|
||||
private readonly _onScroll: Emitter<void>;
|
||||
|
||||
@@ -94,48 +96,73 @@ export class Menu extends ActionBar {
|
||||
const key = KeyCodeUtils.fromString(e.key);
|
||||
if (this.mnemonics.has(key)) {
|
||||
EventHelper.stop(e, true);
|
||||
const actions = this.mnemonics.get(key);
|
||||
const actions = this.mnemonics.get(key)!;
|
||||
|
||||
if (actions.length === 1) {
|
||||
if (actions[0] instanceof SubmenuActionItem) {
|
||||
this.focusItemByElement(actions[0].container);
|
||||
}
|
||||
|
||||
actions[0].onClick(event);
|
||||
actions[0].onClick(e);
|
||||
}
|
||||
|
||||
if (actions.length > 1) {
|
||||
const action = actions.shift();
|
||||
this.focusItemByElement(action.container);
|
||||
if (action) {
|
||||
this.focusItemByElement(action.container);
|
||||
actions.push(action);
|
||||
}
|
||||
|
||||
actions.push(action);
|
||||
this.mnemonics.set(key, actions);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
if (isLinux) {
|
||||
this._register(addDisposableListener(menuElement, EventType.KEY_DOWN, e => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
|
||||
if (event.equals(KeyCode.Home) || event.equals(KeyCode.PageUp)) {
|
||||
this.focusedItem = this.items.length - 1;
|
||||
this.focusNext();
|
||||
EventHelper.stop(e, true);
|
||||
} else if (event.equals(KeyCode.End) || event.equals(KeyCode.PageDown)) {
|
||||
this.focusedItem = 0;
|
||||
this.focusPrevious();
|
||||
EventHelper.stop(e, true);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
this._register(addDisposableListener(this.domNode, EventType.MOUSE_OUT, e => {
|
||||
let relatedTarget = e.relatedTarget as HTMLElement;
|
||||
if (!isAncestor(relatedTarget, this.domNode)) {
|
||||
this.focusedItem = undefined;
|
||||
this.scrollTopHold = this.menuElement.scrollTop;
|
||||
this.updateFocus();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(addDisposableListener(this.domNode, EventType.MOUSE_UP, e => {
|
||||
// Absorb clicks in menu dead space https://github.com/Microsoft/vscode/issues/63575
|
||||
EventHelper.stop(e, true);
|
||||
}));
|
||||
|
||||
this._register(addDisposableListener(this.actionsList, EventType.MOUSE_OVER, e => {
|
||||
let target = e.target as HTMLElement;
|
||||
if (!target || !isAncestor(target, this.actionsList) || target === this.actionsList) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (target.parentElement !== this.actionsList) {
|
||||
while (target.parentElement !== this.actionsList && target.parentElement !== null) {
|
||||
target = target.parentElement;
|
||||
}
|
||||
|
||||
if (hasClass(target, 'action-item')) {
|
||||
const lastFocusedItem = this.focusedItem;
|
||||
this.scrollTopHold = this.menuElement.scrollTop;
|
||||
this.setFocusedItem(target);
|
||||
|
||||
if (lastFocusedItem !== this.focusedItem) {
|
||||
@@ -171,7 +198,11 @@ export class Menu extends ActionBar {
|
||||
this._onScroll.fire();
|
||||
}, this, this.menuDisposables);
|
||||
|
||||
this._register(addDisposableListener(this.menuElement, EventType.SCROLL, (e) => {
|
||||
this._register(addDisposableListener(this.menuElement, EventType.SCROLL, (e: ScrollEvent) => {
|
||||
if (this.scrollTopHold !== undefined) {
|
||||
this.menuElement.scrollTop = this.scrollTopHold;
|
||||
this.scrollTopHold = undefined;
|
||||
}
|
||||
this.scrollableElement.scanDomNode();
|
||||
}));
|
||||
|
||||
@@ -261,7 +292,7 @@ export class Menu extends ActionBar {
|
||||
if (mnemonic && menuActionItem.isEnabled()) {
|
||||
let actionItems: MenuActionItem[] = [];
|
||||
if (this.mnemonics.has(mnemonic)) {
|
||||
actionItems = this.mnemonics.get(mnemonic);
|
||||
actionItems = this.mnemonics.get(mnemonic)!;
|
||||
}
|
||||
|
||||
actionItems.push(menuActionItem);
|
||||
@@ -276,7 +307,11 @@ export class Menu extends ActionBar {
|
||||
if (options.getKeyBinding) {
|
||||
const keybinding = options.getKeyBinding(action);
|
||||
if (keybinding) {
|
||||
menuItemOptions.keybinding = keybinding.getLabel();
|
||||
const keybindingLabel = keybinding.getLabel();
|
||||
|
||||
if (keybindingLabel) {
|
||||
menuItemOptions.keybinding = keybindingLabel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +322,7 @@ export class Menu extends ActionBar {
|
||||
if (mnemonic && menuActionItem.isEnabled()) {
|
||||
let actionItems: MenuActionItem[] = [];
|
||||
if (this.mnemonics.has(mnemonic)) {
|
||||
actionItems = this.mnemonics.get(mnemonic);
|
||||
actionItems = this.mnemonics.get(mnemonic)!;
|
||||
}
|
||||
|
||||
actionItems.push(menuActionItem);
|
||||
@@ -342,6 +377,10 @@ class MenuActionItem extends BaseActionItem {
|
||||
render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
if (!this.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.container = container;
|
||||
|
||||
this.item = append(this.element, $('a.action-menu-item'));
|
||||
@@ -439,7 +478,7 @@ class MenuActionItem extends BaseActionItem {
|
||||
removeClasses(this.item, this.cssClass);
|
||||
}
|
||||
if (this.options.icon) {
|
||||
this.cssClass = this.getAction().class;
|
||||
this.cssClass = this.getAction().class || '';
|
||||
addClass(this.label, 'icon');
|
||||
if (this.cssClass) {
|
||||
addClasses(this.label, this.cssClass);
|
||||
@@ -452,11 +491,17 @@ class MenuActionItem extends BaseActionItem {
|
||||
|
||||
updateEnabled(): void {
|
||||
if (this.getAction().enabled) {
|
||||
removeClass(this.element, 'disabled');
|
||||
if (this.element) {
|
||||
removeClass(this.element, 'disabled');
|
||||
}
|
||||
|
||||
removeClass(this.item, 'disabled');
|
||||
this.item.tabIndex = 0;
|
||||
} else {
|
||||
addClass(this.element, 'disabled');
|
||||
if (this.element) {
|
||||
addClass(this.element, 'disabled');
|
||||
}
|
||||
|
||||
addClass(this.item, 'disabled');
|
||||
removeTabIndexAndUpdateFocus(this.item);
|
||||
}
|
||||
@@ -483,7 +528,7 @@ class MenuActionItem extends BaseActionItem {
|
||||
return;
|
||||
}
|
||||
|
||||
const isSelected = hasClass(this.element, 'focused');
|
||||
const isSelected = this.element && hasClass(this.element, 'focused');
|
||||
const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor;
|
||||
const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : this.menuStyle.backgroundColor;
|
||||
const border = isSelected && this.menuStyle.selectionBorderColor ? `1px solid ${this.menuStyle.selectionBorderColor}` : null;
|
||||
@@ -501,8 +546,8 @@ class MenuActionItem extends BaseActionItem {
|
||||
}
|
||||
|
||||
class SubmenuActionItem extends MenuActionItem {
|
||||
private mysubmenu: Menu;
|
||||
private submenuContainer: HTMLElement;
|
||||
private mysubmenu: Menu | null;
|
||||
private submenuContainer: HTMLElement | undefined;
|
||||
private submenuIndicator: HTMLElement;
|
||||
private submenuDisposables: IDisposable[] = [];
|
||||
private mouseOver: boolean;
|
||||
@@ -525,7 +570,7 @@ class SubmenuActionItem extends MenuActionItem {
|
||||
}, 250);
|
||||
|
||||
this.hideScheduler = new RunOnceScheduler(() => {
|
||||
if ((!isAncestor(document.activeElement, this.element) && this.parentData.submenu === this.mysubmenu)) {
|
||||
if (this.element && (!isAncestor(document.activeElement, this.element) && this.parentData.submenu === this.mysubmenu)) {
|
||||
this.parentData.parent.focus(false);
|
||||
this.cleanupExistingSubmenu(true);
|
||||
}
|
||||
@@ -535,6 +580,10 @@ class SubmenuActionItem extends MenuActionItem {
|
||||
render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
if (!this.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
addClass(this.item, 'monaco-submenu-item');
|
||||
this.item.setAttribute('aria-haspopup', 'true');
|
||||
|
||||
@@ -570,7 +619,7 @@ class SubmenuActionItem extends MenuActionItem {
|
||||
}));
|
||||
|
||||
this._register(addDisposableListener(this.element, EventType.FOCUS_OUT, e => {
|
||||
if (!isAncestor(document.activeElement, this.element)) {
|
||||
if (this.element && !isAncestor(document.activeElement, this.element)) {
|
||||
this.hideScheduler.schedule();
|
||||
}
|
||||
}));
|
||||
@@ -597,16 +646,20 @@ class SubmenuActionItem extends MenuActionItem {
|
||||
private cleanupExistingSubmenu(force: boolean): void {
|
||||
if (this.parentData.submenu && (force || (this.parentData.submenu !== this.mysubmenu))) {
|
||||
this.parentData.submenu.dispose();
|
||||
this.parentData.submenu = null;
|
||||
this.parentData.submenu = undefined;
|
||||
|
||||
if (this.submenuContainer) {
|
||||
this.submenuDisposables = dispose(this.submenuDisposables);
|
||||
this.submenuContainer = null;
|
||||
this.submenuContainer = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createSubmenu(selectFirstItem = true): void {
|
||||
if (!this.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.parentData.submenu) {
|
||||
this.submenuContainer = append(this.element, $('div.monaco-submenu'));
|
||||
addClasses(this.submenuContainer, 'menubar-menu-items-holder', 'context-view');
|
||||
@@ -618,13 +671,15 @@ class SubmenuActionItem extends MenuActionItem {
|
||||
|
||||
const boundingRect = this.element.getBoundingClientRect();
|
||||
const childBoundingRect = this.submenuContainer.getBoundingClientRect();
|
||||
const computedStyles = getComputedStyle(this.parentData.parent.domNode);
|
||||
const paddingTop = parseFloat(computedStyles.paddingTop || '0') || 0;
|
||||
|
||||
if (window.innerWidth <= boundingRect.right + childBoundingRect.width) {
|
||||
this.submenuContainer.style.left = '10px';
|
||||
this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset + boundingRect.height}px`;
|
||||
} else {
|
||||
this.submenuContainer.style.left = `${this.element.offsetWidth}px`;
|
||||
this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset}px`;
|
||||
this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset - paddingTop}px`;
|
||||
}
|
||||
|
||||
this.submenuDisposables.push(addDisposableListener(this.submenuContainer, EventType.KEY_UP, e => {
|
||||
@@ -633,11 +688,14 @@ class SubmenuActionItem extends MenuActionItem {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
this.parentData.parent.focus();
|
||||
this.parentData.submenu.dispose();
|
||||
this.parentData.submenu = null;
|
||||
|
||||
if (this.parentData.submenu) {
|
||||
this.parentData.submenu.dispose();
|
||||
this.parentData.submenu = undefined;
|
||||
}
|
||||
|
||||
this.submenuDisposables = dispose(this.submenuDisposables);
|
||||
this.submenuContainer = null;
|
||||
this.submenuContainer = undefined;
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -651,11 +709,14 @@ class SubmenuActionItem extends MenuActionItem {
|
||||
|
||||
this.submenuDisposables.push(this.parentData.submenu.onDidCancel(() => {
|
||||
this.parentData.parent.focus();
|
||||
this.parentData.submenu.dispose();
|
||||
this.parentData.submenu = null;
|
||||
|
||||
if (this.parentData.submenu) {
|
||||
this.parentData.submenu.dispose();
|
||||
this.parentData.submenu = undefined;
|
||||
}
|
||||
|
||||
this.submenuDisposables = dispose(this.submenuDisposables);
|
||||
this.submenuContainer = null;
|
||||
this.submenuContainer = undefined;
|
||||
}));
|
||||
|
||||
this.parentData.submenu.focus(selectFirstItem);
|
||||
@@ -673,7 +734,7 @@ class SubmenuActionItem extends MenuActionItem {
|
||||
return;
|
||||
}
|
||||
|
||||
const isSelected = hasClass(this.element, 'focused');
|
||||
const isSelected = this.element && hasClass(this.element, 'focused');
|
||||
const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor;
|
||||
|
||||
this.submenuIndicator.style.backgroundColor = fgColor ? `${fgColor}` : null;
|
||||
@@ -695,7 +756,7 @@ class SubmenuActionItem extends MenuActionItem {
|
||||
|
||||
if (this.submenuContainer) {
|
||||
this.submenuDisposables = dispose(this.submenuDisposables);
|
||||
this.submenuContainer = null;
|
||||
this.submenuContainer = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,9 +59,9 @@ export class MenuBar extends Disposable {
|
||||
index: number;
|
||||
holder?: HTMLElement;
|
||||
widget?: Menu;
|
||||
};
|
||||
} | undefined;
|
||||
|
||||
private focusToReturn: HTMLElement;
|
||||
private focusToReturn: HTMLElement | undefined;
|
||||
private menuUpdater: RunOnceScheduler;
|
||||
|
||||
// Input-related
|
||||
@@ -80,7 +80,7 @@ export class MenuBar extends Disposable {
|
||||
|
||||
private numMenusShown: number;
|
||||
private menuStyle: IMenuStyles;
|
||||
private overflowLayoutScheduled: IDisposable;
|
||||
private overflowLayoutScheduled: IDisposable | null;
|
||||
|
||||
constructor(private container: HTMLElement, private options: IMenuBarOptions = {}) {
|
||||
super();
|
||||
@@ -118,7 +118,7 @@ export class MenuBar extends Disposable {
|
||||
} else if (event.equals(KeyCode.Escape) && this.isFocused && !this.isOpen) {
|
||||
this.setUnfocusedState();
|
||||
} else if (!this.isOpen && !event.ctrlKey && this.options.enableMnemonics && this.mnemonicsInUse && this.mnemonics.has(key)) {
|
||||
const menuIndex = this.mnemonics.get(key);
|
||||
const menuIndex = this.mnemonics.get(key)!;
|
||||
this.onMenuTriggered(menuIndex, false);
|
||||
} else {
|
||||
eventHandled = false;
|
||||
@@ -152,7 +152,7 @@ export class MenuBar extends Disposable {
|
||||
|
||||
if (event.relatedTarget) {
|
||||
if (!this.container.contains(event.relatedTarget as HTMLElement)) {
|
||||
this.focusToReturn = null;
|
||||
this.focusToReturn = undefined;
|
||||
this.setUnfocusedState();
|
||||
}
|
||||
}
|
||||
@@ -171,7 +171,7 @@ export class MenuBar extends Disposable {
|
||||
this.mnemonicsInUse = true;
|
||||
this.updateMnemonicVisibility(true);
|
||||
|
||||
const menuIndex = this.mnemonics.get(key);
|
||||
const menuIndex = this.mnemonics.get(key)!;
|
||||
this.onMenuTriggered(menuIndex, false);
|
||||
}));
|
||||
|
||||
@@ -223,7 +223,7 @@ export class MenuBar extends Disposable {
|
||||
Gesture.addTarget(buttonElement);
|
||||
this._register(DOM.addDisposableListener(buttonElement, EventType.Tap, (e: GestureEvent) => {
|
||||
// Ignore this touch if the menu is touched
|
||||
if (this.isOpen && this.focusedMenu.holder && DOM.isAncestor(e.initialTarget as HTMLElement, this.focusedMenu.holder)) {
|
||||
if (this.isOpen && this.focusedMenu && this.focusedMenu.holder && DOM.isAncestor(e.initialTarget as HTMLElement, this.focusedMenu.holder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ export class MenuBar extends Disposable {
|
||||
Gesture.addTarget(buttonElement);
|
||||
this._register(DOM.addDisposableListener(buttonElement, EventType.Tap, (e: GestureEvent) => {
|
||||
// Ignore this touch if the menu is touched
|
||||
if (this.isOpen && this.focusedMenu.holder && DOM.isAncestor(e.initialTarget as HTMLElement, this.focusedMenu.holder)) {
|
||||
if (this.isOpen && this.focusedMenu && this.focusedMenu.holder && DOM.isAncestor(e.initialTarget as HTMLElement, this.focusedMenu.holder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -377,7 +377,9 @@ export class MenuBar extends Disposable {
|
||||
DOM.removeNode(this.overflowMenu.titleElement);
|
||||
DOM.removeNode(this.overflowMenu.buttonElement);
|
||||
|
||||
this.overflowLayoutScheduled = dispose(this.overflowLayoutScheduled);
|
||||
if (this.overflowLayoutScheduled) {
|
||||
this.overflowLayoutScheduled = dispose(this.overflowLayoutScheduled);
|
||||
}
|
||||
}
|
||||
|
||||
blur(): void {
|
||||
@@ -439,7 +441,7 @@ export class MenuBar extends Disposable {
|
||||
|
||||
this.overflowMenu.actions = [];
|
||||
for (let idx = this.numMenusShown; idx < this.menuCache.length; idx++) {
|
||||
this.overflowMenu.actions.push(new SubmenuAction(this.menuCache[idx].label, this.menuCache[idx].actions));
|
||||
this.overflowMenu.actions.push(new SubmenuAction(this.menuCache[idx].label, this.menuCache[idx].actions || []));
|
||||
}
|
||||
|
||||
DOM.removeNode(this.overflowMenu.buttonElement);
|
||||
@@ -496,7 +498,7 @@ export class MenuBar extends Disposable {
|
||||
if (!this.overflowLayoutScheduled) {
|
||||
this.overflowLayoutScheduled = DOM.scheduleAtNextAnimationFrame(() => {
|
||||
this.updateOverflowAction();
|
||||
this.overflowLayoutScheduled = void 0;
|
||||
this.overflowLayoutScheduled = null;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -518,6 +520,8 @@ export class MenuBar extends Disposable {
|
||||
if (this.container.style.display !== 'flex') {
|
||||
this.container.style.display = 'flex';
|
||||
this._onVisibilityChange.fire(true);
|
||||
|
||||
this.updateOverflowAction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,11 +560,11 @@ export class MenuBar extends Disposable {
|
||||
}
|
||||
|
||||
if (isFocused) {
|
||||
this.focusedMenu = null;
|
||||
this.focusedMenu = undefined;
|
||||
|
||||
if (this.focusToReturn) {
|
||||
this.focusToReturn.focus();
|
||||
this.focusToReturn = null;
|
||||
this.focusToReturn = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,11 +588,11 @@ export class MenuBar extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
this.focusedMenu = null;
|
||||
this.focusedMenu = undefined;
|
||||
|
||||
if (this.focusToReturn) {
|
||||
this.focusToReturn.focus();
|
||||
this.focusToReturn = null;
|
||||
this.focusToReturn = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,7 +818,10 @@ export class MenuBar extends Disposable {
|
||||
}
|
||||
|
||||
if (this.focusedMenu.holder) {
|
||||
DOM.removeClass(this.focusedMenu.holder.parentElement, 'open');
|
||||
if (this.focusedMenu.holder.parentElement) {
|
||||
DOM.removeClass(this.focusedMenu.holder.parentElement, 'open');
|
||||
}
|
||||
|
||||
this.focusedMenu.holder.remove();
|
||||
}
|
||||
|
||||
@@ -829,6 +836,11 @@ export class MenuBar extends Disposable {
|
||||
private showCustomMenu(menuIndex: number, selectFirst = true): void {
|
||||
const actualMenuIndex = menuIndex >= this.numMenusShown ? MenuBar.OVERFLOW_INDEX : menuIndex;
|
||||
const customMenu = actualMenuIndex === MenuBar.OVERFLOW_INDEX ? this.overflowMenu : this.menuCache[actualMenuIndex];
|
||||
|
||||
if (!customMenu.actions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const menuHolder = $('div.menubar-menu-items-holder');
|
||||
|
||||
DOM.addClass(customMenu.buttonElement, 'open');
|
||||
@@ -851,12 +863,6 @@ export class MenuBar extends Disposable {
|
||||
this.focusState = MenubarState.FOCUSED;
|
||||
}));
|
||||
|
||||
this._register(menuWidget.onDidBlur(() => {
|
||||
setTimeout(() => {
|
||||
this.cleanupCustomMenu();
|
||||
}, 100);
|
||||
}));
|
||||
|
||||
if (actualMenuIndex !== menuIndex) {
|
||||
menuWidget.trigger(menuIndex - this.numMenusShown);
|
||||
} else {
|
||||
@@ -897,7 +903,7 @@ class ModifierKeyEmitter extends Emitter<IModifierKeyStatus> {
|
||||
ctrlKey: false
|
||||
};
|
||||
|
||||
this._subscriptions.push(domEvent(document.body, 'keydown')(e => {
|
||||
this._subscriptions.push(domEvent(document.body, 'keydown', true)(e => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
|
||||
if (e.altKey && !this._keyStatus.altKey) {
|
||||
@@ -920,7 +926,8 @@ class ModifierKeyEmitter extends Emitter<IModifierKeyStatus> {
|
||||
this.fire(this._keyStatus);
|
||||
}
|
||||
}));
|
||||
this._subscriptions.push(domEvent(document.body, 'keyup')(e => {
|
||||
|
||||
this._subscriptions.push(domEvent(document.body, 'keyup', true)(e => {
|
||||
if (!e.altKey && this._keyStatus.altKey) {
|
||||
this._keyStatus.lastKeyReleased = 'alt';
|
||||
} else if (!e.ctrlKey && this._keyStatus.ctrlKey) {
|
||||
@@ -943,11 +950,11 @@ class ModifierKeyEmitter extends Emitter<IModifierKeyStatus> {
|
||||
this.fire(this._keyStatus);
|
||||
}
|
||||
}));
|
||||
this._subscriptions.push(domEvent(document.body, 'mousedown')(e => {
|
||||
|
||||
this._subscriptions.push(domEvent(document.body, 'mousedown', true)(e => {
|
||||
this._keyStatus.lastKeyPressed = undefined;
|
||||
}));
|
||||
|
||||
|
||||
this._subscriptions.push(domEvent(window, 'blur')(e => {
|
||||
this._keyStatus.lastKeyPressed = undefined;
|
||||
this._keyStatus.lastKeyReleased = undefined;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
If you intend to install Octicons locally, install `octicons-local.ttf`. It should appear as “github-octicons” in your font list. It is specially designed not to conflict with GitHub's web fonts.
|
||||
@@ -1,140 +0,0 @@
|
||||
{
|
||||
"registrations": [
|
||||
{
|
||||
"component": {
|
||||
"type": "other",
|
||||
"other": {
|
||||
"name": "octicons-code",
|
||||
"version": "3.1.0",
|
||||
"downloadUrl": "https://registry.npmjs.org/octicons/-/octicons-3.1.0.tgz"
|
||||
}
|
||||
},
|
||||
"licenseDetail": [
|
||||
"The MIT License (MIT)",
|
||||
"",
|
||||
"(c) 2012-2015 GitHub",
|
||||
"",
|
||||
"Permission is hereby granted, free of charge, to any person obtaining a copy",
|
||||
"of this software and associated documentation files (the \"Software\"), to deal",
|
||||
"in the Software without restriction, including without limitation the rights",
|
||||
"to use, copy, modify, merge, publish, distribute, sublicense, and/or sell",
|
||||
"copies of the Software, and to permit persons to whom the Software is",
|
||||
"furnished to do so, subject to the following conditions:",
|
||||
"",
|
||||
"The above copyright notice and this permission notice shall be included in",
|
||||
"all copies or substantial portions of the Software.",
|
||||
"",
|
||||
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR",
|
||||
"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,",
|
||||
"FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE",
|
||||
"AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER",
|
||||
"LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,",
|
||||
"OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN",
|
||||
"THE SOFTWARE."
|
||||
],
|
||||
"license": "MIT",
|
||||
"version": "3.1.0"
|
||||
},
|
||||
{
|
||||
"component": {
|
||||
"type": "other",
|
||||
"other": {
|
||||
"name": "octicons-font",
|
||||
"version": "3.1.0",
|
||||
"downloadUrl": "https://registry.npmjs.org/octicons/-/octicons-3.1.0.tgz"
|
||||
}
|
||||
},
|
||||
"licenseDetail": [
|
||||
"(c) 2012-2015 GitHub",
|
||||
"",
|
||||
"SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007",
|
||||
"",
|
||||
"PREAMBLE",
|
||||
"The goals of the Open Font License (OFL) are to stimulate worldwide",
|
||||
"development of collaborative font projects, to support the font creation",
|
||||
"efforts of academic and linguistic communities, and to provide a free and",
|
||||
"open framework in which fonts may be shared and improved in partnership",
|
||||
"with others.",
|
||||
"",
|
||||
"The OFL allows the licensed fonts to be used, studied, modified and",
|
||||
"redistributed freely as long as they are not sold by themselves. The",
|
||||
"fonts, including any derivative works, can be bundled, embedded,",
|
||||
"redistributed and/or sold with any software provided that any reserved",
|
||||
"names are not used by derivative works. The fonts and derivatives,",
|
||||
"however, cannot be released under any other type of license. The",
|
||||
"requirement for fonts to remain under this license does not apply",
|
||||
"to any document created using the fonts or their derivatives.",
|
||||
"",
|
||||
"DEFINITIONS",
|
||||
"\"Font Software\" refers to the set of files released by the Copyright",
|
||||
"Holder(s) under this license and clearly marked as such. This may",
|
||||
"include source files, build scripts and documentation.",
|
||||
"",
|
||||
"\"Reserved Font Name\" refers to any names specified as such after the",
|
||||
"copyright statement(s).",
|
||||
"",
|
||||
"\"Original Version\" refers to the collection of Font Software components as",
|
||||
"distributed by the Copyright Holder(s).",
|
||||
"",
|
||||
"\"Modified Version\" refers to any derivative made by adding to, deleting,",
|
||||
"or substituting -- in part or in whole -- any of the components of the",
|
||||
"Original Version, by changing formats or by porting the Font Software to a",
|
||||
"new environment.",
|
||||
"",
|
||||
"\"Author\" refers to any designer, engineer, programmer, technical",
|
||||
"writer or other person who contributed to the Font Software.",
|
||||
"",
|
||||
"PERMISSION & CONDITIONS",
|
||||
"Permission is hereby granted, free of charge, to any person obtaining",
|
||||
"a copy of the Font Software, to use, study, copy, merge, embed, modify,",
|
||||
"redistribute, and sell modified and unmodified copies of the Font",
|
||||
"Software, subject to the following conditions:",
|
||||
"",
|
||||
"1) Neither the Font Software nor any of its individual components,",
|
||||
"in Original or Modified Versions, may be sold by itself.",
|
||||
"",
|
||||
"2) Original or Modified Versions of the Font Software may be bundled,",
|
||||
"redistributed and/or sold with any software, provided that each copy",
|
||||
"contains the above copyright notice and this license. These can be",
|
||||
"included either as stand-alone text files, human-readable headers or",
|
||||
"in the appropriate machine-readable metadata fields within text or",
|
||||
"binary files as long as those fields can be easily viewed by the user.",
|
||||
"",
|
||||
"3) No Modified Version of the Font Software may use the Reserved Font",
|
||||
"Name(s) unless explicit written permission is granted by the corresponding",
|
||||
"Copyright Holder. This restriction only applies to the primary font name as",
|
||||
"presented to the users.",
|
||||
"",
|
||||
"4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font",
|
||||
"Software shall not be used to promote, endorse or advertise any",
|
||||
"Modified Version, except to acknowledge the contribution(s) of the",
|
||||
"Copyright Holder(s) and the Author(s) or with their explicit written",
|
||||
"permission.",
|
||||
"",
|
||||
"5) The Font Software, modified or unmodified, in part or in whole,",
|
||||
"must be distributed entirely under this license, and must not be",
|
||||
"distributed under any other license. The requirement for fonts to",
|
||||
"remain under this license does not apply to any document created",
|
||||
"using the Font Software.",
|
||||
"",
|
||||
"TERMINATION",
|
||||
"This license becomes null and void if any of the above conditions are",
|
||||
"not met.",
|
||||
"",
|
||||
"DISCLAIMER",
|
||||
"THE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,",
|
||||
"EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF",
|
||||
"MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT",
|
||||
"OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE",
|
||||
"COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,",
|
||||
"INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL",
|
||||
"DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING",
|
||||
"FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM",
|
||||
"OTHER DEALINGS IN THE FONT SOFTWARE."
|
||||
],
|
||||
"license": "SIL OFL 1.1",
|
||||
"version": "3.1.0"
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
@@ -1,233 +1,243 @@
|
||||
/*! *****************************************************************************
|
||||
(c) 2012-2015 GitHub
|
||||
|
||||
When using the GitHub logos, be sure to follow the GitHub logo guidelines (https://github.com/logos)
|
||||
|
||||
Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL)
|
||||
Applies to all font files
|
||||
|
||||
Code License: MIT (http://choosealicense.com/licenses/mit/)
|
||||
Applies to all other files
|
||||
***************************************************************************** */
|
||||
|
||||
@font-face {
|
||||
font-family: 'octicons';
|
||||
src: url('octicons.eot?#iefix') format('embedded-opentype'),
|
||||
url('octicons.woff') format('woff'),
|
||||
url('octicons.ttf') format('truetype'),
|
||||
url('octicons.svg#octicons') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-family: "octicons";
|
||||
src: url("./octicons.ttf?4cd2299755e93a2430ba5703f4476584") format("truetype"),
|
||||
url("./octicons.svg?4cd2299755e93a2430ba5703f4476584#octicons") format("svg");
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
.octicon is optimized for 16px.
|
||||
.mega-octicon is optimized for 32px but can be used larger.
|
||||
|
||||
*/
|
||||
.octicon, .mega-octicon {
|
||||
font: normal normal normal 16px/1 octicons;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
font: normal normal normal 16px/1 octicons;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.mega-octicon { font-size: 32px; }
|
||||
|
||||
.octicon-alert:before { content: '\f02d'} /* */
|
||||
.octicon-arrow-down:before { content: '\f03f'} /* */
|
||||
.octicon-arrow-left:before { content: '\f040'} /* */
|
||||
.octicon-arrow-right:before { content: '\f03e'} /* */
|
||||
.octicon-arrow-small-down:before { content: '\f0a0'} /* */
|
||||
.octicon-arrow-small-left:before { content: '\f0a1'} /* */
|
||||
.octicon-arrow-small-right:before { content: '\f071'} /* */
|
||||
.octicon-arrow-small-up:before { content: '\f09f'} /* */
|
||||
.octicon-arrow-up:before { content: '\f03d'} /* */
|
||||
.octicon-microscope:before,
|
||||
.octicon-beaker:before { content: '\f0dd'} /* */
|
||||
.octicon-bell:before { content: '\f0de'} /* */
|
||||
.octicon-book:before { content: '\f007'} /* */
|
||||
.octicon-bookmark:before { content: '\f07b'} /* */
|
||||
.octicon-briefcase:before { content: '\f0d3'} /* */
|
||||
.octicon-broadcast:before { content: '\f048'} /* */
|
||||
.octicon-browser:before { content: '\f0c5'} /* */
|
||||
.octicon-bug:before { content: '\f091'} /* */
|
||||
.octicon-calendar:before { content: '\f068'} /* */
|
||||
.octicon-check:before { content: '\f03a'} /* */
|
||||
.octicon-checklist:before { content: '\f076'} /* */
|
||||
.octicon-chevron-down:before { content: '\f0a3'} /* */
|
||||
.octicon-chevron-left:before { content: '\f0a4'} /* */
|
||||
.octicon-chevron-right:before { content: '\f078'} /* */
|
||||
.octicon-chevron-up:before { content: '\f0a2'} /* */
|
||||
.octicon-circle-slash:before { content: '\f084'} /* */
|
||||
.octicon-circuit-board:before { content: '\f0d6'} /* */
|
||||
.octicon-clippy:before { content: '\f035'} /* */
|
||||
.octicon-clock:before { content: '\f046'} /* */
|
||||
.octicon-cloud-download:before { content: '\f00b'} /* */
|
||||
.octicon-cloud-upload:before { content: '\f00c'} /* */
|
||||
.octicon-code:before { content: '\f05f'} /* */
|
||||
.octicon-color-mode:before { content: '\f065'} /* */
|
||||
.octicon-comment-add:before,
|
||||
.octicon-comment:before { content: '\f02b'} /* */
|
||||
.octicon-comment-discussion:before { content: '\f04f'} /* */
|
||||
.octicon-credit-card:before { content: '\f045'} /* */
|
||||
.octicon-dash:before { content: '\f0ca'} /* */
|
||||
.octicon-dashboard:before { content: '\f07d'} /* */
|
||||
.octicon-database:before { content: '\f096'} /* */
|
||||
.octicon-clone:before,
|
||||
.octicon-desktop-download:before { content: '\f0dc'} /* */
|
||||
.octicon-device-camera:before { content: '\f056'} /* */
|
||||
.octicon-device-camera-video:before { content: '\f057'} /* */
|
||||
.octicon-device-desktop:before { content: '\f27c'} /* */
|
||||
.octicon-device-mobile:before { content: '\f038'} /* */
|
||||
.octicon-diff:before { content: '\f04d'} /* */
|
||||
.octicon-diff-added:before { content: '\f06b'} /* */
|
||||
.octicon-diff-ignored:before { content: '\f099'} /* */
|
||||
.octicon-diff-modified:before { content: '\f06d'} /* */
|
||||
.octicon-diff-removed:before { content: '\f06c'} /* */
|
||||
.octicon-diff-renamed:before { content: '\f06e'} /* */
|
||||
.octicon-ellipsis:before { content: '\f09a'} /* */
|
||||
.octicon-eye-unwatch:before,
|
||||
.octicon-eye-watch:before,
|
||||
.octicon-eye:before { content: '\f04e'} /* */
|
||||
.octicon-file-binary:before { content: '\f094'} /* */
|
||||
.octicon-file-code:before { content: '\f010'} /* */
|
||||
.octicon-file-directory:before { content: '\f016'} /* */
|
||||
.octicon-file-media:before { content: '\f012'} /* */
|
||||
.octicon-file-pdf:before { content: '\f014'} /* */
|
||||
.octicon-file-submodule:before { content: '\f017'} /* */
|
||||
.octicon-file-symlink-directory:before { content: '\f0b1'} /* */
|
||||
.octicon-file-symlink-file:before { content: '\f0b0'} /* */
|
||||
.octicon-file-text:before { content: '\f011'} /* */
|
||||
.octicon-file-zip:before { content: '\f013'} /* */
|
||||
.octicon-flame:before { content: '\f0d2'} /* */
|
||||
.octicon-fold:before { content: '\f0cc'} /* */
|
||||
.octicon-gear:before { content: '\f02f'} /* */
|
||||
.octicon-gift:before { content: '\f042'} /* */
|
||||
.octicon-gist:before { content: '\f00e'} /* */
|
||||
.octicon-gist-secret:before { content: '\f08c'} /* */
|
||||
.octicon-git-branch-create:before,
|
||||
.octicon-git-branch-delete:before,
|
||||
.octicon-git-branch:before { content: '\f020'} /* */
|
||||
.octicon-git-commit:before { content: '\f01f'} /* */
|
||||
.octicon-git-compare:before { content: '\f0ac'} /* */
|
||||
.octicon-git-merge:before { content: '\f023'} /* */
|
||||
.octicon-git-pull-request-abandoned:before,
|
||||
.octicon-git-pull-request:before { content: '\f009'} /* */
|
||||
.octicon-globe:before { content: '\f0b6'} /* */
|
||||
.octicon-graph:before { content: '\f043'} /* */
|
||||
.octicon-heart:before { content: '\2665'} /* ♥ */
|
||||
.octicon-history:before { content: '\f07e'} /* */
|
||||
.octicon-home:before { content: '\f08d'} /* */
|
||||
.octicon-horizontal-rule:before { content: '\f070'} /* */
|
||||
.octicon-hubot:before { content: '\f09d'} /* */
|
||||
.octicon-inbox:before { content: '\f0cf'} /* */
|
||||
.octicon-info:before { content: '\f059'} /* */
|
||||
.octicon-issue-closed:before { content: '\f028'} /* */
|
||||
.octicon-issue-opened:before { content: '\f026'} /* */
|
||||
.octicon-issue-reopened:before { content: '\f027'} /* */
|
||||
.octicon-jersey:before { content: '\f019'} /* */
|
||||
.octicon-key:before { content: '\f049'} /* */
|
||||
.octicon-keyboard:before { content: '\f00d'} /* */
|
||||
.octicon-law:before { content: '\f0d8'} /* */
|
||||
.octicon-light-bulb:before { content: '\f000'} /* */
|
||||
.octicon-link:before { content: '\f05c'} /* */
|
||||
.octicon-link-external:before { content: '\f07f'} /* */
|
||||
.octicon-list-ordered:before { content: '\f062'} /* */
|
||||
.octicon-list-unordered:before { content: '\f061'} /* */
|
||||
.octicon-location:before { content: '\f060'} /* */
|
||||
.octicon-gist-private:before,
|
||||
.octicon-mirror-private:before,
|
||||
.octicon-git-fork-private:before,
|
||||
.octicon-lock:before { content: '\f06a'} /* */
|
||||
.octicon-logo-github:before { content: '\f092'} /* */
|
||||
.octicon-mail:before { content: '\f03b'} /* */
|
||||
.octicon-mail-read:before { content: '\f03c'} /* */
|
||||
.octicon-mail-reply:before { content: '\f051'} /* */
|
||||
.octicon-mark-github:before { content: '\f00a'} /* */
|
||||
.octicon-markdown:before { content: '\f0c9'} /* */
|
||||
.octicon-megaphone:before { content: '\f077'} /* */
|
||||
.octicon-mention:before { content: '\f0be'} /* */
|
||||
.octicon-milestone:before { content: '\f075'} /* */
|
||||
.octicon-mirror-public:before,
|
||||
.octicon-mirror:before { content: '\f024'} /* */
|
||||
.octicon-mortar-board:before { content: '\f0d7'} /* */
|
||||
.octicon-mute:before { content: '\f080'} /* */
|
||||
.octicon-no-newline:before { content: '\f09c'} /* */
|
||||
.octicon-octoface:before { content: '\f008'} /* */
|
||||
.octicon-organization:before { content: '\f037'} /* */
|
||||
.octicon-package:before { content: '\f0c4'} /* */
|
||||
.octicon-paintcan:before { content: '\f0d1'} /* */
|
||||
.octicon-pencil:before { content: '\f058'} /* */
|
||||
.octicon-person-add:before,
|
||||
.octicon-person-follow:before,
|
||||
.octicon-person:before { content: '\f018'} /* */
|
||||
.octicon-pin:before { content: '\f041'} /* */
|
||||
.octicon-plug:before { content: '\f0d4'} /* */
|
||||
.octicon-repo-create:before,
|
||||
.octicon-gist-new:before,
|
||||
.octicon-file-directory-create:before,
|
||||
.octicon-file-add:before,
|
||||
.octicon-plus:before { content: '\f05d'} /* */
|
||||
.octicon-primitive-dot:before { content: '\f052'} /* */
|
||||
.octicon-primitive-square:before { content: '\f053'} /* */
|
||||
.octicon-pulse:before { content: '\f085'} /* */
|
||||
.octicon-question:before { content: '\f02c'} /* */
|
||||
.octicon-quote:before { content: '\f063'} /* */
|
||||
.octicon-radio-tower:before { content: '\f030'} /* */
|
||||
.octicon-repo-delete:before,
|
||||
.octicon-repo:before { content: '\f001'} /* */
|
||||
.octicon-repo-clone:before { content: '\f04c'} /* */
|
||||
.octicon-repo-force-push:before { content: '\f04a'} /* */
|
||||
.octicon-gist-fork:before,
|
||||
.octicon-repo-forked:before { content: '\f002'} /* */
|
||||
.octicon-repo-pull:before { content: '\f006'} /* */
|
||||
.octicon-repo-push:before { content: '\f005'} /* */
|
||||
.octicon-rocket:before { content: '\f033'} /* */
|
||||
.octicon-rss:before { content: '\f034'} /* */
|
||||
.octicon-ruby:before { content: '\f047'} /* */
|
||||
.octicon-screen-full:before { content: '\f066'} /* */
|
||||
.octicon-screen-normal:before { content: '\f067'} /* */
|
||||
.octicon-search-save:before,
|
||||
.octicon-search:before { content: '\f02e'} /* */
|
||||
.octicon-server:before { content: '\f097'} /* */
|
||||
.octicon-settings:before { content: '\f07c'} /* */
|
||||
.octicon-shield:before { content: '\f0e1'} /* */
|
||||
.octicon-log-in:before,
|
||||
.octicon-sign-in:before { content: '\f036'} /* */
|
||||
.octicon-log-out:before,
|
||||
.octicon-sign-out:before { content: '\f032'} /* */
|
||||
.octicon-squirrel:before { content: '\f0b2'} /* */
|
||||
.octicon-star-add:before,
|
||||
.octicon-star-delete:before,
|
||||
.octicon-star:before { content: '\f02a'} /* */
|
||||
.octicon-stop:before { content: '\f08f'} /* */
|
||||
.octicon-repo-sync:before,
|
||||
.octicon-sync:before { content: '\f087'} /* */
|
||||
.octicon-tag-remove:before,
|
||||
.octicon-tag-add:before,
|
||||
.octicon-tag:before { content: '\f015'} /* */
|
||||
.octicon-telescope:before { content: '\f088'} /* */
|
||||
.octicon-terminal:before { content: '\f0c8'} /* */
|
||||
.octicon-three-bars:before { content: '\f05e'} /* */
|
||||
.octicon-thumbsdown:before { content: '\f0db'} /* */
|
||||
.octicon-thumbsup:before { content: '\f0da'} /* */
|
||||
.octicon-tools:before { content: '\f031'} /* */
|
||||
.octicon-trashcan:before { content: '\f0d0'} /* */
|
||||
.octicon-triangle-down:before { content: '\f05b'} /* */
|
||||
.octicon-triangle-left:before { content: '\f044'} /* */
|
||||
.octicon-triangle-right:before { content: '\f05a'} /* */
|
||||
.octicon-triangle-up:before { content: '\f0aa'} /* */
|
||||
.octicon-unfold:before { content: '\f039'} /* */
|
||||
.octicon-unmute:before { content: '\f0ba'} /* */
|
||||
.octicon-versions:before { content: '\f064'} /* */
|
||||
.octicon-watch:before { content: '\f0e0'} /* */
|
||||
.octicon-remove-close:before,
|
||||
.octicon-x:before { content: '\f081'} /* */
|
||||
.octicon-zap:before { content: '\26A1'} /* ⚡ */
|
||||
|
||||
|
||||
.octicon-alert:before { content: "\f02d" }
|
||||
.octicon-arrow-down:before { content: "\f03f" }
|
||||
.octicon-arrow-left:before { content: "\f040" }
|
||||
.octicon-arrow-right:before { content: "\f03e" }
|
||||
.octicon-arrow-small-down:before { content: "\f0a0" }
|
||||
.octicon-arrow-small-left:before { content: "\f0a1" }
|
||||
.octicon-arrow-small-right:before { content: "\f071" }
|
||||
.octicon-arrow-small-up:before { content: "\f09f" }
|
||||
.octicon-arrow-up:before { content: "\f03d" }
|
||||
.octicon-beaker:before { content: "\f0dd" }
|
||||
.octicon-bell:before { content: "\f0de" }
|
||||
.octicon-bold:before { content: "\f282" }
|
||||
.octicon-book:before { content: "\f007" }
|
||||
.octicon-bookmark:before { content: "\f07b" }
|
||||
.octicon-briefcase:before { content: "\f0d3" }
|
||||
.octicon-broadcast:before { content: "\f048" }
|
||||
.octicon-browser:before { content: "\f0c5" }
|
||||
.octicon-bug:before { content: "\f091" }
|
||||
.octicon-calendar:before { content: "\f068" }
|
||||
.octicon-check:before { content: "\f03a" }
|
||||
.octicon-checklist:before { content: "\f076" }
|
||||
.octicon-chevron-down:before { content: "\f0a3" }
|
||||
.octicon-chevron-left:before { content: "\f0a4" }
|
||||
.octicon-chevron-right:before { content: "\f078" }
|
||||
.octicon-chevron-up:before { content: "\f0a2" }
|
||||
.octicon-circle-slash:before { content: "\f084" }
|
||||
.octicon-circuit-board:before { content: "\f0d6" }
|
||||
.octicon-clippy:before { content: "\f035" }
|
||||
.octicon-clock:before { content: "\f046" }
|
||||
.octicon-clone:before { content: "\f0dc" }
|
||||
.octicon-cloud-download:before { content: "\f00b" }
|
||||
.octicon-cloud-upload:before { content: "\f00c" }
|
||||
.octicon-code:before { content: "\f05f" }
|
||||
.octicon-color-mode:before { content: "\f065" }
|
||||
.octicon-comment-add:before { content: "\f02b" }
|
||||
.octicon-comment-discussion:before { content: "\f04f" }
|
||||
.octicon-comment:before { content: "\f02b" }
|
||||
.octicon-credit-card:before { content: "\f045" }
|
||||
.octicon-dash:before { content: "\f0ca" }
|
||||
.octicon-dashboard:before { content: "\f07d" }
|
||||
.octicon-database:before { content: "\f096" }
|
||||
.octicon-desktop-download:before { content: "\f0dc" }
|
||||
.octicon-device-camera-video:before { content: "\f057" }
|
||||
.octicon-device-camera:before { content: "\f056" }
|
||||
.octicon-device-desktop:before { content: "\f27c" }
|
||||
.octicon-device-mobile:before { content: "\f038" }
|
||||
.octicon-diff-added:before { content: "\f06b" }
|
||||
.octicon-diff-ignored:before { content: "\f099" }
|
||||
.octicon-diff-modified:before { content: "\f06d" }
|
||||
.octicon-diff-removed:before { content: "\f06c" }
|
||||
.octicon-diff-renamed:before { content: "\f06e" }
|
||||
.octicon-diff:before { content: "\f04d" }
|
||||
.octicon-ellipsis:before { content: "\f09a" }
|
||||
.octicon-eye-unwatch:before { content: "\f04e" }
|
||||
.octicon-eye-watch:before { content: "\f04e" }
|
||||
.octicon-eye:before { content: "\f04e" }
|
||||
.octicon-file-add:before { content: "\f05d" }
|
||||
.octicon-file-binary:before { content: "\f094" }
|
||||
.octicon-file-code:before { content: "\f010" }
|
||||
.octicon-file-directory-create:before { content: "\f05d" }
|
||||
.octicon-file-directory:before { content: "\f016" }
|
||||
.octicon-file-media:before { content: "\f012" }
|
||||
.octicon-file-pdf:before { content: "\f014" }
|
||||
.octicon-file-submodule:before { content: "\f017" }
|
||||
.octicon-file-symlink-directory:before { content: "\f0b1" }
|
||||
.octicon-file-symlink-file:before { content: "\f0b0" }
|
||||
.octicon-file-text:before { content: "\f283" }
|
||||
.octicon-file-zip:before { content: "\f013" }
|
||||
.octicon-file:before { content: "\f283" }
|
||||
.octicon-flame:before { content: "\f0d2" }
|
||||
.octicon-fold:before { content: "\f0cc" }
|
||||
.octicon-gear:before { content: "\f02f" }
|
||||
.octicon-gift:before { content: "\f042" }
|
||||
.octicon-gist-fork:before { content: "\f002" }
|
||||
.octicon-gist-new:before { content: "\f05d" }
|
||||
.octicon-gist-private:before { content: "\f06a" }
|
||||
.octicon-gist-secret:before { content: "\f08c" }
|
||||
.octicon-gist:before { content: "\f00e" }
|
||||
.octicon-git-branch-create:before { content: "\f020" }
|
||||
.octicon-git-branch-delete:before { content: "\f020" }
|
||||
.octicon-git-branch:before { content: "\f020" }
|
||||
.octicon-git-commit:before { content: "\f01f" }
|
||||
.octicon-git-compare:before { content: "\f0ac" }
|
||||
.octicon-git-fork-private:before { content: "\f06a" }
|
||||
.octicon-git-merge:before { content: "\f023" }
|
||||
.octicon-git-pull-request-abandoned:before { content: "\f009" }
|
||||
.octicon-git-pull-request:before { content: "\f009" }
|
||||
.octicon-globe:before { content: "\f0b6" }
|
||||
.octicon-grabber:before { content: "\f284" }
|
||||
.octicon-graph:before { content: "\f043" }
|
||||
.octicon-heart:before { content: "\2665" }
|
||||
.octicon-history:before { content: "\f07e" }
|
||||
.octicon-home:before { content: "\f08d" }
|
||||
.octicon-horizontal-rule:before { content: "\f070" }
|
||||
.octicon-hubot:before { content: "\f09d" }
|
||||
.octicon-inbox:before { content: "\f0cf" }
|
||||
.octicon-info:before { content: "\f059" }
|
||||
.octicon-issue-closed:before { content: "\f028" }
|
||||
.octicon-issue-opened:before { content: "\f026" }
|
||||
.octicon-issue-reopened:before { content: "\f027" }
|
||||
.octicon-italic:before { content: "\f285" }
|
||||
.octicon-jersey:before { content: "\f019" }
|
||||
.octicon-kebab-horizontal:before { content: "\f286" }
|
||||
.octicon-kebab-vertical:before { content: "\f287" }
|
||||
.octicon-key:before { content: "\f049" }
|
||||
.octicon-keyboard:before { content: "\f00d" }
|
||||
.octicon-law:before { content: "\f0d8" }
|
||||
.octicon-light-bulb:before { content: "\f000" }
|
||||
.octicon-link-external:before { content: "\f07f" }
|
||||
.octicon-link:before { content: "\f05c" }
|
||||
.octicon-list-ordered:before { content: "\f062" }
|
||||
.octicon-list-unordered:before { content: "\f061" }
|
||||
.octicon-location:before { content: "\f060" }
|
||||
.octicon-lock:before { content: "\f06a" }
|
||||
.octicon-log-in:before { content: "\f036" }
|
||||
.octicon-log-out:before { content: "\f032" }
|
||||
.octicon-logo-gist:before { content: "\f288" }
|
||||
.octicon-logo-github:before { content: "\f092" }
|
||||
.octicon-mail-read:before { content: "\f03c" }
|
||||
.octicon-mail-reply:before { content: "\f28c" }
|
||||
.octicon-mail:before { content: "\f03b" }
|
||||
.octicon-mark-github:before { content: "\f00a" }
|
||||
.octicon-markdown:before { content: "\f0c9" }
|
||||
.octicon-megaphone:before { content: "\f077" }
|
||||
.octicon-mention:before { content: "\f0be" }
|
||||
.octicon-microscope:before { content: "\f0dd" }
|
||||
.octicon-milestone:before { content: "\f075" }
|
||||
.octicon-mirror-private:before { content: "\f06a" }
|
||||
.octicon-mirror-public:before { content: "\f024" }
|
||||
.octicon-mirror:before { content: "\f024" }
|
||||
.octicon-mortar-board:before { content: "\f0d7" }
|
||||
.octicon-mute:before { content: "\f080" }
|
||||
.octicon-no-newline:before { content: "\f09c" }
|
||||
.octicon-note:before { content: "\f289" }
|
||||
.octicon-octoface:before { content: "\f008" }
|
||||
.octicon-organization:before { content: "\f037" }
|
||||
.octicon-organization-filled:before { content: "\26a2" }
|
||||
.octicon-organization-outline:before { content: "\f037" }
|
||||
.octicon-package:before { content: "\f0c4" }
|
||||
.octicon-paintcan:before { content: "\f0d1" }
|
||||
.octicon-pencil:before { content: "\f058" }
|
||||
.octicon-person-add:before { content: "\f018" }
|
||||
.octicon-person-follow:before { content: "\f018" }
|
||||
.octicon-person:before { content: "\f018" }
|
||||
.octicon-person-filled:before { content: "\26a3" }
|
||||
.octicon-person-outline:before { content: "\f018" }
|
||||
.octicon-pin:before { content: "\f041" }
|
||||
.octicon-plug:before { content: "\f0d4" }
|
||||
.octicon-plus-small:before { content: "\f28a" }
|
||||
.octicon-plus:before { content: "\f05d" }
|
||||
.octicon-primitive-dot:before { content: "\f052" }
|
||||
.octicon-primitive-square:before { content: "\f053" }
|
||||
.octicon-project:before { content: "\f28b" }
|
||||
.octicon-pulse:before { content: "\f085" }
|
||||
.octicon-question:before { content: "\f02c" }
|
||||
.octicon-quote:before { content: "\f063" }
|
||||
.octicon-radio-tower:before { content: "\f030" }
|
||||
.octicon-remove-close:before { content: "\f081" }
|
||||
.octicon-reply:before { content: "\f28c" }
|
||||
.octicon-repo-clone:before { content: "\f04c" }
|
||||
.octicon-repo-create:before { content: "\f05d" }
|
||||
.octicon-repo-delete:before { content: "\f001" }
|
||||
.octicon-repo-force-push:before { content: "\f04a" }
|
||||
.octicon-repo-forked:before { content: "\f002" }
|
||||
.octicon-repo-pull:before { content: "\f006" }
|
||||
.octicon-repo-push:before { content: "\f005" }
|
||||
.octicon-repo-sync:before { content: "\f087" }
|
||||
.octicon-repo:before { content: "\f001" }
|
||||
.octicon-report:before { content: "\f28d" }
|
||||
.octicon-rocket:before { content: "\f033" }
|
||||
.octicon-rss:before { content: "\f034" }
|
||||
.octicon-ruby:before { content: "\f047" }
|
||||
.octicon-screen-full:before { content: "\f066" }
|
||||
.octicon-screen-normal:before { content: "\f067" }
|
||||
.octicon-search-save:before { content: "\f02e" }
|
||||
.octicon-search:before { content: "\f02e" }
|
||||
.octicon-server:before { content: "\f097" }
|
||||
.octicon-settings:before { content: "\f07c" }
|
||||
.octicon-shield:before { content: "\f0e1" }
|
||||
.octicon-sign-in:before { content: "\f036" }
|
||||
.octicon-sign-out:before { content: "\f032" }
|
||||
.octicon-smiley:before { content: "\f27d" }
|
||||
.octicon-squirrel:before { content: "\f0b2" }
|
||||
.octicon-star-add:before { content: "\f02a" }
|
||||
.octicon-star-delete:before { content: "\f02a" }
|
||||
.octicon-star:before { content: "\f02a" }
|
||||
.octicon-stop:before { content: "\f08f" }
|
||||
.octicon-sync:before { content: "\f087" }
|
||||
.octicon-tag-add:before { content: "\f015" }
|
||||
.octicon-tag-remove:before { content: "\f015" }
|
||||
.octicon-tag:before { content: "\f015" }
|
||||
.octicon-tasklist:before { content: "\f27e" }
|
||||
.octicon-telescope:before { content: "\f088" }
|
||||
.octicon-terminal:before { content: "\f0c8" }
|
||||
.octicon-text-size:before { content: "\f27f" }
|
||||
.octicon-three-bars:before { content: "\f05e" }
|
||||
.octicon-thumbsdown:before { content: "\f0db" }
|
||||
.octicon-thumbsup:before { content: "\f0da" }
|
||||
.octicon-tools:before { content: "\f031" }
|
||||
.octicon-trashcan:before { content: "\f0d0" }
|
||||
.octicon-triangle-down:before { content: "\f05b" }
|
||||
.octicon-triangle-left:before { content: "\f044" }
|
||||
.octicon-triangle-right:before { content: "\f05a" }
|
||||
.octicon-triangle-up:before { content: "\f0aa" }
|
||||
.octicon-unfold:before { content: "\f039" }
|
||||
.octicon-unmute:before { content: "\f0ba" }
|
||||
.octicon-unverified:before { content: "\f280" }
|
||||
.octicon-verified:before { content: "\f281" }
|
||||
.octicon-versions:before { content: "\f064" }
|
||||
.octicon-watch:before { content: "\f0e0" }
|
||||
.octicon-x:before { content: "\f081" }
|
||||
.octicon-zap:before { content: "\26a1" }
|
||||
.octicon-archive:before { content: "\f101" }
|
||||
.octicon-arrow-both:before { content: "\f102" }
|
||||
.octicon-eye-closed:before { content: "\f103" }
|
||||
.octicon-fold-down:before { content: "\f104" }
|
||||
.octicon-fold-up:before { content: "\f105" }
|
||||
.octicon-github-action:before { content: "\f106" }
|
||||
.octicon-play:before { content: "\f107" }
|
||||
.octicon-request-changes:before { content: "\f108" }
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
@octicons-font-path: ".";
|
||||
@octicons-version: "396334ee3da78f4302d25c758ae3e3ce5dc3c97d";
|
||||
|
||||
@font-face {
|
||||
font-family: 'octicons';
|
||||
src: ~"url('@{octicons-font-path}/octicons.eot?#iefix&v=@{octicons-version}') format('embedded-opentype')",
|
||||
~"url('@{octicons-font-path}/octicons.woff?v=@{octicons-version}') format('woff')",
|
||||
~"url('@{octicons-font-path}/octicons.ttf?v=@{octicons-version}') format('truetype')",
|
||||
~"url('@{octicons-font-path}/octicons.svg?v=@{octicons-version}#octicons') format('svg')";
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
// .octicon is optimized for 16px.
|
||||
// .mega-octicon is optimized for 32px but can be used larger.
|
||||
.octicon, .mega-octicon {
|
||||
font: normal normal normal 16px/1 octicons;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.mega-octicon { font-size: 32px; }
|
||||
|
||||
.octicon-alert:before { content: '\f02d'} /* */
|
||||
.octicon-arrow-down:before { content: '\f03f'} /* */
|
||||
.octicon-arrow-left:before { content: '\f040'} /* */
|
||||
.octicon-arrow-right:before { content: '\f03e'} /* */
|
||||
.octicon-arrow-small-down:before { content: '\f0a0'} /* */
|
||||
.octicon-arrow-small-left:before { content: '\f0a1'} /* */
|
||||
.octicon-arrow-small-right:before { content: '\f071'} /* */
|
||||
.octicon-arrow-small-up:before { content: '\f09f'} /* */
|
||||
.octicon-arrow-up:before { content: '\f03d'} /* */
|
||||
.octicon-microscope:before,
|
||||
.octicon-beaker:before { content: '\f0dd'} /* */
|
||||
.octicon-bell:before { content: '\f0de'} /* */
|
||||
.octicon-book:before { content: '\f007'} /* */
|
||||
.octicon-bookmark:before { content: '\f07b'} /* */
|
||||
.octicon-briefcase:before { content: '\f0d3'} /* */
|
||||
.octicon-broadcast:before { content: '\f048'} /* */
|
||||
.octicon-browser:before { content: '\f0c5'} /* */
|
||||
.octicon-bug:before { content: '\f091'} /* */
|
||||
.octicon-calendar:before { content: '\f068'} /* */
|
||||
.octicon-check:before { content: '\f03a'} /* */
|
||||
.octicon-checklist:before { content: '\f076'} /* */
|
||||
.octicon-chevron-down:before { content: '\f0a3'} /* */
|
||||
.octicon-chevron-left:before { content: '\f0a4'} /* */
|
||||
.octicon-chevron-right:before { content: '\f078'} /* */
|
||||
.octicon-chevron-up:before { content: '\f0a2'} /* */
|
||||
.octicon-circle-slash:before { content: '\f084'} /* */
|
||||
.octicon-circuit-board:before { content: '\f0d6'} /* */
|
||||
.octicon-clippy:before { content: '\f035'} /* */
|
||||
.octicon-clock:before { content: '\f046'} /* */
|
||||
.octicon-cloud-download:before { content: '\f00b'} /* */
|
||||
.octicon-cloud-upload:before { content: '\f00c'} /* */
|
||||
.octicon-code:before { content: '\f05f'} /* */
|
||||
.octicon-color-mode:before { content: '\f065'} /* */
|
||||
.octicon-comment-add:before,
|
||||
.octicon-comment:before { content: '\f02b'} /* */
|
||||
.octicon-comment-discussion:before { content: '\f04f'} /* */
|
||||
.octicon-credit-card:before { content: '\f045'} /* */
|
||||
.octicon-dash:before { content: '\f0ca'} /* */
|
||||
.octicon-dashboard:before { content: '\f07d'} /* */
|
||||
.octicon-database:before { content: '\f096'} /* */
|
||||
.octicon-clone:before,
|
||||
.octicon-desktop-download:before { content: '\f0dc'} /* */
|
||||
.octicon-device-camera:before { content: '\f056'} /* */
|
||||
.octicon-device-camera-video:before { content: '\f057'} /* */
|
||||
.octicon-device-desktop:before { content: '\f27c'} /* */
|
||||
.octicon-device-mobile:before { content: '\f038'} /* */
|
||||
.octicon-diff:before { content: '\f04d'} /* */
|
||||
.octicon-diff-added:before { content: '\f06b'} /* */
|
||||
.octicon-diff-ignored:before { content: '\f099'} /* */
|
||||
.octicon-diff-modified:before { content: '\f06d'} /* */
|
||||
.octicon-diff-removed:before { content: '\f06c'} /* */
|
||||
.octicon-diff-renamed:before { content: '\f06e'} /* */
|
||||
.octicon-ellipsis:before { content: '\f09a'} /* */
|
||||
.octicon-eye-unwatch:before,
|
||||
.octicon-eye-watch:before,
|
||||
.octicon-eye:before { content: '\f04e'} /* */
|
||||
.octicon-file-binary:before { content: '\f094'} /* */
|
||||
.octicon-file-code:before { content: '\f010'} /* */
|
||||
.octicon-file-directory:before { content: '\f016'} /* */
|
||||
.octicon-file-media:before { content: '\f012'} /* */
|
||||
.octicon-file-pdf:before { content: '\f014'} /* */
|
||||
.octicon-file-submodule:before { content: '\f017'} /* */
|
||||
.octicon-file-symlink-directory:before { content: '\f0b1'} /* */
|
||||
.octicon-file-symlink-file:before { content: '\f0b0'} /* */
|
||||
.octicon-file-text:before { content: '\f011'} /* */
|
||||
.octicon-file-zip:before { content: '\f013'} /* */
|
||||
.octicon-flame:before { content: '\f0d2'} /* */
|
||||
.octicon-fold:before { content: '\f0cc'} /* */
|
||||
.octicon-gear:before { content: '\f02f'} /* */
|
||||
.octicon-gift:before { content: '\f042'} /* */
|
||||
.octicon-gist:before { content: '\f00e'} /* */
|
||||
.octicon-gist-secret:before { content: '\f08c'} /* */
|
||||
.octicon-git-branch-create:before,
|
||||
.octicon-git-branch-delete:before,
|
||||
.octicon-git-branch:before { content: '\f020'} /* */
|
||||
.octicon-git-commit:before { content: '\f01f'} /* */
|
||||
.octicon-git-compare:before { content: '\f0ac'} /* */
|
||||
.octicon-git-merge:before { content: '\f023'} /* */
|
||||
.octicon-git-pull-request-abandoned:before,
|
||||
.octicon-git-pull-request:before { content: '\f009'} /* */
|
||||
.octicon-globe:before { content: '\f0b6'} /* */
|
||||
.octicon-graph:before { content: '\f043'} /* */
|
||||
.octicon-heart:before { content: '\2665'} /* ♥ */
|
||||
.octicon-history:before { content: '\f07e'} /* */
|
||||
.octicon-home:before { content: '\f08d'} /* */
|
||||
.octicon-horizontal-rule:before { content: '\f070'} /* */
|
||||
.octicon-hubot:before { content: '\f09d'} /* */
|
||||
.octicon-inbox:before { content: '\f0cf'} /* */
|
||||
.octicon-info:before { content: '\f059'} /* */
|
||||
.octicon-issue-closed:before { content: '\f028'} /* */
|
||||
.octicon-issue-opened:before { content: '\f026'} /* */
|
||||
.octicon-issue-reopened:before { content: '\f027'} /* */
|
||||
.octicon-jersey:before { content: '\f019'} /* */
|
||||
.octicon-key:before { content: '\f049'} /* */
|
||||
.octicon-keyboard:before { content: '\f00d'} /* */
|
||||
.octicon-law:before { content: '\f0d8'} /* */
|
||||
.octicon-light-bulb:before { content: '\f000'} /* */
|
||||
.octicon-link:before { content: '\f05c'} /* */
|
||||
.octicon-link-external:before { content: '\f07f'} /* */
|
||||
.octicon-list-ordered:before { content: '\f062'} /* */
|
||||
.octicon-list-unordered:before { content: '\f061'} /* */
|
||||
.octicon-location:before { content: '\f060'} /* */
|
||||
.octicon-gist-private:before,
|
||||
.octicon-mirror-private:before,
|
||||
.octicon-git-fork-private:before,
|
||||
.octicon-lock:before { content: '\f06a'} /* */
|
||||
.octicon-logo-github:before { content: '\f092'} /* */
|
||||
.octicon-mail:before { content: '\f03b'} /* */
|
||||
.octicon-mail-read:before { content: '\f03c'} /* */
|
||||
.octicon-mail-reply:before { content: '\f051'} /* */
|
||||
.octicon-mark-github:before { content: '\f00a'} /* */
|
||||
.octicon-markdown:before { content: '\f0c9'} /* */
|
||||
.octicon-megaphone:before { content: '\f077'} /* */
|
||||
.octicon-mention:before { content: '\f0be'} /* */
|
||||
.octicon-milestone:before { content: '\f075'} /* */
|
||||
.octicon-mirror-public:before,
|
||||
.octicon-mirror:before { content: '\f024'} /* */
|
||||
.octicon-mortar-board:before { content: '\f0d7'} /* */
|
||||
.octicon-mute:before { content: '\f080'} /* */
|
||||
.octicon-no-newline:before { content: '\f09c'} /* */
|
||||
.octicon-octoface:before { content: '\f008'} /* */
|
||||
.octicon-organization:before { content: '\f037'} /* */
|
||||
.octicon-package:before { content: '\f0c4'} /* */
|
||||
.octicon-paintcan:before { content: '\f0d1'} /* */
|
||||
.octicon-pencil:before { content: '\f058'} /* */
|
||||
.octicon-person-add:before,
|
||||
.octicon-person-follow:before,
|
||||
.octicon-person:before { content: '\f018'} /* */
|
||||
.octicon-pin:before { content: '\f041'} /* */
|
||||
.octicon-plug:before { content: '\f0d4'} /* */
|
||||
.octicon-repo-create:before,
|
||||
.octicon-gist-new:before,
|
||||
.octicon-file-directory-create:before,
|
||||
.octicon-file-add:before,
|
||||
.octicon-plus:before { content: '\f05d'} /* */
|
||||
.octicon-primitive-dot:before { content: '\f052'} /* */
|
||||
.octicon-primitive-square:before { content: '\f053'} /* */
|
||||
.octicon-pulse:before { content: '\f085'} /* */
|
||||
.octicon-question:before { content: '\f02c'} /* */
|
||||
.octicon-quote:before { content: '\f063'} /* */
|
||||
.octicon-radio-tower:before { content: '\f030'} /* */
|
||||
.octicon-repo-delete:before,
|
||||
.octicon-repo:before { content: '\f001'} /* */
|
||||
.octicon-repo-clone:before { content: '\f04c'} /* */
|
||||
.octicon-repo-force-push:before { content: '\f04a'} /* */
|
||||
.octicon-gist-fork:before,
|
||||
.octicon-repo-forked:before { content: '\f002'} /* */
|
||||
.octicon-repo-pull:before { content: '\f006'} /* */
|
||||
.octicon-repo-push:before { content: '\f005'} /* */
|
||||
.octicon-rocket:before { content: '\f033'} /* */
|
||||
.octicon-rss:before { content: '\f034'} /* */
|
||||
.octicon-ruby:before { content: '\f047'} /* */
|
||||
.octicon-screen-full:before { content: '\f066'} /* */
|
||||
.octicon-screen-normal:before { content: '\f067'} /* */
|
||||
.octicon-search-save:before,
|
||||
.octicon-search:before { content: '\f02e'} /* */
|
||||
.octicon-server:before { content: '\f097'} /* */
|
||||
.octicon-settings:before { content: '\f07c'} /* */
|
||||
.octicon-shield:before { content: '\f0e1'} /* */
|
||||
.octicon-log-in:before,
|
||||
.octicon-sign-in:before { content: '\f036'} /* */
|
||||
.octicon-log-out:before,
|
||||
.octicon-sign-out:before { content: '\f032'} /* */
|
||||
.octicon-squirrel:before { content: '\f0b2'} /* */
|
||||
.octicon-star-add:before,
|
||||
.octicon-star-delete:before,
|
||||
.octicon-star:before { content: '\f02a'} /* */
|
||||
.octicon-stop:before { content: '\f08f'} /* */
|
||||
.octicon-repo-sync:before,
|
||||
.octicon-sync:before { content: '\f087'} /* */
|
||||
.octicon-tag-remove:before,
|
||||
.octicon-tag-add:before,
|
||||
.octicon-tag:before { content: '\f015'} /* */
|
||||
.octicon-telescope:before { content: '\f088'} /* */
|
||||
.octicon-terminal:before { content: '\f0c8'} /* */
|
||||
.octicon-three-bars:before { content: '\f05e'} /* */
|
||||
.octicon-thumbsdown:before { content: '\f0db'} /* */
|
||||
.octicon-thumbsup:before { content: '\f0da'} /* */
|
||||
.octicon-tools:before { content: '\f031'} /* */
|
||||
.octicon-trashcan:before { content: '\f0d0'} /* */
|
||||
.octicon-triangle-down:before { content: '\f05b'} /* */
|
||||
.octicon-triangle-left:before { content: '\f044'} /* */
|
||||
.octicon-triangle-right:before { content: '\f05a'} /* */
|
||||
.octicon-triangle-up:before { content: '\f0aa'} /* */
|
||||
.octicon-unfold:before { content: '\f039'} /* */
|
||||
.octicon-unmute:before { content: '\f0ba'} /* */
|
||||
.octicon-versions:before { content: '\f064'} /* */
|
||||
.octicon-watch:before { content: '\f0e0'} /* */
|
||||
.octicon-remove-close:before,
|
||||
.octicon-x:before { content: '\f081'} /* */
|
||||
.octicon-zap:before { content: '\26A1'} /* ⚡ */
|
||||
@@ -1,220 +0,0 @@
|
||||
$octicons-font-path: "." !default;
|
||||
$octicons-version: "396334ee3da78f4302d25c758ae3e3ce5dc3c97d";
|
||||
|
||||
@font-face {
|
||||
font-family: 'octicons';
|
||||
src: url('#{$octicons-font-path}/octicons.eot?#iefix&v=#{$octicons-version}') format('embedded-opentype'),
|
||||
url('#{$octicons-font-path}/octicons.woff?v=#{$octicons-version}') format('woff'),
|
||||
url('#{$octicons-font-path}/octicons.ttf?v=#{$octicons-version}') format('truetype'),
|
||||
url('#{$octicons-font-path}/octicons.svg?v=#{$octicons-version}#octicons') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
// .octicon is optimized for 16px.
|
||||
// .mega-octicon is optimized for 32px but can be used larger.
|
||||
.octicon, .mega-octicon {
|
||||
font: normal normal normal 16px/1 octicons;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.mega-octicon { font-size: 32px; }
|
||||
|
||||
.octicon-alert:before { content: '\f02d'} /* */
|
||||
.octicon-arrow-down:before { content: '\f03f'} /* */
|
||||
.octicon-arrow-left:before { content: '\f040'} /* */
|
||||
.octicon-arrow-right:before { content: '\f03e'} /* */
|
||||
.octicon-arrow-small-down:before { content: '\f0a0'} /* */
|
||||
.octicon-arrow-small-left:before { content: '\f0a1'} /* */
|
||||
.octicon-arrow-small-right:before { content: '\f071'} /* */
|
||||
.octicon-arrow-small-up:before { content: '\f09f'} /* */
|
||||
.octicon-arrow-up:before { content: '\f03d'} /* */
|
||||
.octicon-microscope:before,
|
||||
.octicon-beaker:before { content: '\f0dd'} /* */
|
||||
.octicon-bell:before { content: '\f0de'} /* */
|
||||
.octicon-book:before { content: '\f007'} /* */
|
||||
.octicon-bookmark:before { content: '\f07b'} /* */
|
||||
.octicon-briefcase:before { content: '\f0d3'} /* */
|
||||
.octicon-broadcast:before { content: '\f048'} /* */
|
||||
.octicon-browser:before { content: '\f0c5'} /* */
|
||||
.octicon-bug:before { content: '\f091'} /* */
|
||||
.octicon-calendar:before { content: '\f068'} /* */
|
||||
.octicon-check:before { content: '\f03a'} /* */
|
||||
.octicon-checklist:before { content: '\f076'} /* */
|
||||
.octicon-chevron-down:before { content: '\f0a3'} /* */
|
||||
.octicon-chevron-left:before { content: '\f0a4'} /* */
|
||||
.octicon-chevron-right:before { content: '\f078'} /* */
|
||||
.octicon-chevron-up:before { content: '\f0a2'} /* */
|
||||
.octicon-circle-slash:before { content: '\f084'} /* */
|
||||
.octicon-circuit-board:before { content: '\f0d6'} /* */
|
||||
.octicon-clippy:before { content: '\f035'} /* */
|
||||
.octicon-clock:before { content: '\f046'} /* */
|
||||
.octicon-cloud-download:before { content: '\f00b'} /* */
|
||||
.octicon-cloud-upload:before { content: '\f00c'} /* */
|
||||
.octicon-code:before { content: '\f05f'} /* */
|
||||
.octicon-color-mode:before { content: '\f065'} /* */
|
||||
.octicon-comment-add:before,
|
||||
.octicon-comment:before { content: '\f02b'} /* */
|
||||
.octicon-comment-discussion:before { content: '\f04f'} /* */
|
||||
.octicon-credit-card:before { content: '\f045'} /* */
|
||||
.octicon-dash:before { content: '\f0ca'} /* */
|
||||
.octicon-dashboard:before { content: '\f07d'} /* */
|
||||
.octicon-database:before { content: '\f096'} /* */
|
||||
.octicon-clone:before,
|
||||
.octicon-desktop-download:before { content: '\f0dc'} /* */
|
||||
.octicon-device-camera:before { content: '\f056'} /* */
|
||||
.octicon-device-camera-video:before { content: '\f057'} /* */
|
||||
.octicon-device-desktop:before { content: '\f27c'} /* */
|
||||
.octicon-device-mobile:before { content: '\f038'} /* */
|
||||
.octicon-diff:before { content: '\f04d'} /* */
|
||||
.octicon-diff-added:before { content: '\f06b'} /* */
|
||||
.octicon-diff-ignored:before { content: '\f099'} /* */
|
||||
.octicon-diff-modified:before { content: '\f06d'} /* */
|
||||
.octicon-diff-removed:before { content: '\f06c'} /* */
|
||||
.octicon-diff-renamed:before { content: '\f06e'} /* */
|
||||
.octicon-ellipsis:before { content: '\f09a'} /* */
|
||||
.octicon-eye-unwatch:before,
|
||||
.octicon-eye-watch:before,
|
||||
.octicon-eye:before { content: '\f04e'} /* */
|
||||
.octicon-file-binary:before { content: '\f094'} /* */
|
||||
.octicon-file-code:before { content: '\f010'} /* */
|
||||
.octicon-file-directory:before { content: '\f016'} /* */
|
||||
.octicon-file-media:before { content: '\f012'} /* */
|
||||
.octicon-file-pdf:before { content: '\f014'} /* */
|
||||
.octicon-file-submodule:before { content: '\f017'} /* */
|
||||
.octicon-file-symlink-directory:before { content: '\f0b1'} /* */
|
||||
.octicon-file-symlink-file:before { content: '\f0b0'} /* */
|
||||
.octicon-file-text:before { content: '\f011'} /* */
|
||||
.octicon-file-zip:before { content: '\f013'} /* */
|
||||
.octicon-flame:before { content: '\f0d2'} /* */
|
||||
.octicon-fold:before { content: '\f0cc'} /* */
|
||||
.octicon-gear:before { content: '\f02f'} /* */
|
||||
.octicon-gift:before { content: '\f042'} /* */
|
||||
.octicon-gist:before { content: '\f00e'} /* */
|
||||
.octicon-gist-secret:before { content: '\f08c'} /* */
|
||||
.octicon-git-branch-create:before,
|
||||
.octicon-git-branch-delete:before,
|
||||
.octicon-git-branch:before { content: '\f020'} /* */
|
||||
.octicon-git-commit:before { content: '\f01f'} /* */
|
||||
.octicon-git-compare:before { content: '\f0ac'} /* */
|
||||
.octicon-git-merge:before { content: '\f023'} /* */
|
||||
.octicon-git-pull-request-abandoned:before,
|
||||
.octicon-git-pull-request:before { content: '\f009'} /* */
|
||||
.octicon-globe:before { content: '\f0b6'} /* */
|
||||
.octicon-graph:before { content: '\f043'} /* */
|
||||
.octicon-heart:before { content: '\2665'} /* ♥ */
|
||||
.octicon-history:before { content: '\f07e'} /* */
|
||||
.octicon-home:before { content: '\f08d'} /* */
|
||||
.octicon-horizontal-rule:before { content: '\f070'} /* */
|
||||
.octicon-hubot:before { content: '\f09d'} /* */
|
||||
.octicon-inbox:before { content: '\f0cf'} /* */
|
||||
.octicon-info:before { content: '\f059'} /* */
|
||||
.octicon-issue-closed:before { content: '\f028'} /* */
|
||||
.octicon-issue-opened:before { content: '\f026'} /* */
|
||||
.octicon-issue-reopened:before { content: '\f027'} /* */
|
||||
.octicon-jersey:before { content: '\f019'} /* */
|
||||
.octicon-key:before { content: '\f049'} /* */
|
||||
.octicon-keyboard:before { content: '\f00d'} /* */
|
||||
.octicon-law:before { content: '\f0d8'} /* */
|
||||
.octicon-light-bulb:before { content: '\f000'} /* */
|
||||
.octicon-link:before { content: '\f05c'} /* */
|
||||
.octicon-link-external:before { content: '\f07f'} /* */
|
||||
.octicon-list-ordered:before { content: '\f062'} /* */
|
||||
.octicon-list-unordered:before { content: '\f061'} /* */
|
||||
.octicon-location:before { content: '\f060'} /* */
|
||||
.octicon-gist-private:before,
|
||||
.octicon-mirror-private:before,
|
||||
.octicon-git-fork-private:before,
|
||||
.octicon-lock:before { content: '\f06a'} /* */
|
||||
.octicon-logo-github:before { content: '\f092'} /* */
|
||||
.octicon-mail:before { content: '\f03b'} /* */
|
||||
.octicon-mail-read:before { content: '\f03c'} /* */
|
||||
.octicon-mail-reply:before { content: '\f051'} /* */
|
||||
.octicon-mark-github:before { content: '\f00a'} /* */
|
||||
.octicon-markdown:before { content: '\f0c9'} /* */
|
||||
.octicon-megaphone:before { content: '\f077'} /* */
|
||||
.octicon-mention:before { content: '\f0be'} /* */
|
||||
.octicon-milestone:before { content: '\f075'} /* */
|
||||
.octicon-mirror-public:before,
|
||||
.octicon-mirror:before { content: '\f024'} /* */
|
||||
.octicon-mortar-board:before { content: '\f0d7'} /* */
|
||||
.octicon-mute:before { content: '\f080'} /* */
|
||||
.octicon-no-newline:before { content: '\f09c'} /* */
|
||||
.octicon-octoface:before { content: '\f008'} /* */
|
||||
.octicon-organization:before { content: '\f037'} /* */
|
||||
.octicon-package:before { content: '\f0c4'} /* */
|
||||
.octicon-paintcan:before { content: '\f0d1'} /* */
|
||||
.octicon-pencil:before { content: '\f058'} /* */
|
||||
.octicon-person-add:before,
|
||||
.octicon-person-follow:before,
|
||||
.octicon-person:before { content: '\f018'} /* */
|
||||
.octicon-pin:before { content: '\f041'} /* */
|
||||
.octicon-plug:before { content: '\f0d4'} /* */
|
||||
.octicon-repo-create:before,
|
||||
.octicon-gist-new:before,
|
||||
.octicon-file-directory-create:before,
|
||||
.octicon-file-add:before,
|
||||
.octicon-plus:before { content: '\f05d'} /* */
|
||||
.octicon-primitive-dot:before { content: '\f052'} /* */
|
||||
.octicon-primitive-square:before { content: '\f053'} /* */
|
||||
.octicon-pulse:before { content: '\f085'} /* */
|
||||
.octicon-question:before { content: '\f02c'} /* */
|
||||
.octicon-quote:before { content: '\f063'} /* */
|
||||
.octicon-radio-tower:before { content: '\f030'} /* */
|
||||
.octicon-repo-delete:before,
|
||||
.octicon-repo:before { content: '\f001'} /* */
|
||||
.octicon-repo-clone:before { content: '\f04c'} /* */
|
||||
.octicon-repo-force-push:before { content: '\f04a'} /* */
|
||||
.octicon-gist-fork:before,
|
||||
.octicon-repo-forked:before { content: '\f002'} /* */
|
||||
.octicon-repo-pull:before { content: '\f006'} /* */
|
||||
.octicon-repo-push:before { content: '\f005'} /* */
|
||||
.octicon-rocket:before { content: '\f033'} /* */
|
||||
.octicon-rss:before { content: '\f034'} /* */
|
||||
.octicon-ruby:before { content: '\f047'} /* */
|
||||
.octicon-screen-full:before { content: '\f066'} /* */
|
||||
.octicon-screen-normal:before { content: '\f067'} /* */
|
||||
.octicon-search-save:before,
|
||||
.octicon-search:before { content: '\f02e'} /* */
|
||||
.octicon-server:before { content: '\f097'} /* */
|
||||
.octicon-settings:before { content: '\f07c'} /* */
|
||||
.octicon-shield:before { content: '\f0e1'} /* */
|
||||
.octicon-log-in:before,
|
||||
.octicon-sign-in:before { content: '\f036'} /* */
|
||||
.octicon-log-out:before,
|
||||
.octicon-sign-out:before { content: '\f032'} /* */
|
||||
.octicon-squirrel:before { content: '\f0b2'} /* */
|
||||
.octicon-star-add:before,
|
||||
.octicon-star-delete:before,
|
||||
.octicon-star:before { content: '\f02a'} /* */
|
||||
.octicon-stop:before { content: '\f08f'} /* */
|
||||
.octicon-repo-sync:before,
|
||||
.octicon-sync:before { content: '\f087'} /* */
|
||||
.octicon-tag-remove:before,
|
||||
.octicon-tag-add:before,
|
||||
.octicon-tag:before { content: '\f015'} /* */
|
||||
.octicon-telescope:before { content: '\f088'} /* */
|
||||
.octicon-terminal:before { content: '\f0c8'} /* */
|
||||
.octicon-three-bars:before { content: '\f05e'} /* */
|
||||
.octicon-thumbsdown:before { content: '\f0db'} /* */
|
||||
.octicon-thumbsup:before { content: '\f0da'} /* */
|
||||
.octicon-tools:before { content: '\f031'} /* */
|
||||
.octicon-trashcan:before { content: '\f0d0'} /* */
|
||||
.octicon-triangle-down:before { content: '\f05b'} /* */
|
||||
.octicon-triangle-left:before { content: '\f044'} /* */
|
||||
.octicon-triangle-right:before { content: '\f05a'} /* */
|
||||
.octicon-triangle-up:before { content: '\f0aa'} /* */
|
||||
.octicon-unfold:before { content: '\f039'} /* */
|
||||
.octicon-unmute:before { content: '\f0ba'} /* */
|
||||
.octicon-versions:before { content: '\f064'} /* */
|
||||
.octicon-watch:before { content: '\f0e0'} /* */
|
||||
.octicon-remove-close:before,
|
||||
.octicon-x:before { content: '\f081'} /* */
|
||||
.octicon-zap:before { content: '\26A1'} /* ⚡ */
|
||||
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 131 KiB |
@@ -1,217 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'octicons';
|
||||
src: font-url('octicons.eot?#iefix') format('embedded-opentype'),
|
||||
font-url('octicons.woff') format('woff'),
|
||||
font-url('octicons.ttf') format('truetype'),
|
||||
font-url('octicons.svg#octicons') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
// .octicon is optimized for 16px.
|
||||
// .mega-octicon is optimized for 32px but can be used larger.
|
||||
.octicon, .mega-octicon {
|
||||
font: normal normal normal 16px/1 octicons;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.mega-octicon { font-size: 32px; }
|
||||
|
||||
.octicon-alert:before { content: '\f02d'} /* */
|
||||
.octicon-arrow-down:before { content: '\f03f'} /* */
|
||||
.octicon-arrow-left:before { content: '\f040'} /* */
|
||||
.octicon-arrow-right:before { content: '\f03e'} /* */
|
||||
.octicon-arrow-small-down:before { content: '\f0a0'} /* */
|
||||
.octicon-arrow-small-left:before { content: '\f0a1'} /* */
|
||||
.octicon-arrow-small-right:before { content: '\f071'} /* */
|
||||
.octicon-arrow-small-up:before { content: '\f09f'} /* */
|
||||
.octicon-arrow-up:before { content: '\f03d'} /* */
|
||||
.octicon-microscope:before,
|
||||
.octicon-beaker:before { content: '\f0dd'} /* */
|
||||
.octicon-bell:before { content: '\f0de'} /* */
|
||||
.octicon-book:before { content: '\f007'} /* */
|
||||
.octicon-bookmark:before { content: '\f07b'} /* */
|
||||
.octicon-briefcase:before { content: '\f0d3'} /* */
|
||||
.octicon-broadcast:before { content: '\f048'} /* */
|
||||
.octicon-browser:before { content: '\f0c5'} /* */
|
||||
.octicon-bug:before { content: '\f091'} /* */
|
||||
.octicon-calendar:before { content: '\f068'} /* */
|
||||
.octicon-check:before { content: '\f03a'} /* */
|
||||
.octicon-checklist:before { content: '\f076'} /* */
|
||||
.octicon-chevron-down:before { content: '\f0a3'} /* */
|
||||
.octicon-chevron-left:before { content: '\f0a4'} /* */
|
||||
.octicon-chevron-right:before { content: '\f078'} /* */
|
||||
.octicon-chevron-up:before { content: '\f0a2'} /* */
|
||||
.octicon-circle-slash:before { content: '\f084'} /* */
|
||||
.octicon-circuit-board:before { content: '\f0d6'} /* */
|
||||
.octicon-clippy:before { content: '\f035'} /* */
|
||||
.octicon-clock:before { content: '\f046'} /* */
|
||||
.octicon-cloud-download:before { content: '\f00b'} /* */
|
||||
.octicon-cloud-upload:before { content: '\f00c'} /* */
|
||||
.octicon-code:before { content: '\f05f'} /* */
|
||||
.octicon-color-mode:before { content: '\f065'} /* */
|
||||
.octicon-comment-add:before,
|
||||
.octicon-comment:before { content: '\f02b'} /* */
|
||||
.octicon-comment-discussion:before { content: '\f04f'} /* */
|
||||
.octicon-credit-card:before { content: '\f045'} /* */
|
||||
.octicon-dash:before { content: '\f0ca'} /* */
|
||||
.octicon-dashboard:before { content: '\f07d'} /* */
|
||||
.octicon-database:before { content: '\f096'} /* */
|
||||
.octicon-clone:before,
|
||||
.octicon-desktop-download:before { content: '\f0dc'} /* */
|
||||
.octicon-device-camera:before { content: '\f056'} /* */
|
||||
.octicon-device-camera-video:before { content: '\f057'} /* */
|
||||
.octicon-device-desktop:before { content: '\f27c'} /* */
|
||||
.octicon-device-mobile:before { content: '\f038'} /* */
|
||||
.octicon-diff:before { content: '\f04d'} /* */
|
||||
.octicon-diff-added:before { content: '\f06b'} /* */
|
||||
.octicon-diff-ignored:before { content: '\f099'} /* */
|
||||
.octicon-diff-modified:before { content: '\f06d'} /* */
|
||||
.octicon-diff-removed:before { content: '\f06c'} /* */
|
||||
.octicon-diff-renamed:before { content: '\f06e'} /* */
|
||||
.octicon-ellipsis:before { content: '\f09a'} /* */
|
||||
.octicon-eye-unwatch:before,
|
||||
.octicon-eye-watch:before,
|
||||
.octicon-eye:before { content: '\f04e'} /* */
|
||||
.octicon-file-binary:before { content: '\f094'} /* */
|
||||
.octicon-file-code:before { content: '\f010'} /* */
|
||||
.octicon-file-directory:before { content: '\f016'} /* */
|
||||
.octicon-file-media:before { content: '\f012'} /* */
|
||||
.octicon-file-pdf:before { content: '\f014'} /* */
|
||||
.octicon-file-submodule:before { content: '\f017'} /* */
|
||||
.octicon-file-symlink-directory:before { content: '\f0b1'} /* */
|
||||
.octicon-file-symlink-file:before { content: '\f0b0'} /* */
|
||||
.octicon-file-text:before { content: '\f011'} /* */
|
||||
.octicon-file-zip:before { content: '\f013'} /* */
|
||||
.octicon-flame:before { content: '\f0d2'} /* */
|
||||
.octicon-fold:before { content: '\f0cc'} /* */
|
||||
.octicon-gear:before { content: '\f02f'} /* */
|
||||
.octicon-gift:before { content: '\f042'} /* */
|
||||
.octicon-gist:before { content: '\f00e'} /* */
|
||||
.octicon-gist-secret:before { content: '\f08c'} /* */
|
||||
.octicon-git-branch-create:before,
|
||||
.octicon-git-branch-delete:before,
|
||||
.octicon-git-branch:before { content: '\f020'} /* */
|
||||
.octicon-git-commit:before { content: '\f01f'} /* */
|
||||
.octicon-git-compare:before { content: '\f0ac'} /* */
|
||||
.octicon-git-merge:before { content: '\f023'} /* */
|
||||
.octicon-git-pull-request-abandoned:before,
|
||||
.octicon-git-pull-request:before { content: '\f009'} /* */
|
||||
.octicon-globe:before { content: '\f0b6'} /* */
|
||||
.octicon-graph:before { content: '\f043'} /* */
|
||||
.octicon-heart:before { content: '\2665'} /* ♥ */
|
||||
.octicon-history:before { content: '\f07e'} /* */
|
||||
.octicon-home:before { content: '\f08d'} /* */
|
||||
.octicon-horizontal-rule:before { content: '\f070'} /* */
|
||||
.octicon-hubot:before { content: '\f09d'} /* */
|
||||
.octicon-inbox:before { content: '\f0cf'} /* */
|
||||
.octicon-info:before { content: '\f059'} /* */
|
||||
.octicon-issue-closed:before { content: '\f028'} /* */
|
||||
.octicon-issue-opened:before { content: '\f026'} /* */
|
||||
.octicon-issue-reopened:before { content: '\f027'} /* */
|
||||
.octicon-jersey:before { content: '\f019'} /* */
|
||||
.octicon-key:before { content: '\f049'} /* */
|
||||
.octicon-keyboard:before { content: '\f00d'} /* */
|
||||
.octicon-law:before { content: '\f0d8'} /* */
|
||||
.octicon-light-bulb:before { content: '\f000'} /* */
|
||||
.octicon-link:before { content: '\f05c'} /* */
|
||||
.octicon-link-external:before { content: '\f07f'} /* */
|
||||
.octicon-list-ordered:before { content: '\f062'} /* */
|
||||
.octicon-list-unordered:before { content: '\f061'} /* */
|
||||
.octicon-location:before { content: '\f060'} /* */
|
||||
.octicon-gist-private:before,
|
||||
.octicon-mirror-private:before,
|
||||
.octicon-git-fork-private:before,
|
||||
.octicon-lock:before { content: '\f06a'} /* */
|
||||
.octicon-logo-github:before { content: '\f092'} /* */
|
||||
.octicon-mail:before { content: '\f03b'} /* */
|
||||
.octicon-mail-read:before { content: '\f03c'} /* */
|
||||
.octicon-mail-reply:before { content: '\f051'} /* */
|
||||
.octicon-mark-github:before { content: '\f00a'} /* */
|
||||
.octicon-markdown:before { content: '\f0c9'} /* */
|
||||
.octicon-megaphone:before { content: '\f077'} /* */
|
||||
.octicon-mention:before { content: '\f0be'} /* */
|
||||
.octicon-milestone:before { content: '\f075'} /* */
|
||||
.octicon-mirror-public:before,
|
||||
.octicon-mirror:before { content: '\f024'} /* */
|
||||
.octicon-mortar-board:before { content: '\f0d7'} /* */
|
||||
.octicon-mute:before { content: '\f080'} /* */
|
||||
.octicon-no-newline:before { content: '\f09c'} /* */
|
||||
.octicon-octoface:before { content: '\f008'} /* */
|
||||
.octicon-organization:before { content: '\f037'} /* */
|
||||
.octicon-package:before { content: '\f0c4'} /* */
|
||||
.octicon-paintcan:before { content: '\f0d1'} /* */
|
||||
.octicon-pencil:before { content: '\f058'} /* */
|
||||
.octicon-person-add:before,
|
||||
.octicon-person-follow:before,
|
||||
.octicon-person:before { content: '\f018'} /* */
|
||||
.octicon-pin:before { content: '\f041'} /* */
|
||||
.octicon-plug:before { content: '\f0d4'} /* */
|
||||
.octicon-repo-create:before,
|
||||
.octicon-gist-new:before,
|
||||
.octicon-file-directory-create:before,
|
||||
.octicon-file-add:before,
|
||||
.octicon-plus:before { content: '\f05d'} /* */
|
||||
.octicon-primitive-dot:before { content: '\f052'} /* */
|
||||
.octicon-primitive-square:before { content: '\f053'} /* */
|
||||
.octicon-pulse:before { content: '\f085'} /* */
|
||||
.octicon-question:before { content: '\f02c'} /* */
|
||||
.octicon-quote:before { content: '\f063'} /* */
|
||||
.octicon-radio-tower:before { content: '\f030'} /* */
|
||||
.octicon-repo-delete:before,
|
||||
.octicon-repo:before { content: '\f001'} /* */
|
||||
.octicon-repo-clone:before { content: '\f04c'} /* */
|
||||
.octicon-repo-force-push:before { content: '\f04a'} /* */
|
||||
.octicon-gist-fork:before,
|
||||
.octicon-repo-forked:before { content: '\f002'} /* */
|
||||
.octicon-repo-pull:before { content: '\f006'} /* */
|
||||
.octicon-repo-push:before { content: '\f005'} /* */
|
||||
.octicon-rocket:before { content: '\f033'} /* */
|
||||
.octicon-rss:before { content: '\f034'} /* */
|
||||
.octicon-ruby:before { content: '\f047'} /* */
|
||||
.octicon-screen-full:before { content: '\f066'} /* */
|
||||
.octicon-screen-normal:before { content: '\f067'} /* */
|
||||
.octicon-search-save:before,
|
||||
.octicon-search:before { content: '\f02e'} /* */
|
||||
.octicon-server:before { content: '\f097'} /* */
|
||||
.octicon-settings:before { content: '\f07c'} /* */
|
||||
.octicon-shield:before { content: '\f0e1'} /* */
|
||||
.octicon-log-in:before,
|
||||
.octicon-sign-in:before { content: '\f036'} /* */
|
||||
.octicon-log-out:before,
|
||||
.octicon-sign-out:before { content: '\f032'} /* */
|
||||
.octicon-squirrel:before { content: '\f0b2'} /* */
|
||||
.octicon-star-add:before,
|
||||
.octicon-star-delete:before,
|
||||
.octicon-star:before { content: '\f02a'} /* */
|
||||
.octicon-stop:before { content: '\f08f'} /* */
|
||||
.octicon-repo-sync:before,
|
||||
.octicon-sync:before { content: '\f087'} /* */
|
||||
.octicon-tag-remove:before,
|
||||
.octicon-tag-add:before,
|
||||
.octicon-tag:before { content: '\f015'} /* */
|
||||
.octicon-telescope:before { content: '\f088'} /* */
|
||||
.octicon-terminal:before { content: '\f0c8'} /* */
|
||||
.octicon-three-bars:before { content: '\f05e'} /* */
|
||||
.octicon-thumbsdown:before { content: '\f0db'} /* */
|
||||
.octicon-thumbsup:before { content: '\f0da'} /* */
|
||||
.octicon-tools:before { content: '\f031'} /* */
|
||||
.octicon-trashcan:before { content: '\f0d0'} /* */
|
||||
.octicon-triangle-down:before { content: '\f05b'} /* */
|
||||
.octicon-triangle-left:before { content: '\f044'} /* */
|
||||
.octicon-triangle-right:before { content: '\f05a'} /* */
|
||||
.octicon-triangle-up:before { content: '\f0aa'} /* */
|
||||
.octicon-unfold:before { content: '\f039'} /* */
|
||||
.octicon-unmute:before { content: '\f0ba'} /* */
|
||||
.octicon-versions:before { content: '\f064'} /* */
|
||||
.octicon-watch:before { content: '\f0e0'} /* */
|
||||
.octicon-remove-close:before,
|
||||
.octicon-x:before { content: '\f081'} /* */
|
||||
.octicon-zap:before { content: '\26A1'} /* ⚡ */
|
||||
@@ -34,8 +34,8 @@ export class HorizontalScrollbar extends AbstractScrollbar {
|
||||
className: 'left-arrow',
|
||||
top: scrollbarDelta,
|
||||
left: arrowDelta,
|
||||
bottom: void 0,
|
||||
right: void 0,
|
||||
bottom: undefined,
|
||||
right: undefined,
|
||||
bgWidth: options.arrowSize,
|
||||
bgHeight: options.horizontalScrollbarSize,
|
||||
onActivate: () => this._host.onMouseWheel(new StandardWheelEvent(null, 1, 0)),
|
||||
@@ -44,8 +44,8 @@ export class HorizontalScrollbar extends AbstractScrollbar {
|
||||
this._createArrow({
|
||||
className: 'right-arrow',
|
||||
top: scrollbarDelta,
|
||||
left: void 0,
|
||||
bottom: void 0,
|
||||
left: undefined,
|
||||
bottom: undefined,
|
||||
right: arrowDelta,
|
||||
bgWidth: options.arrowSize,
|
||||
bgHeight: options.horizontalScrollbarSize,
|
||||
|
||||
@@ -281,6 +281,7 @@ export abstract class AbstractScrollableElement extends Widget {
|
||||
let massagedOptions = resolveOptions(newOptions);
|
||||
this._options.handleMouseWheel = massagedOptions.handleMouseWheel;
|
||||
this._options.mouseWheelScrollSensitivity = massagedOptions.mouseWheelScrollSensitivity;
|
||||
this._options.fastScrollSensitivity = massagedOptions.fastScrollSensitivity;
|
||||
this._setListeningToMouseWheel(this._options.handleMouseWheel);
|
||||
|
||||
if (!this._options.lazyRender) {
|
||||
@@ -340,6 +341,12 @@ export abstract class AbstractScrollableElement extends Widget {
|
||||
deltaY = 0;
|
||||
}
|
||||
|
||||
if (e.browserEvent && e.browserEvent.altKey) {
|
||||
// fastScrolling
|
||||
deltaX = deltaX * this._options.fastScrollSensitivity;
|
||||
deltaY = deltaY * this._options.fastScrollSensitivity;
|
||||
}
|
||||
|
||||
const futureScrollPosition = this._scrollable.getFutureScrollPosition();
|
||||
|
||||
let desiredScrollPosition: INewScrollPosition = {};
|
||||
@@ -540,6 +547,7 @@ function resolveOptions(opts: ScrollableElementCreationOptions): ScrollableEleme
|
||||
alwaysConsumeMouseWheel: (typeof opts.alwaysConsumeMouseWheel !== 'undefined' ? opts.alwaysConsumeMouseWheel : false),
|
||||
scrollYToX: (typeof opts.scrollYToX !== 'undefined' ? opts.scrollYToX : false),
|
||||
mouseWheelScrollSensitivity: (typeof opts.mouseWheelScrollSensitivity !== 'undefined' ? opts.mouseWheelScrollSensitivity : 1),
|
||||
fastScrollSensitivity: (typeof opts.fastScrollSensitivity !== 'undefined' ? opts.fastScrollSensitivity : 5),
|
||||
mouseWheelSmoothScroll: (typeof opts.mouseWheelSmoothScroll !== 'undefined' ? opts.mouseWheelSmoothScroll : true),
|
||||
arrowSize: (typeof opts.arrowSize !== 'undefined' ? opts.arrowSize : 11),
|
||||
|
||||
|
||||
@@ -50,6 +50,11 @@ export interface ScrollableElementCreationOptions {
|
||||
* Defaults to 1.
|
||||
*/
|
||||
mouseWheelScrollSensitivity?: number;
|
||||
/**
|
||||
* FastScrolling mulitplier speed when pressing `Alt`
|
||||
* Defaults to 5.
|
||||
*/
|
||||
fastScrollSensitivity?: number;
|
||||
/**
|
||||
* Height for vertical arrows (top/bottom) and width for horizontal arrows (left/right).
|
||||
* Defaults to 11.
|
||||
@@ -107,6 +112,7 @@ export interface ScrollableElementCreationOptions {
|
||||
export interface ScrollableElementChangeOptions {
|
||||
handleMouseWheel?: boolean;
|
||||
mouseWheelScrollSensitivity?: number;
|
||||
fastScrollSensitivity: number;
|
||||
}
|
||||
|
||||
export interface ScrollableElementResolvedOptions {
|
||||
@@ -118,6 +124,7 @@ export interface ScrollableElementResolvedOptions {
|
||||
scrollYToX: boolean;
|
||||
alwaysConsumeMouseWheel: boolean;
|
||||
mouseWheelScrollSensitivity: number;
|
||||
fastScrollSensitivity: number;
|
||||
mouseWheelSmoothScroll: boolean;
|
||||
arrowSize: number;
|
||||
listenOnDomNode: HTMLElement | null;
|
||||
|
||||
@@ -35,8 +35,8 @@ export class VerticalScrollbar extends AbstractScrollbar {
|
||||
className: 'up-arrow',
|
||||
top: arrowDelta,
|
||||
left: scrollbarDelta,
|
||||
bottom: void 0,
|
||||
right: void 0,
|
||||
bottom: undefined,
|
||||
right: undefined,
|
||||
bgWidth: options.verticalScrollbarSize,
|
||||
bgHeight: options.arrowSize,
|
||||
onActivate: () => this._host.onMouseWheel(new StandardWheelEvent(null, 0, 1)),
|
||||
@@ -44,10 +44,10 @@ export class VerticalScrollbar extends AbstractScrollbar {
|
||||
|
||||
this._createArrow({
|
||||
className: 'down-arrow',
|
||||
top: void 0,
|
||||
top: undefined,
|
||||
left: scrollbarDelta,
|
||||
bottom: arrowDelta,
|
||||
right: void 0,
|
||||
right: undefined,
|
||||
bgWidth: options.verticalScrollbarSize,
|
||||
bgHeight: options.arrowSize,
|
||||
onActivate: () => this._host.onMouseWheel(new StandardWheelEvent(null, 0, -1)),
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { SelectBoxNative } from 'vs/base/browser/ui/selectBox/selectBoxNative';
|
||||
import { SelectBoxList } from 'vs/base/browser/ui/selectBox/selectBoxCustom';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { IContentActionHandler } from 'vs/base/browser/htmlContentRenderer';
|
||||
|
||||
|
||||
// Public SelectBox interface - Calls routed to appropriate select implementation class
|
||||
|
||||
@@ -22,10 +22,9 @@ export interface ISelectBoxDelegate {
|
||||
|
||||
// Public SelectBox Interface
|
||||
readonly onDidSelect: Event<ISelectData>;
|
||||
setOptions(options: string[], selected?: number, disabled?: number): void;
|
||||
setOptions(options: ISelectOptionItem[], selected?: number);
|
||||
select(index: number): void;
|
||||
setAriaLabel(label: string);
|
||||
setDetailsProvider(provider: (index: number) => { details: string, isMarkdown: boolean });
|
||||
focus(): void;
|
||||
blur(): void;
|
||||
dispose(): void;
|
||||
@@ -37,16 +36,25 @@ export interface ISelectBoxDelegate {
|
||||
}
|
||||
|
||||
export interface ISelectBoxOptions {
|
||||
useCustomDrawn?: boolean;
|
||||
ariaLabel?: string;
|
||||
minBottomMargin?: number;
|
||||
hasDetails?: boolean;
|
||||
markdownActionHandler?: IContentActionHandler;
|
||||
}
|
||||
|
||||
// Utilize optionItem interface to capture all option parameters
|
||||
export interface ISelectOptionItem {
|
||||
text: string;
|
||||
decoratorRight?: string;
|
||||
description?: string;
|
||||
descriptionIsMarkdown?: boolean;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ISelectBoxStyles extends IListStyles {
|
||||
selectBackground?: Color;
|
||||
selectListBackground?: Color;
|
||||
selectForeground?: Color;
|
||||
decoratorRightForeground?: Color;
|
||||
selectBorder?: Color;
|
||||
selectListBorder?: Color;
|
||||
focusBorder?: Color;
|
||||
@@ -72,13 +80,13 @@ export class SelectBox extends Widget implements ISelectBoxDelegate {
|
||||
private styles: ISelectBoxStyles;
|
||||
private selectBoxDelegate: ISelectBoxDelegate;
|
||||
|
||||
constructor(options: string[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles = deepClone(defaultStyles), selectBoxOptions?: ISelectBoxOptions) {
|
||||
constructor(options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles = deepClone(defaultStyles), selectBoxOptions?: ISelectBoxOptions) {
|
||||
super();
|
||||
|
||||
mixin(this.styles, defaultStyles, false);
|
||||
|
||||
// Instantiate select implementation based on platform
|
||||
if (isMacintosh && !(selectBoxOptions && selectBoxOptions.hasDetails)) {
|
||||
// Default to native SelectBox for OSX unless overridden
|
||||
if (isMacintosh && !(selectBoxOptions && selectBoxOptions.useCustomDrawn)) {
|
||||
this.selectBoxDelegate = new SelectBoxNative(options, selected, styles, selectBoxOptions);
|
||||
} else {
|
||||
this.selectBoxDelegate = new SelectBoxList(options, selected, contextViewProvider, styles, selectBoxOptions);
|
||||
@@ -96,8 +104,8 @@ export class SelectBox extends Widget implements ISelectBoxDelegate {
|
||||
return this.selectBoxDelegate.onDidSelect;
|
||||
}
|
||||
|
||||
public setOptions(options: string[], selected?: number, disabled?: number): void {
|
||||
this.selectBoxDelegate.setOptions(options, selected, disabled);
|
||||
public setOptions(options: ISelectOptionItem[], selected?: number): void {
|
||||
this.selectBoxDelegate.setOptions(options, selected);
|
||||
}
|
||||
|
||||
public select(index: number): void {
|
||||
@@ -108,10 +116,6 @@ export class SelectBox extends Widget implements ISelectBoxDelegate {
|
||||
this.selectBoxDelegate.setAriaLabel(label);
|
||||
}
|
||||
|
||||
public setDetailsProvider(provider: (index: number) => { details: string, isMarkdown: boolean }): void {
|
||||
this.selectBoxDelegate.setDetailsProvider(provider);
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.selectBoxDelegate.focus();
|
||||
}
|
||||
|
||||
@@ -80,8 +80,18 @@
|
||||
overflow: hidden;
|
||||
padding-left: 3.5px;
|
||||
white-space: nowrap;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row > .option-decorator-right {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
padding-right: 10px;
|
||||
white-space: nowrap;
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
/* Accepted CSS hiding technique for accessibility reader text */
|
||||
/* https://webaim.org/techniques/css/invisiblecontent/ */
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import 'vs/css!./selectBoxCustom';
|
||||
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Event, Emitter, chain } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { KeyCode, KeyCodeUtils } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
@@ -16,7 +16,7 @@ import { List } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListVirtualDelegate, IListRenderer, IListEvent } from 'vs/base/browser/ui/list/list';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { ISelectBoxDelegate, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { ISelectBoxDelegate, ISelectOptionItem, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer';
|
||||
|
||||
@@ -24,16 +24,11 @@ const $ = dom.$;
|
||||
|
||||
const SELECT_OPTION_ENTRY_TEMPLATE_ID = 'selectOption.entry.template';
|
||||
|
||||
export interface ISelectOptionItem {
|
||||
optionText: string;
|
||||
optionDescriptionText?: string;
|
||||
optionDisabled: boolean;
|
||||
}
|
||||
|
||||
interface ISelectListTemplateData {
|
||||
root: HTMLElement;
|
||||
optionText: HTMLElement;
|
||||
optionDescriptionText: HTMLElement;
|
||||
text: HTMLElement;
|
||||
itemDescription: HTMLElement;
|
||||
decoratorRight: HTMLElement;
|
||||
disposables: IDisposable[];
|
||||
}
|
||||
|
||||
@@ -47,29 +42,32 @@ class SelectListRenderer implements IListRenderer<ISelectOptionItem, ISelectList
|
||||
const data = <ISelectListTemplateData>Object.create(null);
|
||||
data.disposables = [];
|
||||
data.root = container;
|
||||
data.optionText = dom.append(container, $('.option-text'));
|
||||
data.optionDescriptionText = dom.append(container, $('.option-text-description'));
|
||||
dom.addClass(data.optionDescriptionText, 'visually-hidden');
|
||||
data.text = dom.append(container, $('.option-text'));
|
||||
data.decoratorRight = dom.append(container, $('.option-decorator-right'));
|
||||
data.itemDescription = dom.append(container, $('.option-text-description'));
|
||||
dom.addClass(data.itemDescription, 'visually-hidden');
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement(element: ISelectOptionItem, index: number, templateData: ISelectListTemplateData): void {
|
||||
const data = <ISelectListTemplateData>templateData;
|
||||
const optionText = (<ISelectOptionItem>element).optionText;
|
||||
const optionDisabled = (<ISelectOptionItem>element).optionDisabled;
|
||||
const text = (<ISelectOptionItem>element).text;
|
||||
const decoratorRight = (<ISelectOptionItem>element).decoratorRight;
|
||||
const isDisabled = (<ISelectOptionItem>element).isDisabled;
|
||||
|
||||
data.optionText.textContent = optionText;
|
||||
data.text.textContent = text;
|
||||
data.decoratorRight.innerText = (!!decoratorRight ? decoratorRight : '');
|
||||
|
||||
if (typeof element.optionDescriptionText === 'string') {
|
||||
const optionDescriptionId = (optionText.replace(/ /g, '_').toLowerCase() + '_description_' + data.root.id);
|
||||
data.optionText.setAttribute('aria-describedby', optionDescriptionId);
|
||||
data.optionDescriptionText.id = optionDescriptionId;
|
||||
data.optionDescriptionText.innerText = element.optionDescriptionText;
|
||||
if (typeof element.description === 'string') {
|
||||
const itemDescriptionId = (text.replace(/ /g, '_').toLowerCase() + '_description_' + data.root.id);
|
||||
data.text.setAttribute('aria-describedby', itemDescriptionId);
|
||||
data.itemDescription.id = itemDescriptionId;
|
||||
data.itemDescription.innerText = element.description;
|
||||
}
|
||||
|
||||
// pseudo-select disabled option
|
||||
if (optionDisabled) {
|
||||
if (isDisabled) {
|
||||
dom.addClass((<HTMLElement>data.root), 'option-disabled');
|
||||
} else {
|
||||
// Make sure we do class removal from prior template rendering
|
||||
@@ -77,10 +75,6 @@ class SelectListRenderer implements IListRenderer<ISelectOptionItem, ISelectList
|
||||
}
|
||||
}
|
||||
|
||||
disposeElement(): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ISelectListTemplateData): void {
|
||||
templateData.disposables = dispose(templateData.disposables);
|
||||
}
|
||||
@@ -96,9 +90,8 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
private selectBoxOptions: ISelectBoxOptions;
|
||||
// {{SQL CARBON EDIT}}
|
||||
public selectElement: HTMLSelectElement;
|
||||
private options: string[];
|
||||
private options: ISelectOptionItem[];
|
||||
private selected: number;
|
||||
private disabledOptionIndex: number;
|
||||
private readonly _onDidSelect: Emitter<ISelectData>;
|
||||
private toDispose: IDisposable[];
|
||||
private styles: ISelectBoxStyles;
|
||||
@@ -111,13 +104,13 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
private widthControlElement: HTMLElement;
|
||||
private _currentSelection: number;
|
||||
private _dropDownPosition: AnchorPosition;
|
||||
private detailsProvider: (index: number) => { details: string, isMarkdown: boolean };
|
||||
private _hasDetails: boolean = false;
|
||||
private selectionDetailsPane: HTMLElement;
|
||||
private _skipLayout: boolean = false;
|
||||
|
||||
private _sticky: boolean = false; // for dev purposes only
|
||||
|
||||
constructor(options: string[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles, selectBoxOptions?: ISelectBoxOptions) {
|
||||
constructor(options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles, selectBoxOptions?: ISelectBoxOptions) {
|
||||
|
||||
this.toDispose = [];
|
||||
this._isVisible = false;
|
||||
@@ -252,19 +245,18 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
return this._onDidSelect.event;
|
||||
}
|
||||
|
||||
public setOptions(options: string[], selected?: number, disabled?: number): void {
|
||||
public setOptions(options: ISelectOptionItem[], selected?: number): void {
|
||||
if (!this.options || !arrays.equals(this.options, options)) {
|
||||
this.options = options;
|
||||
this.selectElement.options.length = 0;
|
||||
this._hasDetails = false;
|
||||
|
||||
let i = 0;
|
||||
this.options.forEach((option) => {
|
||||
this.selectElement.add(this.createOption(option, i, disabled === i++));
|
||||
this.options.forEach((option, index) => {
|
||||
this.selectElement.add(this.createOption(option.text, index, option.isDisabled));
|
||||
if (typeof option.description === 'string') {
|
||||
this._hasDetails = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (disabled !== undefined) {
|
||||
this.disabledOptionIndex = disabled;
|
||||
}
|
||||
}
|
||||
|
||||
if (selected !== undefined) {
|
||||
@@ -280,18 +272,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
// Mirror options in drop-down
|
||||
// Populate select list for non-native select mode
|
||||
if (this.selectList && !!this.options) {
|
||||
let listEntries: ISelectOptionItem[];
|
||||
listEntries = [];
|
||||
|
||||
for (let index = 0; index < this.options.length; index++) {
|
||||
const element = this.options[index];
|
||||
let optionDisabled: boolean;
|
||||
index === this.disabledOptionIndex ? optionDisabled = true : optionDisabled = false;
|
||||
const optionDescription = this.detailsProvider ? this.detailsProvider(index) : { details: undefined, isMarkdown: false };
|
||||
|
||||
listEntries.push({ optionText: element, optionDisabled: optionDisabled, optionDescriptionText: optionDescription.details });
|
||||
}
|
||||
this.selectList.splice(0, this.selectList.length, listEntries);
|
||||
this.selectList.splice(0, this.selectList.length, this.options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,10 +296,6 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
this.selectElement.setAttribute('aria-label', this.selectBoxOptions.ariaLabel);
|
||||
}
|
||||
|
||||
public setDetailsProvider(provider: (index: number) => { details: string, isMarkdown: boolean }): void {
|
||||
this.detailsProvider = provider;
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.focus();
|
||||
@@ -353,6 +330,10 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused:not(:hover) { color: ${this.styles.listFocusForeground} !important; }`);
|
||||
}
|
||||
|
||||
if (this.styles.decoratorRightForeground) {
|
||||
content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row .option-decorator-right { color: ${this.styles.decoratorRightForeground} !important; }`);
|
||||
}
|
||||
|
||||
if (this.styles.selectBackground && this.styles.selectBorder && !this.styles.selectBorder.equals(this.styles.selectBackground)) {
|
||||
content.push(`.monaco-select-box-dropdown-container { border: 1px solid ${this.styles.selectBorder} } `);
|
||||
content.push(`.monaco-select-box-dropdown-container > .select-box-details-pane.border-top { border-top: 1px solid ${this.styles.selectBorder} } `);
|
||||
@@ -533,23 +514,16 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
// Iterate over detailed descriptions, find max height
|
||||
private measureMaxDetailsHeight(): number {
|
||||
|
||||
if (!this.detailsProvider) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let maxDetailsPaneHeight = 0;
|
||||
let description = { details: '', isMarkdown: false };
|
||||
|
||||
this.options.forEach((option, index) => {
|
||||
|
||||
this.selectionDetailsPane.innerText = '';
|
||||
|
||||
description = this.detailsProvider ? this.detailsProvider(index) : { details: '', isMarkdown: false };
|
||||
if (description.details) {
|
||||
if (description.isMarkdown) {
|
||||
this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(description.details));
|
||||
if (option.description) {
|
||||
if (option.descriptionIsMarkdown) {
|
||||
this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(option.description));
|
||||
} else {
|
||||
this.selectionDetailsPane.innerText = description.details;
|
||||
this.selectionDetailsPane.innerText = option.description;
|
||||
}
|
||||
this.selectionDetailsPane.style.display = 'block';
|
||||
} else {
|
||||
@@ -562,18 +536,19 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
});
|
||||
|
||||
// Reset description to selected
|
||||
description = this.detailsProvider ? this.detailsProvider(this.selected) : { details: '', isMarkdown: false };
|
||||
this.selectionDetailsPane.innerText = '';
|
||||
|
||||
if (description.details) {
|
||||
if (description.isMarkdown) {
|
||||
this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(description.details));
|
||||
this.selectionDetailsPane.innerText = '';
|
||||
const description = this.options[this.selected].description || null;
|
||||
const descriptionIsMarkdown = this.options[this.selected].descriptionIsMarkdown || null;
|
||||
|
||||
if (description) {
|
||||
if (descriptionIsMarkdown) {
|
||||
this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(description));
|
||||
} else {
|
||||
this.selectionDetailsPane.innerText = description.details;
|
||||
this.selectionDetailsPane.innerText = description;
|
||||
}
|
||||
this.selectionDetailsPane.style.display = 'block';
|
||||
}
|
||||
|
||||
return maxDetailsPaneHeight;
|
||||
}
|
||||
|
||||
@@ -610,7 +585,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
this.selectList.layout();
|
||||
let listHeight = this.selectList.contentHeight;
|
||||
|
||||
const maxDetailsPaneHeight = this.measureMaxDetailsHeight();
|
||||
const maxDetailsPaneHeight = this._hasDetails ? this.measureMaxDetailsHeight() : 0;
|
||||
|
||||
const minRequiredDropDownHeight = listHeight + verticalPadding + maxDetailsPaneHeight;
|
||||
const maxVisibleOptionsBelow = ((Math.floor((maxSelectDropDownHeightBelow - verticalPadding - maxDetailsPaneHeight) / this.getHeight())));
|
||||
@@ -702,7 +677,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
this.selectList.reveal(this.selectList.getFocus()[0] || 0);
|
||||
}
|
||||
|
||||
if (this.detailsProvider) {
|
||||
if (this._hasDetails) {
|
||||
// Leave the selectDropDownContainer to size itself according to children (list + details) - #57447
|
||||
this.selectList.getHTMLElement().style.height = (listHeight + verticalPadding) + 'px';
|
||||
} else {
|
||||
@@ -727,14 +702,18 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
|
||||
if (container && !!this.options) {
|
||||
let longest = 0;
|
||||
let longestLength = 0;
|
||||
|
||||
for (let index = 0; index < this.options.length; index++) {
|
||||
if (this.options[index].length > this.options[longest].length) {
|
||||
this.options.forEach((option, index) => {
|
||||
const len = option.text.length + (!!option.decoratorRight ? option.decoratorRight.length : 0);
|
||||
if (len > longestLength) {
|
||||
longest = index;
|
||||
longestLength = len;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
container.innerHTML = this.options[longest];
|
||||
|
||||
container.innerHTML = this.options[longest].text + (!!this.options[longest].decoratorRight ? (this.options[longest].decoratorRight + ' ') : '');
|
||||
elementWidth = dom.getTotalWidth(container);
|
||||
}
|
||||
|
||||
@@ -763,14 +742,13 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
this.selectList = new List(this.selectDropDownListContainer, this, [this.listRenderer], {
|
||||
ariaLabel: this.selectBoxOptions.ariaLabel,
|
||||
useShadows: false,
|
||||
selectOnMouseDown: false,
|
||||
verticalScrollMode: ScrollbarVisibility.Visible,
|
||||
keyboardSupport: false,
|
||||
mouseSupport: false
|
||||
});
|
||||
|
||||
// SetUp list keyboard controller - control navigation, disabled items, focus
|
||||
const onSelectDropDownKeyDown = chain(domEvent(this.selectDropDownListContainer, 'keydown'))
|
||||
const onSelectDropDownKeyDown = Event.chain(domEvent(this.selectDropDownListContainer, 'keydown'))
|
||||
.filter(() => this.selectList.length > 0)
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
|
||||
@@ -786,7 +764,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
|
||||
// SetUp list mouse controller - control navigation, disabled items, focus
|
||||
|
||||
chain(domEvent(this.selectList.getHTMLElement(), 'mouseup'))
|
||||
Event.chain(domEvent(this.selectList.getHTMLElement(), 'mouseup'))
|
||||
.filter(() => this.selectList.length > 0)
|
||||
.on(e => this.onMouseUp(e), this, this.toDispose);
|
||||
|
||||
@@ -809,12 +787,18 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
|
||||
dom.EventHelper.stop(e);
|
||||
|
||||
// Check our mouse event is on an option (not scrollbar)
|
||||
if (!e.toElement.classList.contains('option-text')) {
|
||||
const target = <Element>e.target;
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const listRowElement = e.toElement.parentElement;
|
||||
// Check our mouse event is on an option (not scrollbar)
|
||||
if (!!target.classList.contains('slider')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const listRowElement = target.closest('.monaco-list-row');
|
||||
|
||||
if (!listRowElement) {
|
||||
return;
|
||||
}
|
||||
@@ -839,7 +823,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
|
||||
this._onDidSelect.fire({
|
||||
index: this.selectElement.selectedIndex,
|
||||
selected: this.options[this.selected]
|
||||
selected: this.options[this.selected].text
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -871,9 +855,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
}
|
||||
};
|
||||
|
||||
const renderedMarkdown = renderMarkdown({ value: text }, {
|
||||
actionHandler: this.selectBoxOptions.markdownActionHandler
|
||||
});
|
||||
const renderedMarkdown = renderMarkdown({ value: text });
|
||||
|
||||
renderedMarkdown.classList.add('select-box-description-markdown');
|
||||
cleanRenderedMarkdown(renderedMarkdown);
|
||||
@@ -884,18 +866,20 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
// List Focus Change - passive - update details pane with newly focused element's data
|
||||
private onListFocus(e: IListEvent<ISelectOptionItem>) {
|
||||
// Skip during initial layout
|
||||
if (!this._isVisible) {
|
||||
if (!this._isVisible || !this._hasDetails) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectionDetailsPane.innerText = '';
|
||||
const selectedIndex = e.indexes[0];
|
||||
let description = this.detailsProvider ? this.detailsProvider(selectedIndex) : { details: '', isMarkdown: false };
|
||||
if (description.details) {
|
||||
if (description.isMarkdown) {
|
||||
this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(description.details));
|
||||
const description = this.options[selectedIndex].description || null;
|
||||
const descriptionIsMarkdown = this.options[selectedIndex].descriptionIsMarkdown || null;
|
||||
|
||||
if (description) {
|
||||
if (descriptionIsMarkdown) {
|
||||
this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(description));
|
||||
} else {
|
||||
this.selectionDetailsPane.innerText = description.details;
|
||||
this.selectionDetailsPane.innerText = description;
|
||||
}
|
||||
this.selectionDetailsPane.style.display = 'block';
|
||||
} else {
|
||||
@@ -929,7 +913,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
this._currentSelection = this.selected;
|
||||
this._onDidSelect.fire({
|
||||
index: this.selectElement.selectedIndex,
|
||||
selected: this.options[this.selected]
|
||||
selected: this.options[this.selected].text
|
||||
});
|
||||
}
|
||||
|
||||
@@ -939,14 +923,18 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
// List navigation - have to handle a disabled option (jump over)
|
||||
private onDownArrow(): void {
|
||||
if (this.selected < this.options.length - 1) {
|
||||
|
||||
// Skip disabled options
|
||||
if ((this.selected + 1) === this.disabledOptionIndex && this.options.length > this.selected + 2) {
|
||||
const nextOptionDisabled = this.options[this.selected + 1].isDisabled;
|
||||
|
||||
if (nextOptionDisabled && this.options.length > this.selected + 2) {
|
||||
this.selected += 2;
|
||||
} else if ((this.selected + 1) === this.disabledOptionIndex) {
|
||||
} else if (nextOptionDisabled) {
|
||||
return;
|
||||
} else {
|
||||
this.selected++;
|
||||
}
|
||||
|
||||
// Set focus/selection - only fire event when closing drop-down or on blur
|
||||
this.select(this.selected);
|
||||
this.selectList.setFocus([this.selected]);
|
||||
@@ -957,7 +945,8 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
private onUpArrow(): void {
|
||||
if (this.selected > 0) {
|
||||
// Skip disabled options
|
||||
if ((this.selected - 1) === this.disabledOptionIndex && this.selected > 1) {
|
||||
const previousOptionDisabled = this.options[this.selected - 1].isDisabled;
|
||||
if (previousOptionDisabled && this.selected > 1) {
|
||||
this.selected -= 2;
|
||||
} else {
|
||||
this.selected--;
|
||||
@@ -979,7 +968,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
this.selected = this.selectList.getFocus()[0];
|
||||
|
||||
// Shift selection down if we land on a disabled option
|
||||
if (this.selected === this.disabledOptionIndex && this.selected < this.options.length - 1) {
|
||||
if (this.options[this.selected].isDisabled && this.selected < this.options.length - 1) {
|
||||
this.selected++;
|
||||
this.selectList.setFocus([this.selected]);
|
||||
}
|
||||
@@ -998,7 +987,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
this.selected = this.selectList.getFocus()[0];
|
||||
|
||||
// Shift selection up if we land on a disabled option
|
||||
if (this.selected === this.disabledOptionIndex && this.selected > 0) {
|
||||
if (this.options[this.selected].isDisabled && this.selected > 0) {
|
||||
this.selected--;
|
||||
this.selectList.setFocus([this.selected]);
|
||||
}
|
||||
@@ -1014,7 +1003,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
return;
|
||||
}
|
||||
this.selected = 0;
|
||||
if (this.selected === this.disabledOptionIndex && this.selected > 1) {
|
||||
if (this.options[this.selected].isDisabled && this.selected > 1) {
|
||||
this.selected++;
|
||||
}
|
||||
this.selectList.setFocus([this.selected]);
|
||||
@@ -1029,7 +1018,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
return;
|
||||
}
|
||||
this.selected = this.options.length - 1;
|
||||
if (this.selected === this.disabledOptionIndex && this.selected > 1) {
|
||||
if (this.options[this.selected].isDisabled && this.selected > 1) {
|
||||
this.selected--;
|
||||
}
|
||||
this.selectList.setFocus([this.selected]);
|
||||
@@ -1044,7 +1033,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
|
||||
|
||||
for (let i = 0; i < this.options.length - 1; i++) {
|
||||
optionIndex = (i + this.selected + 1) % this.options.length;
|
||||
if (this.options[optionIndex].charAt(0).toUpperCase() === ch) {
|
||||
if (this.options[optionIndex].text.charAt(0).toUpperCase() === ch && !this.options[optionIndex].isDisabled) {
|
||||
this.select(optionIndex);
|
||||
this.selectList.setFocus([optionIndex]);
|
||||
this.selectList.reveal(this.selectList.getFocus()[0]);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { ISelectBoxDelegate, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { ISelectBoxDelegate, ISelectOptionItem, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
export class SelectBoxNative implements ISelectBoxDelegate {
|
||||
@@ -16,17 +16,18 @@ export class SelectBoxNative implements ISelectBoxDelegate {
|
||||
// {{SQL CARBON EDIT}}
|
||||
public selectElement: HTMLSelectElement;
|
||||
private selectBoxOptions: ISelectBoxOptions;
|
||||
private options: string[];
|
||||
private options: ISelectOptionItem[];
|
||||
private selected: number;
|
||||
private readonly _onDidSelect: Emitter<ISelectData>;
|
||||
private toDispose: IDisposable[];
|
||||
private styles: ISelectBoxStyles;
|
||||
|
||||
constructor(options: string[], selected: number, styles: ISelectBoxStyles, selectBoxOptions?: ISelectBoxOptions) {
|
||||
|
||||
constructor(options: ISelectOptionItem[], selected: number, styles: ISelectBoxStyles, selectBoxOptions?: ISelectBoxOptions) {
|
||||
this.toDispose = [];
|
||||
this.selectBoxOptions = selectBoxOptions || Object.create(null);
|
||||
|
||||
this.options = [];
|
||||
|
||||
this.selectElement = document.createElement('select');
|
||||
|
||||
// Workaround for Electron 2.x
|
||||
@@ -84,15 +85,14 @@ export class SelectBoxNative implements ISelectBoxDelegate {
|
||||
return this._onDidSelect.event;
|
||||
}
|
||||
|
||||
public setOptions(options: string[], selected?: number, disabled?: number): void {
|
||||
public setOptions(options: ISelectOptionItem[], selected?: number): void {
|
||||
|
||||
if (!this.options || !arrays.equals(this.options, options)) {
|
||||
this.options = options;
|
||||
this.selectElement.options.length = 0;
|
||||
|
||||
let i = 0;
|
||||
this.options.forEach((option) => {
|
||||
this.selectElement.add(this.createOption(option, i, disabled === i++));
|
||||
this.options.forEach((option, index) => {
|
||||
this.selectElement.add(this.createOption(option.text, index, option.isDisabled));
|
||||
});
|
||||
|
||||
}
|
||||
@@ -103,7 +103,9 @@ export class SelectBoxNative implements ISelectBoxDelegate {
|
||||
}
|
||||
|
||||
public select(index: number): void {
|
||||
if (index >= 0 && index < this.options.length) {
|
||||
if (this.options.length === 0) {
|
||||
this.selected = 0;
|
||||
} else if (index >= 0 && index < this.options.length) {
|
||||
this.selected = index;
|
||||
} else if (index > this.options.length - 1) {
|
||||
// Adjust index to end of list
|
||||
@@ -114,7 +116,11 @@ export class SelectBoxNative implements ISelectBoxDelegate {
|
||||
}
|
||||
|
||||
this.selectElement.selectedIndex = this.selected;
|
||||
this.selectElement.title = this.options[this.selected];
|
||||
if ((this.selected < this.options.length) && typeof this.options[this.selected].text === 'string') {
|
||||
this.selectElement.title = this.options[this.selected].text;
|
||||
} else {
|
||||
this.selectElement.title = '';
|
||||
}
|
||||
}
|
||||
|
||||
public setAriaLabel(label: string): void {
|
||||
@@ -122,10 +128,6 @@ export class SelectBoxNative implements ISelectBoxDelegate {
|
||||
this.selectElement.setAttribute('aria-label', label);
|
||||
}
|
||||
|
||||
public setDetailsProvider(provider: any): void {
|
||||
console.error('details are not available for native select boxes');
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.focus();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import 'vs/css!./panelview';
|
||||
import { IDisposable, dispose, combinedDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Event, Emitter, chain, filterEvent } from 'vs/base/common/event';
|
||||
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';
|
||||
@@ -77,7 +77,7 @@ export abstract class Panel implements IView {
|
||||
|
||||
set minimumBodySize(size: number) {
|
||||
this._minimumBodySize = size;
|
||||
this._onDidChange.fire();
|
||||
this._onDidChange.fire(undefined);
|
||||
}
|
||||
|
||||
get maximumBodySize(): number {
|
||||
@@ -86,7 +86,7 @@ export abstract class Panel implements IView {
|
||||
|
||||
set maximumBodySize(size: number) {
|
||||
this._maximumBodySize = size;
|
||||
this._onDidChange.fire();
|
||||
this._onDidChange.fire(undefined);
|
||||
}
|
||||
|
||||
private get headerSize(): number {
|
||||
@@ -109,6 +109,8 @@ export abstract class Panel implements IView {
|
||||
return headerSize + maximumBodySize;
|
||||
}
|
||||
|
||||
width: number;
|
||||
|
||||
constructor(options: IPanelOptions = {}) {
|
||||
this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded;
|
||||
this.ariaHeaderLabel = options.ariaHeaderLabel || '';
|
||||
@@ -122,14 +124,16 @@ export abstract class Panel implements IView {
|
||||
return this._expanded;
|
||||
}
|
||||
|
||||
setExpanded(expanded: boolean): void {
|
||||
setExpanded(expanded: boolean): boolean {
|
||||
if (this._expanded === !!expanded) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
this._expanded = !!expanded;
|
||||
this.updateHeader();
|
||||
this._onDidChange.fire(expanded ? this.expandedSize : undefined);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
get headerVisible(): boolean {
|
||||
@@ -143,7 +147,7 @@ export abstract class Panel implements IView {
|
||||
|
||||
this._headerVisible = !!visible;
|
||||
this.updateHeader();
|
||||
this._onDidChange.fire();
|
||||
this._onDidChange.fire(undefined);
|
||||
}
|
||||
|
||||
render(): void {
|
||||
@@ -160,7 +164,7 @@ export abstract class Panel implements IView {
|
||||
|
||||
this.updateHeader();
|
||||
|
||||
const onHeaderKeyDown = chain(domEvent(this.header, 'keydown'))
|
||||
const onHeaderKeyDown = Event.chain(domEvent(this.header, 'keydown'))
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
|
||||
onHeaderKeyDown.filter(e => e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space)
|
||||
@@ -186,12 +190,12 @@ export abstract class Panel implements IView {
|
||||
this.renderBody(body);
|
||||
}
|
||||
|
||||
layout(size: number): void {
|
||||
layout(height: number): void {
|
||||
const headerSize = this.headerVisible ? Panel.HEADER_SIZE : 0;
|
||||
|
||||
if (this.isExpanded()) {
|
||||
this.layoutBody(size - headerSize);
|
||||
this.expandedSize = size;
|
||||
this.layoutBody(height - headerSize, this.width);
|
||||
this.expandedSize = height;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +226,7 @@ export abstract class Panel implements IView {
|
||||
|
||||
protected abstract renderHeader(container: HTMLElement): void;
|
||||
protected abstract renderBody(container: HTMLElement): void;
|
||||
protected abstract layoutBody(size: number): void;
|
||||
protected abstract layoutBody(height: number, width: number): void;
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
@@ -367,6 +371,7 @@ export class PanelView extends Disposable {
|
||||
private dndContext: IDndContext = { draggable: null };
|
||||
private el: HTMLElement;
|
||||
private panelItems: IPanelItem[] = [];
|
||||
private width: number;
|
||||
private splitview: SplitView;
|
||||
private animationTimer: number | null = null;
|
||||
|
||||
@@ -391,11 +396,12 @@ export class PanelView extends Disposable {
|
||||
let shouldAnimate = false;
|
||||
disposables.push(scheduleAtNextAnimationFrame(() => shouldAnimate = true));
|
||||
|
||||
filterEvent(panel.onDidChange, () => shouldAnimate)
|
||||
Event.filter(panel.onDidChange, () => shouldAnimate)
|
||||
(this.setupAnimation, this, disposables);
|
||||
|
||||
const panelItem = { panel, disposable: combinedDisposable(disposables) };
|
||||
this.panelItems.splice(index, 0, panelItem);
|
||||
panel.width = this.width;
|
||||
this.splitview.addView(panel, size, index);
|
||||
|
||||
if (this.dnd) {
|
||||
@@ -451,8 +457,14 @@ export class PanelView extends Disposable {
|
||||
return this.splitview.getViewSize(index);
|
||||
}
|
||||
|
||||
layout(size: number): void {
|
||||
this.splitview.layout(size);
|
||||
layout(height: number, width: number): void {
|
||||
this.width = width;
|
||||
|
||||
for (const panelItem of this.panelItems) {
|
||||
panelItem.panel.width = width;
|
||||
}
|
||||
|
||||
this.splitview.layout(height);
|
||||
}
|
||||
|
||||
private setupAnimation(): void {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import 'vs/css!./splitview';
|
||||
import { IDisposable, combinedDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Event, mapEvent, Emitter } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { clamp } from 'vs/base/common/numbers';
|
||||
@@ -238,11 +238,11 @@ export class SplitView extends Disposable {
|
||||
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey } as ISashEvent)
|
||||
: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX, alt: e.altKey } as ISashEvent);
|
||||
|
||||
const onStart = mapEvent(sash.onDidStart, sashEventMapper);
|
||||
const onStart = Event.map(sash.onDidStart, sashEventMapper);
|
||||
const onStartDisposable = onStart(this.onSashStart, this);
|
||||
const onChange = mapEvent(sash.onDidChange, sashEventMapper);
|
||||
const onChange = Event.map(sash.onDidChange, sashEventMapper);
|
||||
const onChangeDisposable = onChange(this.onSashChange, this);
|
||||
const onEnd = mapEvent(sash.onDidEnd, () => firstIndex(this.sashItems, item => item.sash === sash));
|
||||
const onEnd = Event.map(sash.onDidEnd, () => firstIndex(this.sashItems, item => item.sash === sash));
|
||||
const onEndDisposable = onEnd(this.onSashEnd, this);
|
||||
const onDidResetDisposable = sash.onDidReset(() => this._onDidSashReset.fire(firstIndex(this.sashItems, item => item.sash === sash)));
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface IToolBarOptions {
|
||||
orientation?: ActionsOrientation;
|
||||
actionItemProvider?: IActionItemProvider;
|
||||
ariaLabel?: string;
|
||||
getKeyBinding?: (action: IAction) => ResolvedKeybinding;
|
||||
getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined;
|
||||
actionRunner?: IActionRunner;
|
||||
toggleMenuTitle?: string;
|
||||
anchorAlignmentProvider?: () => AnchorAlignment;
|
||||
@@ -31,7 +31,7 @@ export class ToolBar extends Disposable {
|
||||
private options: IToolBarOptions;
|
||||
private actionBar: ActionBar;
|
||||
private toggleMenuAction: ToggleMenuAction;
|
||||
private toggleMenuActionItem: DropdownMenuActionItem;
|
||||
private toggleMenuActionItem?: DropdownMenuActionItem;
|
||||
private hasSecondaryActions: boolean;
|
||||
private lookupKeybindings: boolean;
|
||||
|
||||
@@ -72,9 +72,9 @@ export class ToolBar extends Disposable {
|
||||
'toolbar-toggle-more',
|
||||
this.options.anchorAlignmentProvider
|
||||
);
|
||||
this.toggleMenuActionItem.setActionContext(this.actionBar.context);
|
||||
this.toggleMenuActionItem!.setActionContext(this.actionBar.context);
|
||||
|
||||
return this.toggleMenuActionItem;
|
||||
return this.toggleMenuActionItem || null;
|
||||
}
|
||||
|
||||
return options.actionItemProvider ? options.actionItemProvider(action) : null;
|
||||
@@ -118,8 +118,8 @@ export class ToolBar extends Disposable {
|
||||
let primaryActionsToSet = primaryActions ? primaryActions.slice(0) : [];
|
||||
|
||||
// Inject additional action to open secondary actions if present
|
||||
this.hasSecondaryActions = secondaryActions && secondaryActions.length > 0;
|
||||
if (this.hasSecondaryActions) {
|
||||
this.hasSecondaryActions = !!(secondaryActions && secondaryActions.length > 0);
|
||||
if (this.hasSecondaryActions && secondaryActions) {
|
||||
this.toggleMenuAction.menuActions = secondaryActions.slice(0);
|
||||
primaryActionsToSet.push(this.toggleMenuAction);
|
||||
}
|
||||
@@ -132,10 +132,10 @@ export class ToolBar extends Disposable {
|
||||
};
|
||||
}
|
||||
|
||||
private getKeybindingLabel(action: IAction): string {
|
||||
const key = this.lookupKeybindings ? this.options.getKeyBinding(action) : void 0;
|
||||
private getKeybindingLabel(action: IAction): string | undefined {
|
||||
const key = this.lookupKeybindings && this.options.getKeyBinding ? this.options.getKeyBinding(action) : undefined;
|
||||
|
||||
return key ? key.getLabel() : void 0;
|
||||
return (key && key.getLabel()) || undefined;
|
||||
}
|
||||
|
||||
addPrimaryAction(primaryAction: IAction): () => void {
|
||||
@@ -157,7 +157,7 @@ export class ToolBar extends Disposable {
|
||||
dispose(): void {
|
||||
if (this.toggleMenuActionItem) {
|
||||
this.toggleMenuActionItem.dispose();
|
||||
this.toggleMenuActionItem = void 0;
|
||||
this.toggleMenuActionItem = undefined;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
@@ -173,7 +173,7 @@ class ToggleMenuAction extends Action {
|
||||
|
||||
constructor(toggleDropdownMenu: () => void, title?: string) {
|
||||
title = title || nls.localize('moreActions', "More Actions...");
|
||||
super(ToggleMenuAction.ID, title, null, true);
|
||||
super(ToggleMenuAction.ID, title, undefined, true);
|
||||
|
||||
this.toggleDropdownMenu = toggleDropdownMenu;
|
||||
}
|
||||
|
||||
@@ -3,64 +3,81 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ComposedTreeDelegate, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
|
||||
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Emitter, Event, mapEvent } from 'vs/base/common/event';
|
||||
import { timeout, always } from 'vs/base/common/async';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { timeout, always, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { toggleClass } from 'vs/base/browser/dom';
|
||||
import { Iterator } from 'vs/base/common/iterator';
|
||||
import { IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
|
||||
import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { toggleClass } from 'vs/base/browser/dom';
|
||||
|
||||
export interface IDataSource<T extends NonNullable<any>> {
|
||||
hasChildren(element: T | null): boolean;
|
||||
getChildren(element: T | null): Thenable<T[]>;
|
||||
const enum AsyncDataTreeNodeState {
|
||||
Uninitialized = 'uninitialized',
|
||||
Loaded = 'loaded',
|
||||
Loading = 'loading'
|
||||
}
|
||||
|
||||
enum AsyncDataTreeNodeState {
|
||||
Uninitialized,
|
||||
Loaded,
|
||||
Loading,
|
||||
Slow
|
||||
}
|
||||
|
||||
interface IAsyncDataTreeNode<T extends NonNullable<any>> {
|
||||
element: T | null;
|
||||
readonly parent: IAsyncDataTreeNode<T> | null;
|
||||
interface IAsyncDataTreeNode<TInput, T> {
|
||||
element: TInput | T;
|
||||
readonly parent: IAsyncDataTreeNode<TInput, T> | null;
|
||||
readonly children: IAsyncDataTreeNode<TInput, T>[];
|
||||
readonly id?: string | null;
|
||||
readonly children?: IAsyncDataTreeNode<T>[];
|
||||
state: AsyncDataTreeNodeState;
|
||||
hasChildren: boolean;
|
||||
needsRefresh: boolean;
|
||||
slow: boolean;
|
||||
disposed: boolean;
|
||||
}
|
||||
|
||||
function isAncestor<TInput, T>(ancestor: IAsyncDataTreeNode<TInput, T>, descendant: IAsyncDataTreeNode<TInput, T>): boolean {
|
||||
if (!descendant.parent) {
|
||||
return false;
|
||||
} else if (descendant.parent === ancestor) {
|
||||
return true;
|
||||
} else {
|
||||
return isAncestor(ancestor, descendant.parent);
|
||||
}
|
||||
}
|
||||
|
||||
function intersects<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, other: IAsyncDataTreeNode<TInput, T>): boolean {
|
||||
return node === other || isAncestor(node, other) || isAncestor(other, node);
|
||||
}
|
||||
|
||||
interface IDataTreeListTemplateData<T> {
|
||||
templateData: T;
|
||||
}
|
||||
|
||||
class AsyncDataTreeNodeWrapper<T, TFilterData> implements ITreeNode<T, TFilterData> {
|
||||
class AsyncDataTreeNodeWrapper<TInput, T, TFilterData> implements ITreeNode<TInput | T, TFilterData> {
|
||||
|
||||
get element(): T { return this.node.element!.element!; }
|
||||
get element(): T { return this.node.element!.element as T; }
|
||||
get parent(): ITreeNode<T, TFilterData> | undefined { return this.node.parent && new AsyncDataTreeNodeWrapper(this.node.parent); }
|
||||
get children(): ITreeNode<T, TFilterData>[] { return this.node.children.map(node => new AsyncDataTreeNodeWrapper(node)); }
|
||||
get depth(): number { return this.node.depth; }
|
||||
get visibleChildrenCount(): number { return this.node.visibleChildrenCount; }
|
||||
get visibleChildIndex(): number { return this.node.visibleChildIndex; }
|
||||
get collapsible(): boolean { return this.node.collapsible; }
|
||||
get collapsed(): boolean { return this.node.collapsed; }
|
||||
get visible(): boolean { return this.node.visible; }
|
||||
get filterData(): TFilterData | undefined { return this.node.filterData; }
|
||||
|
||||
constructor(private node: ITreeNode<IAsyncDataTreeNode<T> | null, TFilterData>) { }
|
||||
constructor(private node: ITreeNode<IAsyncDataTreeNode<TInput, T> | null, TFilterData>) { }
|
||||
}
|
||||
|
||||
class DataTreeRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<IAsyncDataTreeNode<T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
|
||||
class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
|
||||
|
||||
readonly templateId: string;
|
||||
private renderedNodes = new Map<IAsyncDataTreeNode<T>, IDataTreeListTemplateData<TTemplateData>>();
|
||||
private renderedNodes = new Map<IAsyncDataTreeNode<TInput, T>, IDataTreeListTemplateData<TTemplateData>>();
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
|
||||
readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<T>>
|
||||
readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<TInput, T>>
|
||||
) {
|
||||
this.templateId = renderer.templateId;
|
||||
}
|
||||
@@ -70,17 +87,19 @@ class DataTreeRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<I
|
||||
return { templateData };
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
||||
renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
||||
this.renderer.renderElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData);
|
||||
}
|
||||
|
||||
renderTwistie(element: IAsyncDataTreeNode<T>, twistieElement: HTMLElement): boolean {
|
||||
toggleClass(twistieElement, 'loading', element.state === AsyncDataTreeNodeState.Slow);
|
||||
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
|
||||
toggleClass(twistieElement, 'loading', element.slow);
|
||||
return false;
|
||||
}
|
||||
|
||||
disposeElement(node: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
||||
this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData);
|
||||
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
||||
if (this.renderer.disposeElement) {
|
||||
this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData);
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
||||
@@ -93,24 +112,24 @@ class DataTreeRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<I
|
||||
}
|
||||
}
|
||||
|
||||
function asTreeEvent<T>(e: ITreeEvent<IAsyncDataTreeNode<T>>): ITreeEvent<T> {
|
||||
function asTreeEvent<TInput, T>(e: ITreeEvent<IAsyncDataTreeNode<TInput, T>>): ITreeEvent<T> {
|
||||
return {
|
||||
browserEvent: e.browserEvent,
|
||||
elements: e.elements.map(e => e.element!)
|
||||
elements: e.elements.map(e => e.element as T)
|
||||
};
|
||||
}
|
||||
|
||||
function asTreeMouseEvent<T>(e: ITreeMouseEvent<IAsyncDataTreeNode<T>>): ITreeMouseEvent<T> {
|
||||
function asTreeMouseEvent<TInput, T>(e: ITreeMouseEvent<IAsyncDataTreeNode<TInput, T>>): ITreeMouseEvent<T> {
|
||||
return {
|
||||
browserEvent: e.browserEvent,
|
||||
element: e.element && e.element.element!
|
||||
element: e.element && e.element.element as T
|
||||
};
|
||||
}
|
||||
|
||||
function asTreeContextMenuEvent<T>(e: ITreeContextMenuEvent<IAsyncDataTreeNode<T>>): ITreeContextMenuEvent<T> {
|
||||
function asTreeContextMenuEvent<TInput, T>(e: ITreeContextMenuEvent<IAsyncDataTreeNode<TInput, T>>): ITreeContextMenuEvent<T> {
|
||||
return {
|
||||
browserEvent: e.browserEvent,
|
||||
element: e.element && e.element.element!,
|
||||
element: e.element && e.element.element as T,
|
||||
anchor: e.anchor
|
||||
};
|
||||
}
|
||||
@@ -125,14 +144,56 @@ export interface IChildrenResolutionEvent<T> {
|
||||
readonly reason: ChildrenResolutionReason;
|
||||
}
|
||||
|
||||
function asObjectTreeOptions<T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<T>, TFilterData> | undefined {
|
||||
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 data;
|
||||
}
|
||||
|
||||
class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IAsyncDataTreeNode<TInput, T>> {
|
||||
|
||||
constructor(private dnd: ITreeDragAndDrop<T>) { }
|
||||
|
||||
getDragURI(node: IAsyncDataTreeNode<TInput, T>): string | null {
|
||||
return this.dnd.getDragURI(node.element as T);
|
||||
}
|
||||
|
||||
getDragLabel(nodes: IAsyncDataTreeNode<TInput, T>[]): string | undefined {
|
||||
if (this.dnd.getDragLabel) {
|
||||
return this.dnd.getDragLabel(nodes.map(node => node.element as T));
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
|
||||
if (this.dnd.onDragStart) {
|
||||
this.dnd.onDragStart(asAsyncDataTreeDragAndDropData(data), originalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
onDragOver(data: IDragAndDropData, targetNode: IAsyncDataTreeNode<TInput, T> | undefined, targetIndex: number | undefined, originalEvent: DragEvent, raw = true): boolean | IListDragOverReaction {
|
||||
return this.dnd.onDragOver(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, originalEvent);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<TInput, T>, TFilterData> | undefined {
|
||||
return options && {
|
||||
...options,
|
||||
collapseByDefault: true,
|
||||
identityProvider: options.identityProvider && {
|
||||
getId(el) {
|
||||
return options.identityProvider!.getId(el.element!);
|
||||
return options.identityProvider!.getId(el.element as T);
|
||||
}
|
||||
},
|
||||
dnd: options.dnd && new AsyncDataTreeNodeListDragAndDrop(options.dnd),
|
||||
multipleSelectionController: options.multipleSelectionController && {
|
||||
isSelectionSingleChangeEvent(e) {
|
||||
return options.multipleSelectionController!.isSelectionSingleChangeEvent({ ...e, element: e.element } as any);
|
||||
@@ -143,90 +204,137 @@ function asObjectTreeOptions<T, TFilterData>(options?: IAsyncDataTreeOptions<T,
|
||||
},
|
||||
accessibilityProvider: options.accessibilityProvider && {
|
||||
getAriaLabel(e) {
|
||||
return options.accessibilityProvider!.getAriaLabel(e.element!);
|
||||
return options.accessibilityProvider!.getAriaLabel(e.element as T);
|
||||
}
|
||||
},
|
||||
filter: options.filter && {
|
||||
filter(element, parentVisibility) {
|
||||
return options.filter!.filter(element.element!, parentVisibility);
|
||||
filter(e, parentVisibility) {
|
||||
return options.filter!.filter(e.element as T, parentVisibility);
|
||||
}
|
||||
}
|
||||
},
|
||||
keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && {
|
||||
getKeyboardNavigationLabel(e) {
|
||||
return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e.element as T);
|
||||
}
|
||||
},
|
||||
sorter: undefined
|
||||
};
|
||||
}
|
||||
|
||||
function asTreeElement<T>(node: IAsyncDataTreeNode<T>): ITreeElement<IAsyncDataTreeNode<T>> {
|
||||
function asTreeElement<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
|
||||
let collapsed: boolean | undefined;
|
||||
|
||||
if (viewStateContext && node.id) {
|
||||
collapsed = viewStateContext.viewState.expanded.indexOf(node.id) === -1;
|
||||
}
|
||||
|
||||
return {
|
||||
element: node,
|
||||
children: Iterator.map(Iterator.fromArray(node.children!), asTreeElement)
|
||||
children: Iterator.map(Iterator.fromArray(node.children), child => asTreeElement(child, viewStateContext)),
|
||||
collapsible: node.hasChildren,
|
||||
collapsed
|
||||
};
|
||||
}
|
||||
|
||||
export interface IAsyncDataTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
|
||||
export interface IAsyncDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { }
|
||||
|
||||
export interface IAsyncDataTreeOptions<T, TFilterData = void> extends IAsyncDataTreeOptionsUpdate, IAbstractTreeOptions<T, TFilterData> {
|
||||
identityProvider?: IIdentityProvider<T>;
|
||||
sorter?: ITreeSorter<T>;
|
||||
autoExpandSingleChildren?: boolean;
|
||||
}
|
||||
|
||||
export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> implements IDisposable {
|
||||
export interface IAsyncDataTreeViewState {
|
||||
readonly focus: string[];
|
||||
readonly selection: string[];
|
||||
readonly expanded: string[];
|
||||
}
|
||||
|
||||
interface IAsyncDataTreeViewStateContext<TInput, T> {
|
||||
readonly viewState: IAsyncDataTreeViewState;
|
||||
readonly selection: IAsyncDataTreeNode<TInput, T>[];
|
||||
readonly focus: IAsyncDataTreeNode<TInput, T>[];
|
||||
}
|
||||
|
||||
export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable {
|
||||
|
||||
private readonly tree: ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
|
||||
private readonly root: IAsyncDataTreeNode<TInput, T>;
|
||||
private readonly renderedNodes = new Map<null | T, IAsyncDataTreeNode<TInput, T>>();
|
||||
private readonly sorter?: ITreeSorter<T>;
|
||||
|
||||
private readonly subTreeRefreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, Promise<void>>();
|
||||
private readonly refreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, CancelablePromise<T[]>>();
|
||||
|
||||
private readonly tree: ObjectTree<IAsyncDataTreeNode<T>, TFilterData>;
|
||||
private readonly root: IAsyncDataTreeNode<T>;
|
||||
private readonly nodes = new Map<T | null, IAsyncDataTreeNode<T>>();
|
||||
private readonly refreshPromises = new Map<IAsyncDataTreeNode<T>, Thenable<void>>();
|
||||
private readonly identityProvider?: IIdentityProvider<T>;
|
||||
private readonly autoExpandSingleChildren: boolean;
|
||||
|
||||
private readonly _onDidChangeNodeState = new Emitter<IAsyncDataTreeNode<T>>();
|
||||
private readonly _onDidRender = new Emitter<void>();
|
||||
private readonly _onDidChangeNodeSlowState = new Emitter<IAsyncDataTreeNode<TInput, T>>();
|
||||
|
||||
protected readonly disposables: IDisposable[] = [];
|
||||
|
||||
get onDidChangeFocus(): Event<ITreeEvent<T>> { return mapEvent(this.tree.onDidChangeFocus, asTreeEvent); }
|
||||
get onDidChangeSelection(): Event<ITreeEvent<T>> { return mapEvent(this.tree.onDidChangeSelection, asTreeEvent); }
|
||||
get onDidChangeCollapseState(): Event<T> { return mapEvent(this.tree.onDidChangeCollapseState, e => e.element!.element!); }
|
||||
get onDidScroll(): Event<void> { return this.tree.onDidScroll; }
|
||||
|
||||
private readonly _onDidResolveChildren = new Emitter<IChildrenResolutionEvent<T>>();
|
||||
readonly onDidResolveChildren: Event<IChildrenResolutionEvent<T>> = this._onDidResolveChildren.event;
|
||||
get onDidChangeFocus(): Event<ITreeEvent<T>> { return Event.map(this.tree.onDidChangeFocus, asTreeEvent); }
|
||||
get onDidChangeSelection(): Event<ITreeEvent<T>> { return Event.map(this.tree.onDidChangeSelection, asTreeEvent); }
|
||||
get onDidOpen(): Event<ITreeEvent<T>> { return Event.map(this.tree.onDidOpen, asTreeEvent); }
|
||||
|
||||
get onMouseClick(): Event<ITreeMouseEvent<T>> { return mapEvent(this.tree.onMouseClick, asTreeMouseEvent); }
|
||||
get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return mapEvent(this.tree.onMouseDblClick, asTreeMouseEvent); }
|
||||
get onContextMenu(): Event<ITreeContextMenuEvent<T>> { return mapEvent(this.tree.onContextMenu, asTreeContextMenuEvent); }
|
||||
get onMouseClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.tree.onMouseClick, asTreeMouseEvent); }
|
||||
get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.tree.onMouseDblClick, asTreeMouseEvent); }
|
||||
get onContextMenu(): Event<ITreeContextMenuEvent<T>> { return Event.map(this.tree.onContextMenu, asTreeContextMenuEvent); }
|
||||
get onDidFocus(): Event<void> { return this.tree.onDidFocus; }
|
||||
get onDidBlur(): Event<void> { return this.tree.onDidBlur; }
|
||||
|
||||
get filterOnType(): boolean { return this.tree.filterOnType; }
|
||||
get openOnSingleClick(): boolean { return this.tree.openOnSingleClick; }
|
||||
|
||||
get onDidDispose(): Event<void> { return this.tree.onDidDispose; }
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
|
||||
private dataSource: IDataSource<T>,
|
||||
options?: IAsyncDataTreeOptions<T, TFilterData>
|
||||
private dataSource: IAsyncDataSource<TInput, T>,
|
||||
options: IAsyncDataTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
this.identityProvider = options && options.identityProvider;
|
||||
this.identityProvider = options.identityProvider;
|
||||
this.autoExpandSingleChildren = typeof options.autoExpandSingleChildren === 'undefined' ? false : options.autoExpandSingleChildren;
|
||||
this.sorter = options.sorter;
|
||||
|
||||
const objectTreeDelegate = new ComposedTreeDelegate<T | null, IAsyncDataTreeNode<T>>(delegate);
|
||||
const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeState.event));
|
||||
const objectTreeOptions = asObjectTreeOptions(options) || {};
|
||||
objectTreeOptions.collapseByDefault = true;
|
||||
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
|
||||
const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeSlowState.event));
|
||||
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
|
||||
|
||||
this.tree = new ObjectTree(container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
|
||||
|
||||
this.root = {
|
||||
element: null,
|
||||
element: undefined!,
|
||||
parent: null,
|
||||
children: [],
|
||||
state: AsyncDataTreeNodeState.Uninitialized,
|
||||
hasChildren: true,
|
||||
needsRefresh: false,
|
||||
disposed: false,
|
||||
slow: false
|
||||
};
|
||||
|
||||
if (this.identityProvider) {
|
||||
this.root = {
|
||||
...this.root,
|
||||
id: null,
|
||||
children: [],
|
||||
id: null
|
||||
};
|
||||
}
|
||||
|
||||
this.nodes.set(null, this.root);
|
||||
this.renderedNodes.set(null, this.root);
|
||||
|
||||
this.tree.onDidChangeCollapseState(this._onDidChangeCollapseState, this, this.disposables);
|
||||
}
|
||||
|
||||
updateOptions(options: IAsyncDataTreeOptionsUpdate = {}): void {
|
||||
this.tree.updateOptions(options);
|
||||
}
|
||||
|
||||
// Widget
|
||||
|
||||
getHTMLElement(): HTMLElement {
|
||||
@@ -261,50 +369,110 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
this.tree.domFocus();
|
||||
}
|
||||
|
||||
layout(height?: number): void {
|
||||
this.tree.layout(height);
|
||||
layout(height?: number, width?: number): void {
|
||||
this.tree.layout(height, width);
|
||||
}
|
||||
|
||||
style(styles: IListStyles): void {
|
||||
this.tree.style(styles);
|
||||
}
|
||||
|
||||
// Data Tree
|
||||
// Model
|
||||
|
||||
refresh(element: T | null, recursive = true): Thenable<void> {
|
||||
return this.refreshNode(this.getDataNode(element), recursive, ChildrenResolutionReason.Refresh);
|
||||
getInput(): TInput | undefined {
|
||||
return this.root.element as TInput;
|
||||
}
|
||||
|
||||
async setInput(input: TInput, viewState?: IAsyncDataTreeViewState): Promise<void> {
|
||||
this.refreshPromises.forEach(promise => promise.cancel());
|
||||
this.refreshPromises.clear();
|
||||
|
||||
this.root.element = input!;
|
||||
|
||||
const viewStateContext = viewState && { viewState, focus: [], selection: [] } as IAsyncDataTreeViewStateContext<TInput, T>;
|
||||
|
||||
await this.updateChildren(input, true, viewStateContext);
|
||||
|
||||
if (viewStateContext) {
|
||||
this.tree.setFocus(viewStateContext.focus);
|
||||
this.tree.setSelection(viewStateContext.selection);
|
||||
}
|
||||
}
|
||||
|
||||
async updateChildren(element: TInput | T = this.root.element, recursive = true, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
if (typeof this.root.element === 'undefined') {
|
||||
throw new Error('Tree input not set');
|
||||
}
|
||||
|
||||
if (this.root.state === AsyncDataTreeNodeState.Loading) {
|
||||
await this.subTreeRefreshPromises.get(this.root)!;
|
||||
await Event.toPromise(this._onDidRender.event);
|
||||
}
|
||||
|
||||
await this.refreshAndRenderNode(this.getDataNode(element), recursive, ChildrenResolutionReason.Refresh, viewStateContext);
|
||||
}
|
||||
|
||||
hasNode(element: TInput | T): boolean {
|
||||
return element === this.root.element || this.renderedNodes.has(element as T);
|
||||
}
|
||||
|
||||
// View
|
||||
|
||||
refresh(element: T): void {
|
||||
const node = this.getDataNode(element);
|
||||
this.tree.refresh(node);
|
||||
}
|
||||
|
||||
updateWidth(element: T): void {
|
||||
const node = this.getDataNode(element);
|
||||
this.tree.updateWidth(node);
|
||||
}
|
||||
|
||||
// Tree
|
||||
|
||||
getNode(element: T | null): ITreeNode<T | null, TFilterData> {
|
||||
getNode(element: TInput | T = this.root.element): ITreeNode<TInput | T, TFilterData> {
|
||||
const dataNode = this.getDataNode(element);
|
||||
const node = this.tree.getNode(dataNode === this.root ? null : dataNode);
|
||||
return new AsyncDataTreeNodeWrapper<T | null, TFilterData>(node);
|
||||
return new AsyncDataTreeNodeWrapper<TInput, T, TFilterData>(node);
|
||||
}
|
||||
|
||||
collapse(element: T): boolean {
|
||||
return this.tree.collapse(this.getDataNode(element));
|
||||
collapse(element: T, recursive: boolean = false): boolean {
|
||||
const node = this.getDataNode(element);
|
||||
return this.tree.collapse(node === this.root ? null : node, recursive);
|
||||
}
|
||||
|
||||
async expand(element: T): Promise<boolean> {
|
||||
async expand(element: T, recursive: boolean = false): Promise<boolean> {
|
||||
if (typeof this.root.element === 'undefined') {
|
||||
throw new Error('Tree input not set');
|
||||
}
|
||||
|
||||
if (this.root.state === AsyncDataTreeNodeState.Loading) {
|
||||
await this.subTreeRefreshPromises.get(this.root)!;
|
||||
await Event.toPromise(this._onDidRender.event);
|
||||
}
|
||||
|
||||
const node = this.getDataNode(element);
|
||||
|
||||
if (!this.tree.isCollapsed(node)) {
|
||||
if (node !== this.root && node.state !== AsyncDataTreeNodeState.Loading && !this.tree.isCollapsed(node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.tree.expand(node);
|
||||
const result = this.tree.expand(node === this.root ? null : node, recursive);
|
||||
|
||||
if (node.state !== AsyncDataTreeNodeState.Loaded) {
|
||||
await this.refreshNode(node, false, ChildrenResolutionReason.Expand);
|
||||
if (node.state === AsyncDataTreeNodeState.Loading) {
|
||||
await this.subTreeRefreshPromises.get(node)!;
|
||||
await Event.toPromise(this._onDidRender.event);
|
||||
}
|
||||
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
toggleCollapsed(element: T): void {
|
||||
this.tree.toggleCollapsed(this.getDataNode(element));
|
||||
toggleCollapsed(element: T, recursive: boolean = false): boolean {
|
||||
return this.tree.toggleCollapsed(this.getDataNode(element), recursive);
|
||||
}
|
||||
|
||||
expandAll(): void {
|
||||
this.tree.expandAll();
|
||||
}
|
||||
|
||||
collapseAll(): void {
|
||||
@@ -319,8 +487,8 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
return this.tree.isCollapsed(this.getDataNode(element));
|
||||
}
|
||||
|
||||
isExpanded(element: T): boolean {
|
||||
return this.tree.isExpanded(this.getDataNode(element));
|
||||
toggleKeyboardNavigation(): void {
|
||||
this.tree.toggleKeyboardNavigation();
|
||||
}
|
||||
|
||||
refilter(): void {
|
||||
@@ -334,7 +502,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
|
||||
getSelection(): T[] {
|
||||
const nodes = this.tree.getSelection();
|
||||
return nodes.map(n => n!.element!);
|
||||
return nodes.map(n => n!.element as T);
|
||||
}
|
||||
|
||||
setFocus(elements: T[], browserEvent?: UIEvent): void {
|
||||
@@ -368,7 +536,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
|
||||
getFocus(): T[] {
|
||||
const nodes = this.tree.getFocus();
|
||||
return nodes.map(n => n!.element!);
|
||||
return nodes.map(n => n!.element as T);
|
||||
}
|
||||
|
||||
open(elements: T[]): void {
|
||||
@@ -386,21 +554,15 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
|
||||
// Tree navigation
|
||||
|
||||
getParentElement(element: T): T | null {
|
||||
getParentElement(element: T): TInput | T {
|
||||
const node = this.tree.getParentElement(this.getDataNode(element));
|
||||
return node && node.element;
|
||||
return (node && node.element)!;
|
||||
}
|
||||
|
||||
getFirstElementChild(element: T | null = null): T | null | undefined {
|
||||
getFirstElementChild(element: TInput | T = this.root.element): TInput | T | undefined {
|
||||
const dataNode = this.getDataNode(element);
|
||||
const node = this.tree.getFirstElementChild(dataNode === this.root ? null : dataNode);
|
||||
return node && node.element;
|
||||
}
|
||||
|
||||
getLastElementAncestor(element: T | null = null): T | null | undefined {
|
||||
const dataNode = this.getDataNode(element);
|
||||
const node = this.tree.getLastElementAncestor(dataNode === this.root ? null : dataNode);
|
||||
return node && node.element;
|
||||
return (node && node.element)!;
|
||||
}
|
||||
|
||||
// List
|
||||
@@ -411,175 +573,287 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
|
||||
|
||||
// Implementation
|
||||
|
||||
private getDataNode(element: T | null): IAsyncDataTreeNode<T> {
|
||||
const node: IAsyncDataTreeNode<T> = this.nodes.get(element);
|
||||
private getDataNode(element: TInput | T): IAsyncDataTreeNode<TInput, T> {
|
||||
const node: IAsyncDataTreeNode<TInput, T> | undefined = this.renderedNodes.get((element === this.root.element ? null : element) as T);
|
||||
|
||||
if (typeof node === 'undefined') {
|
||||
if (!node) {
|
||||
throw new Error(`Data tree node not found: ${element}`);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private async refreshNode(node: IAsyncDataTreeNode<T>, recursive: boolean, reason: ChildrenResolutionReason): Promise<void> {
|
||||
await this._refreshNode(node, recursive, reason);
|
||||
private async refreshAndRenderNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, reason: ChildrenResolutionReason, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
await this.refreshNode(node, recursive, viewStateContext);
|
||||
this.render(node, viewStateContext);
|
||||
|
||||
if (recursive && node.children) {
|
||||
await Promise.all(node.children.map(child => this.refreshNode(child, recursive, reason)));
|
||||
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 _refreshNode(node: IAsyncDataTreeNode<T>, recursive: boolean, reason: ChildrenResolutionReason): Thenable<void> {
|
||||
private async refreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
if (node.disposed) {
|
||||
console.error('Async data tree node is disposed');
|
||||
return;
|
||||
}
|
||||
|
||||
let result: Promise<void> | undefined;
|
||||
|
||||
this.subTreeRefreshPromises.forEach((refreshPromise, refreshNode) => {
|
||||
if (!result && intersects(refreshNode, node)) {
|
||||
result = refreshPromise.then(() => this.refreshNode(node, recursive, viewStateContext));
|
||||
}
|
||||
});
|
||||
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = this.doRefreshSubTree(node, recursive, viewStateContext);
|
||||
this.subTreeRefreshPromises.set(node, result);
|
||||
|
||||
try {
|
||||
await result;
|
||||
} finally {
|
||||
this.subTreeRefreshPromises.delete(node);
|
||||
}
|
||||
}
|
||||
|
||||
private async doRefreshSubTree(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
node.state = AsyncDataTreeNodeState.Loading;
|
||||
|
||||
try {
|
||||
await this.doRefreshNode(node, recursive, viewStateContext);
|
||||
|
||||
if (recursive) {
|
||||
const childrenToRefresh = node.children
|
||||
.filter(child => {
|
||||
if (child.needsRefresh) {
|
||||
child.needsRefresh = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO@joao: is this still needed?
|
||||
if (child.hasChildren && child.state === AsyncDataTreeNodeState.Loaded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!viewStateContext || !child.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return viewStateContext.viewState.expanded.indexOf(child.id) > -1;
|
||||
});
|
||||
|
||||
await Promise.all(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext)));
|
||||
}
|
||||
} finally {
|
||||
node.state = AsyncDataTreeNodeState.Loaded;
|
||||
}
|
||||
}
|
||||
|
||||
private async doRefreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
|
||||
node.hasChildren = !!this.dataSource.hasChildren(node.element!);
|
||||
|
||||
let childrenPromise: Promise<T[]>;
|
||||
|
||||
if (!node.hasChildren) {
|
||||
childrenPromise = Promise.resolve([]);
|
||||
} else {
|
||||
const slowTimeout = timeout(800);
|
||||
|
||||
slowTimeout.then(() => {
|
||||
node.slow = true;
|
||||
this._onDidChangeNodeSlowState.fire(node);
|
||||
}, _ => null);
|
||||
|
||||
childrenPromise = always(this.doGetChildren(node), () => slowTimeout.cancel());
|
||||
}
|
||||
|
||||
try {
|
||||
const children = await childrenPromise;
|
||||
this.setChildren(node, children, recursive, viewStateContext);
|
||||
} catch (err) {
|
||||
node.needsRefresh = true;
|
||||
|
||||
if (node !== this.root) {
|
||||
this.tree.collapse(node === this.root ? null : node);
|
||||
}
|
||||
|
||||
if (isPromiseCanceledError(err)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw err;
|
||||
} finally {
|
||||
if (node.slow) {
|
||||
node.slow = false;
|
||||
this._onDidChangeNodeSlowState.fire(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private doGetChildren(node: IAsyncDataTreeNode<TInput, T>): Promise<T[]> {
|
||||
let result = this.refreshPromises.get(node);
|
||||
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = this.doRefresh(node, recursive, reason);
|
||||
result = createCancelablePromise(async () => {
|
||||
const children = await this.dataSource.getChildren(node.element!);
|
||||
|
||||
if (this.sorter) {
|
||||
children.sort(this.sorter.compare.bind(this.sorter));
|
||||
}
|
||||
|
||||
return children;
|
||||
});
|
||||
this.refreshPromises.set(node, result);
|
||||
return always(result, () => this.refreshPromises.delete(node));
|
||||
}
|
||||
|
||||
private doRefresh(node: IAsyncDataTreeNode<T>, recursive: boolean, reason: ChildrenResolutionReason): Thenable<void> {
|
||||
const hasChildren = !!this.dataSource.hasChildren(node.element);
|
||||
|
||||
if (!hasChildren) {
|
||||
this.setChildren(node, [], recursive);
|
||||
return Promise.resolve();
|
||||
} else if (node !== this.root && (!this.tree.isCollapsible(node) || this.tree.isCollapsed(node))) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
node.state = AsyncDataTreeNodeState.Loading;
|
||||
this._onDidChangeNodeState.fire(node);
|
||||
|
||||
const slowTimeout = timeout(800);
|
||||
|
||||
slowTimeout.then(() => {
|
||||
node.state = AsyncDataTreeNodeState.Slow;
|
||||
this._onDidChangeNodeState.fire(node);
|
||||
}, _ => null);
|
||||
|
||||
return this.dataSource.getChildren(node.element)
|
||||
.then(children => {
|
||||
slowTimeout.cancel();
|
||||
node.state = AsyncDataTreeNodeState.Loaded;
|
||||
this._onDidChangeNodeState.fire(node);
|
||||
|
||||
this.setChildren(node, children, recursive);
|
||||
this._onDidResolveChildren.fire({ element: node.element, reason });
|
||||
}, err => {
|
||||
slowTimeout.cancel();
|
||||
node.state = AsyncDataTreeNodeState.Uninitialized;
|
||||
this._onDidChangeNodeState.fire(node);
|
||||
|
||||
if (node !== this.root) {
|
||||
this.tree.collapse(node);
|
||||
}
|
||||
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _onDidChangeCollapseState(treeNode: ITreeNode<IAsyncDataTreeNode<T>, any>): void {
|
||||
if (!treeNode.collapsed && treeNode.element.state === AsyncDataTreeNodeState.Uninitialized) {
|
||||
this.refreshNode(treeNode.element, false, ChildrenResolutionReason.Expand);
|
||||
}
|
||||
}
|
||||
|
||||
private setChildren(node: IAsyncDataTreeNode<T>, childrenElements: T[], recursive: boolean): void {
|
||||
const children = childrenElements.map<ITreeElement<IAsyncDataTreeNode<T>>>(element => {
|
||||
if (!this.identityProvider) {
|
||||
return {
|
||||
element: {
|
||||
element,
|
||||
parent: node,
|
||||
state: AsyncDataTreeNodeState.Uninitialized
|
||||
},
|
||||
collapsible: !!this.dataSource.hasChildren(element),
|
||||
collapsed: true
|
||||
};
|
||||
private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent<IAsyncDataTreeNode<TInput, T>, any>): void {
|
||||
if (!node.collapsed && (node.element.state === AsyncDataTreeNodeState.Uninitialized || node.element.needsRefresh)) {
|
||||
if (deep) {
|
||||
this.collapse(node.element.element as T);
|
||||
} else {
|
||||
this.refreshAndRenderNode(node.element, false, ChildrenResolutionReason.Expand)
|
||||
.catch(onUnexpectedError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const nodeChildren = new Map<string, IAsyncDataTreeNode<T>>();
|
||||
private setChildren(node: IAsyncDataTreeNode<TInput, T>, childrenElements: T[], recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
|
||||
let nodeChildren: Map<string, IAsyncDataTreeNode<TInput, T>> | undefined;
|
||||
|
||||
for (const child of node.children!) {
|
||||
if (this.identityProvider) {
|
||||
nodeChildren = new Map();
|
||||
|
||||
for (const child of node.children) {
|
||||
nodeChildren.set(child.id!, child);
|
||||
}
|
||||
}
|
||||
|
||||
const children = childrenElements.map<IAsyncDataTreeNode<TInput, T>>(element => {
|
||||
if (!this.identityProvider) {
|
||||
const hasChildren = !!this.dataSource.hasChildren(element);
|
||||
|
||||
return {
|
||||
element,
|
||||
parent: node,
|
||||
children: [],
|
||||
state: AsyncDataTreeNodeState.Uninitialized,
|
||||
hasChildren,
|
||||
needsRefresh: false,
|
||||
disposed: false,
|
||||
slow: false
|
||||
};
|
||||
}
|
||||
|
||||
const id = this.identityProvider.getId(element).toString();
|
||||
const asyncDataTreeNode = nodeChildren.get(id);
|
||||
const asyncDataTreeNode = nodeChildren!.get(id);
|
||||
|
||||
if (!asyncDataTreeNode) {
|
||||
return {
|
||||
element: {
|
||||
element,
|
||||
parent: node,
|
||||
id,
|
||||
children: [],
|
||||
state: AsyncDataTreeNodeState.Uninitialized
|
||||
},
|
||||
collapsible: !!this.dataSource.hasChildren(element),
|
||||
collapsed: true
|
||||
const childAsyncDataTreeNode: IAsyncDataTreeNode<TInput, T> = {
|
||||
element,
|
||||
parent: node,
|
||||
children: [],
|
||||
id,
|
||||
state: AsyncDataTreeNodeState.Uninitialized,
|
||||
hasChildren: !!this.dataSource.hasChildren(element),
|
||||
needsRefresh: false,
|
||||
disposed: false,
|
||||
slow: false
|
||||
};
|
||||
|
||||
if (viewStateContext) {
|
||||
if (viewStateContext.viewState.focus.indexOf(id) > -1) {
|
||||
viewStateContext.focus.push(childAsyncDataTreeNode);
|
||||
}
|
||||
|
||||
if (viewStateContext.viewState.selection.indexOf(id) > -1) {
|
||||
viewStateContext.selection.push(childAsyncDataTreeNode);
|
||||
}
|
||||
}
|
||||
|
||||
return childAsyncDataTreeNode;
|
||||
}
|
||||
|
||||
asyncDataTreeNode.element = element;
|
||||
|
||||
const collapsible = !!this.dataSource.hasChildren(element);
|
||||
const collapsed = !collapsible || this.tree.isCollapsed(asyncDataTreeNode);
|
||||
|
||||
if (recursive) {
|
||||
asyncDataTreeNode.state = AsyncDataTreeNodeState.Uninitialized;
|
||||
|
||||
if (this.tree.isCollapsed(asyncDataTreeNode)) {
|
||||
asyncDataTreeNode.children!.length = 0;
|
||||
|
||||
return {
|
||||
element: asyncDataTreeNode,
|
||||
collapsible,
|
||||
collapsed
|
||||
};
|
||||
}
|
||||
if (asyncDataTreeNode.state === AsyncDataTreeNodeState.Loaded || asyncDataTreeNode.hasChildren !== !!this.dataSource.hasChildren(asyncDataTreeNode.element)) {
|
||||
asyncDataTreeNode.needsRefresh = true;
|
||||
}
|
||||
|
||||
let children: Iterator<ITreeElement<IAsyncDataTreeNode<T>>> | undefined = undefined;
|
||||
|
||||
if (collapsible) {
|
||||
children = Iterator.map(Iterator.fromArray(asyncDataTreeNode.children!), asTreeElement);
|
||||
}
|
||||
|
||||
return {
|
||||
element: asyncDataTreeNode,
|
||||
children,
|
||||
collapsible,
|
||||
collapsed
|
||||
};
|
||||
return asyncDataTreeNode;
|
||||
});
|
||||
|
||||
// perf: if the node was and still is a leaf, avoid all these expensive no-ops
|
||||
if (node.children.length === 0 && childrenElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.children.splice(0, node.children.length, ...children);
|
||||
}
|
||||
|
||||
private render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
|
||||
const insertedElements = new Set<T>();
|
||||
|
||||
const onDidCreateNode = (treeNode: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>) => {
|
||||
const onDidCreateNode = (treeNode: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>) => {
|
||||
if (treeNode.element.element) {
|
||||
insertedElements.add(treeNode.element.element);
|
||||
this.nodes.set(treeNode.element.element, treeNode.element);
|
||||
insertedElements.add(treeNode.element.element as T);
|
||||
this.renderedNodes.set(treeNode.element.element as T, treeNode.element);
|
||||
}
|
||||
};
|
||||
|
||||
const onDidDeleteNode = (treeNode: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>) => {
|
||||
const onDidDeleteNode = (treeNode: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>) => {
|
||||
if (treeNode.element.element) {
|
||||
if (!insertedElements.has(treeNode.element.element)) {
|
||||
this.nodes.delete(treeNode.element.element);
|
||||
if (!insertedElements.has(treeNode.element.element as T)) {
|
||||
treeNode.element.disposed = true;
|
||||
this.renderedNodes.delete(treeNode.element.element as T);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const children = node.children.map(c => asTreeElement(c, viewStateContext));
|
||||
this.tree.setChildren(node === this.root ? null : node, children, onDidCreateNode, onDidDeleteNode);
|
||||
|
||||
if (this.identityProvider) {
|
||||
node.children!.splice(0, node.children!.length, ...children.map(c => c.element));
|
||||
this._onDidRender.fire();
|
||||
}
|
||||
|
||||
// view state
|
||||
|
||||
getViewState(): IAsyncDataTreeViewState {
|
||||
if (!this.identityProvider) {
|
||||
throw new Error('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.getNode();
|
||||
const queue = [root];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const node = queue.shift()!;
|
||||
|
||||
if (node !== root && node.collapsible && !node.collapsed) {
|
||||
expanded.push(getId(node.element!.element as T));
|
||||
}
|
||||
|
||||
queue.push(...node.children);
|
||||
}
|
||||
|
||||
return { focus, selection, expanded };
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
142
src/vs/base/browser/ui/tree/dataTree.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, IDataSource } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { Iterator } from 'vs/base/common/iterator';
|
||||
|
||||
export interface IDataTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
|
||||
sorter?: ITreeSorter<T>;
|
||||
}
|
||||
|
||||
export interface IDataTreeViewState {
|
||||
readonly focus: string[];
|
||||
readonly selection: string[];
|
||||
readonly collapsed: string[];
|
||||
}
|
||||
|
||||
export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | null, TFilterData, T | null> {
|
||||
|
||||
protected model: ObjectTreeModel<T, TFilterData>;
|
||||
private input: TInput | undefined;
|
||||
|
||||
private identityProvider: IIdentityProvider<T> | undefined;
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
|
||||
private dataSource: IDataSource<TInput, T>,
|
||||
options: IDataTreeOptions<T, TFilterData> = {}
|
||||
) {
|
||||
super(container, delegate, renderers, options);
|
||||
this.identityProvider = options.identityProvider;
|
||||
}
|
||||
|
||||
// Model
|
||||
|
||||
getInput(): TInput | undefined {
|
||||
return this.input;
|
||||
}
|
||||
|
||||
setInput(input: TInput, viewState?: IDataTreeViewState): void {
|
||||
if (viewState && !this.identityProvider) {
|
||||
throw new Error('Can\'t restore tree view state without an identity provider');
|
||||
}
|
||||
|
||||
this.input = input;
|
||||
|
||||
if (!viewState) {
|
||||
this._refresh(input);
|
||||
return;
|
||||
}
|
||||
|
||||
const focus: T[] = [];
|
||||
const selection: T[] = [];
|
||||
|
||||
const isCollapsed = (element: T) => {
|
||||
const id = this.identityProvider!.getId(element).toString();
|
||||
|
||||
if (viewState.focus.indexOf(id) > -1) {
|
||||
focus.push(element);
|
||||
}
|
||||
|
||||
if (viewState.selection.indexOf(id) > -1) {
|
||||
selection.push(element);
|
||||
}
|
||||
|
||||
return id in viewState.collapsed;
|
||||
};
|
||||
|
||||
this._refresh(input, isCollapsed);
|
||||
this.setFocus(focus);
|
||||
this.setSelection(selection);
|
||||
}
|
||||
|
||||
updateChildren(element: TInput | T = this.input!): void {
|
||||
if (typeof this.input === 'undefined') {
|
||||
throw new Error('Tree input not set');
|
||||
}
|
||||
|
||||
this._refresh(element);
|
||||
}
|
||||
|
||||
// View
|
||||
|
||||
refresh(element: T): void {
|
||||
this.model.refresh(element);
|
||||
}
|
||||
|
||||
// Implementation
|
||||
|
||||
private _refresh(element: TInput | T, isCollapsed?: (el: T) => boolean): void {
|
||||
this.model.setChildren((element === this.input ? null : element) as T, this.createIterator(element, isCollapsed));
|
||||
}
|
||||
|
||||
private createIterator(element: TInput | T, isCollapsed?: (el: T) => boolean): Iterator<ITreeElement<T>> {
|
||||
const children = Iterator.fromArray(this.dataSource.getChildren(element));
|
||||
|
||||
return Iterator.map<any, ITreeElement<T>>(children, element => ({
|
||||
element,
|
||||
children: this.createIterator(element),
|
||||
collapsed: isCollapsed && isCollapsed(element)
|
||||
}));
|
||||
}
|
||||
|
||||
protected createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: IDataTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
return new ObjectTreeModel(view, options);
|
||||
}
|
||||
|
||||
// view state
|
||||
|
||||
getViewState(): IDataTreeViewState {
|
||||
if (!this.identityProvider) {
|
||||
throw new Error('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 collapsed: string[] = [];
|
||||
const root = this.model.getNode();
|
||||
const queue = [root];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const node = queue.shift()!;
|
||||
|
||||
if (node !== root && node.collapsed) {
|
||||
collapsed.push(getId(node.element!));
|
||||
}
|
||||
|
||||
queue.push(...node.children);
|
||||
}
|
||||
|
||||
return { focus, selection, collapsed };
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,7 @@ import { IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { ITreeElement, ITreeModel, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
|
||||
export interface IIndexTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
|
||||
collapseByDefault?: boolean; // defaults to false
|
||||
}
|
||||
export interface IIndexTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> { }
|
||||
|
||||
export class IndexTree<T, TFilterData = void> extends AbstractTree<T, TFilterData, number[]> {
|
||||
|
||||
@@ -33,6 +31,10 @@ export class IndexTree<T, TFilterData = void> extends AbstractTree<T, TFilterDat
|
||||
return this.model.splice(location, deleteCount, toInsert);
|
||||
}
|
||||
|
||||
refresh(location: number[]): void {
|
||||
this.model.refresh(location);
|
||||
}
|
||||
|
||||
protected createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: IIndexTreeOptions<T, TFilterData>): ITreeModel<T, TFilterData, number[]> {
|
||||
return new IndexTreeModel(view, this.rootElement, options);
|
||||
}
|
||||
|
||||
@@ -3,15 +3,17 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { Iterator, ISequence } from 'vs/base/common/iterator';
|
||||
import { Emitter, Event, EventBufferer } from 'vs/base/common/event';
|
||||
import { ICollapseStateChangeEvent, ITreeElement, ITreeFilter, ITreeFilterDataResult, ITreeModel, ITreeNode, TreeVisibility, ITreeModelSpliceEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { tail2 } from 'vs/base/common/arrays';
|
||||
import { ITreeFilterDataResult, TreeVisibility, ITreeFilter, ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree';
|
||||
import { Emitter, Event, EventBufferer } from 'vs/base/common/event';
|
||||
import { ISequence, Iterator } from 'vs/base/common/iterator';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
|
||||
interface IMutableTreeNode<T, TFilterData> extends ITreeNode<T, TFilterData> {
|
||||
readonly parent: IMutableTreeNode<T, TFilterData> | undefined;
|
||||
readonly children: IMutableTreeNode<T, TFilterData>[];
|
||||
visibleChildrenCount: number;
|
||||
visibleChildIndex: number;
|
||||
collapsible: boolean;
|
||||
collapsed: boolean;
|
||||
renderNodeCount: number;
|
||||
@@ -19,10 +21,18 @@ interface IMutableTreeNode<T, TFilterData> extends ITreeNode<T, TFilterData> {
|
||||
filterData: TFilterData | undefined;
|
||||
}
|
||||
|
||||
function isFilterResult<T>(obj: any): obj is ITreeFilterDataResult<T> {
|
||||
export function isFilterResult<T>(obj: any): obj is ITreeFilterDataResult<T> {
|
||||
return typeof obj === 'object' && 'visibility' in obj && 'data' in obj;
|
||||
}
|
||||
|
||||
export function getVisibleState(visibility: boolean | TreeVisibility): TreeVisibility {
|
||||
switch (visibility) {
|
||||
case true: return TreeVisibility.Visible;
|
||||
case false: return TreeVisibility.Hidden;
|
||||
default: return visibility;
|
||||
}
|
||||
}
|
||||
|
||||
function treeNodeToElement<T>(node: IMutableTreeNode<T, any>): ITreeElement<T> {
|
||||
const { element, collapsed } = node;
|
||||
const children = Iterator.map(Iterator.fromArray(node.children), treeNodeToElement);
|
||||
@@ -30,42 +40,46 @@ function treeNodeToElement<T>(node: IMutableTreeNode<T, any>): ITreeElement<T> {
|
||||
return { element, children, collapsed };
|
||||
}
|
||||
|
||||
function getVisibleState(visibility: boolean | TreeVisibility): TreeVisibility {
|
||||
switch (visibility) {
|
||||
case true: return TreeVisibility.Visible;
|
||||
case false: return TreeVisibility.Hidden;
|
||||
default: return visibility;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IIndexTreeModelOptions<T, TFilterData> {
|
||||
collapseByDefault?: boolean; // defaults to false
|
||||
filter?: ITreeFilter<T, TFilterData>;
|
||||
readonly collapseByDefault?: boolean; // defaults to false
|
||||
readonly filter?: ITreeFilter<T, TFilterData>;
|
||||
readonly autoExpandSingleChildren?: boolean;
|
||||
}
|
||||
|
||||
export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = void> implements ITreeModel<T, TFilterData, number[]> {
|
||||
|
||||
readonly rootRef = [];
|
||||
|
||||
private root: IMutableTreeNode<T, TFilterData>;
|
||||
private eventBufferer = new EventBufferer();
|
||||
|
||||
private _onDidChangeCollapseState = new Emitter<ITreeNode<T, TFilterData>>();
|
||||
readonly onDidChangeCollapseState: Event<ITreeNode<T, TFilterData>> = this.eventBufferer.wrapEvent(this._onDidChangeCollapseState.event);
|
||||
private _onDidChangeCollapseState = new Emitter<ICollapseStateChangeEvent<T, TFilterData>>();
|
||||
readonly onDidChangeCollapseState: Event<ICollapseStateChangeEvent<T, TFilterData>> = this.eventBufferer.wrapEvent(this._onDidChangeCollapseState.event);
|
||||
|
||||
private _onDidChangeRenderNodeCount = new Emitter<ITreeNode<T, TFilterData>>();
|
||||
readonly onDidChangeRenderNodeCount: Event<ITreeNode<T, TFilterData>> = this.eventBufferer.wrapEvent(this._onDidChangeRenderNodeCount.event);
|
||||
|
||||
private collapseByDefault: boolean;
|
||||
private filter?: ITreeFilter<T, TFilterData>;
|
||||
private autoExpandSingleChildren: boolean;
|
||||
|
||||
private _onDidSplice = new Emitter<ITreeModelSpliceEvent<T, TFilterData>>();
|
||||
readonly onDidSplice = this._onDidSplice.event;
|
||||
|
||||
constructor(private list: ISpliceable<ITreeNode<T, TFilterData>>, rootElement: T, options: IIndexTreeModelOptions<T, TFilterData> = {}) {
|
||||
this.collapseByDefault = typeof options.collapseByDefault === 'undefined' ? false : options.collapseByDefault;
|
||||
this.filter = options.filter;
|
||||
this.autoExpandSingleChildren = typeof options.autoExpandSingleChildren === 'undefined' ? false : options.autoExpandSingleChildren;
|
||||
|
||||
// this.onDidChangeCollapseState(node => console.log(node.collapsed, node));
|
||||
|
||||
this.root = {
|
||||
parent: undefined,
|
||||
element: rootElement,
|
||||
children: [],
|
||||
depth: 0,
|
||||
visibleChildrenCount: 0,
|
||||
visibleChildIndex: -1,
|
||||
collapsible: false,
|
||||
collapsed: false,
|
||||
renderNodeCount: 0,
|
||||
@@ -85,22 +99,64 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
throw new Error('Invalid tree location');
|
||||
}
|
||||
|
||||
const { parentNode, listIndex, revealed } = this.getParentNodeWithListIndex(location);
|
||||
const { parentNode, listIndex, revealed, visible } = this.getParentNodeWithListIndex(location);
|
||||
const treeListElementsToInsert: ITreeNode<T, TFilterData>[] = [];
|
||||
const nodesToInsertIterator = Iterator.map(Iterator.from(toInsert), el => this.createTreeNode(el, parentNode, parentNode.visible ? TreeVisibility.Visible : TreeVisibility.Hidden, revealed, treeListElementsToInsert, onDidCreateNode));
|
||||
|
||||
const lastIndex = location[location.length - 1];
|
||||
|
||||
// figure out what's the visible child start index right before the
|
||||
// splice point
|
||||
let visibleChildStartIndex = 0;
|
||||
|
||||
for (let i = lastIndex; i >= 0 && i < parentNode.children.length; i--) {
|
||||
const child = parentNode.children[i];
|
||||
|
||||
if (child.visible) {
|
||||
visibleChildStartIndex = child.visibleChildIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const nodesToInsert: IMutableTreeNode<T, TFilterData>[] = [];
|
||||
let insertedVisibleChildrenCount = 0;
|
||||
let renderNodeCount = 0;
|
||||
|
||||
Iterator.forEach(nodesToInsertIterator, node => {
|
||||
nodesToInsert.push(node);
|
||||
renderNodeCount += node.renderNodeCount;
|
||||
Iterator.forEach(nodesToInsertIterator, child => {
|
||||
nodesToInsert.push(child);
|
||||
renderNodeCount += child.renderNodeCount;
|
||||
|
||||
if (child.visible) {
|
||||
child.visibleChildIndex = visibleChildStartIndex + insertedVisibleChildrenCount++;
|
||||
}
|
||||
});
|
||||
|
||||
const lastIndex = location[location.length - 1];
|
||||
const deletedNodes = parentNode.children.splice(lastIndex, deleteCount, ...nodesToInsert);
|
||||
|
||||
if (revealed) {
|
||||
// figure out what is the count of deleted visible children
|
||||
let deletedVisibleChildrenCount = 0;
|
||||
|
||||
for (const child of deletedNodes) {
|
||||
if (child.visible) {
|
||||
deletedVisibleChildrenCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// and adjust for all visible children after the splice point
|
||||
if (deletedVisibleChildrenCount !== 0) {
|
||||
for (let i = lastIndex + nodesToInsert.length; i < parentNode.children.length; i++) {
|
||||
const child = parentNode.children[i];
|
||||
|
||||
if (child.visible) {
|
||||
child.visibleChildIndex -= deletedVisibleChildrenCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update parent's visible children count
|
||||
parentNode.visibleChildrenCount += insertedVisibleChildrenCount - deletedVisibleChildrenCount;
|
||||
|
||||
if (revealed && visible) {
|
||||
const visibleDeleteCount = deletedNodes.reduce((r, node) => r + node.renderNodeCount, 0);
|
||||
|
||||
this._updateAncestorsRenderNodeCount(parentNode, renderNodeCount - visibleDeleteCount);
|
||||
@@ -116,37 +172,30 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
deletedNodes.forEach(visit);
|
||||
}
|
||||
|
||||
return Iterator.map(Iterator.fromArray(deletedNodes), treeNodeToElement);
|
||||
const result = Iterator.map(Iterator.fromArray(deletedNodes), treeNodeToElement);
|
||||
this._onDidSplice.fire({ insertedNodes: nodesToInsert, deletedNodes });
|
||||
return result;
|
||||
}
|
||||
|
||||
refresh(location: number[]): void {
|
||||
if (location.length === 0) {
|
||||
throw new Error('Invalid tree location');
|
||||
}
|
||||
|
||||
const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location);
|
||||
|
||||
if (revealed) {
|
||||
this.list.splice(listIndex, 1, [node]);
|
||||
}
|
||||
}
|
||||
|
||||
getListIndex(location: number[]): number {
|
||||
return this.getTreeNodeWithListIndex(location).listIndex;
|
||||
const { listIndex, visible, revealed } = this.getTreeNodeWithListIndex(location);
|
||||
return visible && revealed ? listIndex : -1;
|
||||
}
|
||||
|
||||
setCollapsed(location: number[], collapsed: boolean): boolean {
|
||||
const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location);
|
||||
return this.eventBufferer.bufferEvents(() => this._setCollapsed(node, listIndex, revealed, collapsed));
|
||||
}
|
||||
|
||||
toggleCollapsed(location: number[]): void {
|
||||
const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location);
|
||||
this.eventBufferer.bufferEvents(() => this._setCollapsed(node, listIndex, revealed));
|
||||
}
|
||||
|
||||
collapseAll(): void {
|
||||
const queue = [...this.root.children];
|
||||
let listIndex = 0;
|
||||
|
||||
this.eventBufferer.bufferEvents(() => {
|
||||
while (queue.length > 0) {
|
||||
const node = queue.shift()!;
|
||||
const revealed = listIndex < this.root.children.length;
|
||||
this._setCollapsed(node, listIndex, revealed, true);
|
||||
|
||||
queue.push(...node.children);
|
||||
listIndex++;
|
||||
}
|
||||
});
|
||||
getListRenderCount(location: number[]): number {
|
||||
return this.getTreeNode(location).renderNodeCount;
|
||||
}
|
||||
|
||||
isCollapsible(location: number[]): boolean {
|
||||
@@ -157,36 +206,84 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
return this.getTreeNode(location).collapsed;
|
||||
}
|
||||
|
||||
refilter(): void {
|
||||
const previousRenderNodeCount = this.root.renderNodeCount;
|
||||
const toInsert = this.updateNodeAfterFilterChange(this.root);
|
||||
this.list.splice(0, previousRenderNodeCount, toInsert);
|
||||
}
|
||||
|
||||
private _setCollapsed(node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, collapsed?: boolean | undefined): boolean {
|
||||
if (!node.collapsible) {
|
||||
return false;
|
||||
}
|
||||
setCollapsed(location: number[], collapsed?: boolean, recursive?: boolean): boolean {
|
||||
const node = this.getTreeNode(location);
|
||||
|
||||
if (typeof collapsed === 'undefined') {
|
||||
collapsed = !node.collapsed;
|
||||
}
|
||||
|
||||
if (node.collapsed === collapsed) {
|
||||
return false;
|
||||
return this.eventBufferer.bufferEvents(() => this._setCollapsed(location, collapsed!, recursive));
|
||||
}
|
||||
|
||||
private _setCollapsed(location: number[], collapsed: boolean, recursive?: boolean): boolean {
|
||||
const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location);
|
||||
|
||||
const result = this._setListNodeCollapsed(node, listIndex, revealed, collapsed!, recursive || false);
|
||||
|
||||
if (this.autoExpandSingleChildren && !collapsed! && !recursive) {
|
||||
let onlyVisibleChildIndex = -1;
|
||||
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
const child = node.children[i];
|
||||
|
||||
if (child.visible) {
|
||||
if (onlyVisibleChildIndex > -1) {
|
||||
onlyVisibleChildIndex = -1;
|
||||
break;
|
||||
} else {
|
||||
onlyVisibleChildIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (onlyVisibleChildIndex > -1) {
|
||||
this._setCollapsed([...location, onlyVisibleChildIndex], false, false);
|
||||
}
|
||||
}
|
||||
|
||||
node.collapsed = collapsed;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (revealed) {
|
||||
const previousRenderNodeCount = node.renderNodeCount;
|
||||
const toInsert = this.updateNodeAfterCollapseChange(node);
|
||||
private _setListNodeCollapsed(node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, collapsed: boolean, recursive: boolean): boolean {
|
||||
const result = this._setNodeCollapsed(node, collapsed, recursive, false);
|
||||
|
||||
this.list.splice(listIndex + 1, previousRenderNodeCount - 1, toInsert.slice(1));
|
||||
this._onDidChangeCollapseState.fire(node);
|
||||
if (!revealed || !node.visible) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return true;
|
||||
const previousRenderNodeCount = node.renderNodeCount;
|
||||
const toInsert = this.updateNodeAfterCollapseChange(node);
|
||||
const deleteCount = previousRenderNodeCount - (listIndex === -1 ? 0 : 1);
|
||||
this.list.splice(listIndex + 1, deleteCount, toInsert.slice(1));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private _setNodeCollapsed(node: IMutableTreeNode<T, TFilterData>, collapsed: boolean, recursive: boolean, deep: boolean): boolean {
|
||||
let result = node.collapsible && node.collapsed !== collapsed;
|
||||
|
||||
if (node.collapsible) {
|
||||
node.collapsed = collapsed;
|
||||
|
||||
if (result) {
|
||||
this._onDidChangeCollapseState.fire({ node, deep });
|
||||
}
|
||||
}
|
||||
|
||||
if (recursive) {
|
||||
for (const child of node.children) {
|
||||
result = this._setNodeCollapsed(child, collapsed, true, true) || result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
refilter(): void {
|
||||
const previousRenderNodeCount = this.root.renderNodeCount;
|
||||
const toInsert = this.updateNodeAfterFilterChange(this.root);
|
||||
this.list.splice(0, previousRenderNodeCount, toInsert);
|
||||
}
|
||||
|
||||
private createTreeNode(
|
||||
@@ -202,6 +299,8 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
element: treeElement.element,
|
||||
children: [],
|
||||
depth: parent.depth + 1,
|
||||
visibleChildrenCount: 0,
|
||||
visibleChildIndex: -1,
|
||||
collapsible: typeof treeElement.collapsible === 'boolean' ? treeElement.collapsible : (typeof treeElement.collapsed !== 'undefined'),
|
||||
collapsed: typeof treeElement.collapsed === 'undefined' ? this.collapseByDefault : treeElement.collapsed,
|
||||
renderNodeCount: 1,
|
||||
@@ -219,17 +318,21 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
const childRevealed = revealed && visibility !== TreeVisibility.Hidden && !node.collapsed;
|
||||
const childNodes = Iterator.map(childElements, el => this.createTreeNode(el, node, visibility, childRevealed, treeListElements, onDidCreateNode));
|
||||
|
||||
let hasVisibleDescendants = false;
|
||||
let visibleChildrenCount = 0;
|
||||
let renderNodeCount = 1;
|
||||
|
||||
Iterator.forEach(childNodes, child => {
|
||||
node.children.push(child);
|
||||
hasVisibleDescendants = hasVisibleDescendants || child.visible;
|
||||
renderNodeCount += child.renderNodeCount;
|
||||
|
||||
if (child.visible) {
|
||||
child.visibleChildIndex = visibleChildrenCount++;
|
||||
}
|
||||
});
|
||||
|
||||
node.collapsible = node.collapsible || node.children.length > 0;
|
||||
node.visible = visibility === TreeVisibility.Recurse ? hasVisibleDescendants : (visibility === TreeVisibility.Visible);
|
||||
node.visibleChildrenCount = visibleChildrenCount;
|
||||
node.visible = visibility === TreeVisibility.Recurse ? visibleChildrenCount > 0 : (visibility === TreeVisibility.Visible);
|
||||
|
||||
if (!node.visible) {
|
||||
node.renderNodeCount = 0;
|
||||
@@ -307,9 +410,19 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
|
||||
let hasVisibleDescendants = false;
|
||||
if (!node.collapsed || visibility! !== TreeVisibility.Hidden) {
|
||||
let visibleChildIndex = 0;
|
||||
|
||||
for (const child of node.children) {
|
||||
hasVisibleDescendants = this._updateNodeAfterFilterChange(child, visibility!, result, revealed && !node.collapsed) || hasVisibleDescendants;
|
||||
|
||||
if (child.visible) {
|
||||
child.visibleChildIndex = visibleChildIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
node.visibleChildrenCount = visibleChildIndex;
|
||||
} else {
|
||||
node.visibleChildrenCount = 0;
|
||||
}
|
||||
|
||||
if (node !== this.root) {
|
||||
@@ -373,8 +486,12 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
}
|
||||
|
||||
// expensive
|
||||
private getTreeNodeWithListIndex(location: number[]): { node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean } {
|
||||
const { parentNode, listIndex, revealed } = this.getParentNodeWithListIndex(location);
|
||||
private getTreeNodeWithListIndex(location: number[]): { node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, visible: boolean } {
|
||||
if (location.length === 0) {
|
||||
return { node: this.root, listIndex: -1, revealed: true, visible: false };
|
||||
}
|
||||
|
||||
const { parentNode, listIndex, revealed, visible } = this.getParentNodeWithListIndex(location);
|
||||
const index = location[location.length - 1];
|
||||
|
||||
if (index < 0 || index > parentNode.children.length) {
|
||||
@@ -383,10 +500,10 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
|
||||
const node = parentNode.children[index];
|
||||
|
||||
return { node, listIndex, revealed };
|
||||
return { node, listIndex, revealed, visible: visible && node.visible };
|
||||
}
|
||||
|
||||
private getParentNodeWithListIndex(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root, listIndex: number = 0, revealed = true): { parentNode: IMutableTreeNode<T, TFilterData>; listIndex: number; revealed: boolean; } {
|
||||
private getParentNodeWithListIndex(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root, listIndex: number = 0, revealed = true, visible = true): { parentNode: IMutableTreeNode<T, TFilterData>; listIndex: number; revealed: boolean; visible: boolean; } {
|
||||
const [index, ...rest] = location;
|
||||
|
||||
if (index < 0 || index > node.children.length) {
|
||||
@@ -399,12 +516,13 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
|
||||
}
|
||||
|
||||
revealed = revealed && !node.collapsed;
|
||||
visible = visible && node.visible;
|
||||
|
||||
if (rest.length === 0) {
|
||||
return { parentNode: node, listIndex, revealed };
|
||||
return { parentNode: node, listIndex, revealed, visible };
|
||||
}
|
||||
|
||||
return this.getParentNodeWithListIndex(rest, node.children[index], listIndex + 1, revealed);
|
||||
return this.getParentNodeWithListIndex(rest, node.children[index], listIndex + 1, revealed, visible);
|
||||
}
|
||||
|
||||
getNode(location: number[] = []): ITreeNode<T, TFilterData> {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
text-align: right;
|
||||
margin-right: 6px;
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.monaco-tl-contents {
|
||||
@@ -28,7 +29,7 @@
|
||||
|
||||
.monaco-tl-twistie.collapsible {
|
||||
background-size: 16px;
|
||||
background-position: 100% 50%;
|
||||
background-position: 3px 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("expanded.svg");
|
||||
}
|
||||
@@ -56,6 +57,7 @@
|
||||
|
||||
.monaco-tl-twistie.loading {
|
||||
background-image: url("loading.svg");
|
||||
background-position: 0 center;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-tl-twistie.loading {
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
import { Iterator, ISequence } from 'vs/base/common/iterator';
|
||||
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
|
||||
export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
|
||||
collapseByDefault?: boolean; // defaults to false
|
||||
sorter?: ITreeSorter<T>;
|
||||
}
|
||||
|
||||
export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends AbstractTree<T | null, TFilterData, T | null> {
|
||||
@@ -36,6 +36,10 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
|
||||
return this.model.setChildren(element, children, onDidCreateNode, onDidDeleteNode);
|
||||
}
|
||||
|
||||
refresh(element: T): void {
|
||||
this.model.refresh(element);
|
||||
}
|
||||
|
||||
protected createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: IObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
|
||||
return new ObjectTreeModel(view, options);
|
||||
}
|
||||
|
||||
@@ -7,24 +7,39 @@ import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { Iterator, ISequence, getSequenceIterator } from 'vs/base/common/iterator';
|
||||
import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ITreeModel, ITreeNode, ITreeElement, ITreeSorter, ICollapseStateChangeEvent, ITreeModelSpliceEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
|
||||
export interface IObjectTreeModelOptions<T, TFilterData> extends IIndexTreeModelOptions<T, TFilterData> { }
|
||||
export interface IObjectTreeModelOptions<T, TFilterData> extends IIndexTreeModelOptions<T, TFilterData> {
|
||||
readonly sorter?: ITreeSorter<T>;
|
||||
}
|
||||
|
||||
export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<T | null, TFilterData, T | null> {
|
||||
|
||||
readonly rootRef = null;
|
||||
|
||||
private model: IndexTreeModel<T | null, TFilterData>;
|
||||
private nodes = new Map<T | null, ITreeNode<T, TFilterData>>();
|
||||
private sorter?: ITreeSorter<ITreeElement<T>>;
|
||||
|
||||
readonly onDidChangeCollapseState: Event<ITreeNode<T, TFilterData>>;
|
||||
readonly onDidSplice: Event<ITreeModelSpliceEvent<T | null, TFilterData>>;
|
||||
readonly onDidChangeCollapseState: Event<ICollapseStateChangeEvent<T, TFilterData>>;
|
||||
readonly onDidChangeRenderNodeCount: Event<ITreeNode<T, TFilterData>>;
|
||||
|
||||
get size(): number { return this.nodes.size; }
|
||||
|
||||
constructor(list: ISpliceable<ITreeNode<T, TFilterData>>, options: IObjectTreeModelOptions<T, TFilterData> = {}) {
|
||||
this.model = new IndexTreeModel(list, null, options);
|
||||
this.onDidChangeCollapseState = this.model.onDidChangeCollapseState as Event<ITreeNode<T, TFilterData>>;
|
||||
this.onDidSplice = this.model.onDidSplice;
|
||||
this.onDidChangeCollapseState = this.model.onDidChangeCollapseState as Event<ICollapseStateChangeEvent<T, TFilterData>>;
|
||||
this.onDidChangeRenderNodeCount = this.model.onDidChangeRenderNodeCount as Event<ITreeNode<T, TFilterData>>;
|
||||
|
||||
if (options.sorter) {
|
||||
this.sorter = {
|
||||
compare(a, b) {
|
||||
return options.sorter!.compare(a.element, b.element);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
setChildren(
|
||||
@@ -65,7 +80,11 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
}
|
||||
|
||||
private preserveCollapseState(elements: ISequence<ITreeElement<T | null>> | undefined): ISequence<ITreeElement<T | null>> {
|
||||
const iterator = elements ? getSequenceIterator(elements) : Iterator.empty<ITreeElement<T>>();
|
||||
let iterator = elements ? getSequenceIterator(elements) : Iterator.empty<ITreeElement<T>>();
|
||||
|
||||
if (this.sorter) {
|
||||
iterator = Iterator.fromArray(Iterator.collect(iterator).sort(this.sorter.compare.bind(this.sorter)));
|
||||
}
|
||||
|
||||
return Iterator.map(iterator, treeElement => {
|
||||
const node = this.nodes.get(treeElement.element);
|
||||
@@ -86,6 +105,11 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
});
|
||||
}
|
||||
|
||||
refresh(element: T): void {
|
||||
const location = this.getElementLocation(element);
|
||||
this.model.refresh(location);
|
||||
}
|
||||
|
||||
getParentElement(ref: T | null = null): T | null {
|
||||
const location = this.getElementLocation(ref);
|
||||
return this.model.getParentElement(location);
|
||||
@@ -106,18 +130,9 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
return this.model.getListIndex(location);
|
||||
}
|
||||
|
||||
setCollapsed(element: T, collapsed: boolean): boolean {
|
||||
getListRenderCount(element: T): number {
|
||||
const location = this.getElementLocation(element);
|
||||
return this.model.setCollapsed(location, collapsed);
|
||||
}
|
||||
|
||||
toggleCollapsed(element: T): void {
|
||||
const location = this.getElementLocation(element);
|
||||
this.model.toggleCollapsed(location);
|
||||
}
|
||||
|
||||
collapseAll(): void {
|
||||
this.model.collapseAll();
|
||||
return this.model.getListRenderCount(location);
|
||||
}
|
||||
|
||||
isCollapsible(element: T): boolean {
|
||||
@@ -130,6 +145,11 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
return this.model.isCollapsed(location);
|
||||
}
|
||||
|
||||
setCollapsed(element: T, collapsed?: boolean, recursive?: boolean): boolean {
|
||||
const location = this.getElementLocation(element);
|
||||
return this.model.setCollapsed(location, collapsed, recursive);
|
||||
}
|
||||
|
||||
refilter(): void {
|
||||
this.model.refilter();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Iterator } from 'vs/base/common/iterator';
|
||||
import { IListRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { IListRenderer, IListDragOverReaction, IListDragAndDrop, ListDragOverEffect } from 'vs/base/browser/ui/list/list';
|
||||
import { IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
|
||||
export const enum TreeVisibility {
|
||||
|
||||
@@ -67,6 +68,10 @@ export interface ITreeFilter<T, TFilterData = void> {
|
||||
filter(element: T, parentVisibility: TreeVisibility): TreeFilterResult<TFilterData>;
|
||||
}
|
||||
|
||||
export interface ITreeSorter<T> {
|
||||
compare(element: T, otherElement: T): number;
|
||||
}
|
||||
|
||||
export interface ITreeElement<T> {
|
||||
readonly element: T;
|
||||
readonly children?: Iterator<ITreeElement<T>> | ITreeElement<T>[];
|
||||
@@ -79,17 +84,33 @@ export interface ITreeNode<T, TFilterData = void> {
|
||||
readonly parent: ITreeNode<T, TFilterData> | undefined;
|
||||
readonly children: ITreeNode<T, TFilterData>[];
|
||||
readonly depth: number;
|
||||
readonly visibleChildrenCount: number;
|
||||
readonly visibleChildIndex: number;
|
||||
readonly collapsible: boolean;
|
||||
readonly collapsed: boolean;
|
||||
readonly visible: boolean;
|
||||
readonly filterData: TFilterData | undefined;
|
||||
}
|
||||
|
||||
export interface ICollapseStateChangeEvent<T, TFilterData> {
|
||||
node: ITreeNode<T, TFilterData>;
|
||||
deep: boolean;
|
||||
}
|
||||
|
||||
export interface ITreeModelSpliceEvent<T, TFilterData> {
|
||||
insertedNodes: ITreeNode<T, TFilterData>[];
|
||||
deletedNodes: ITreeNode<T, TFilterData>[];
|
||||
}
|
||||
|
||||
export interface ITreeModel<T, TFilterData, TRef> {
|
||||
readonly onDidChangeCollapseState: Event<ITreeNode<T, TFilterData>>;
|
||||
readonly rootRef: TRef;
|
||||
|
||||
readonly onDidSplice: Event<ITreeModelSpliceEvent<T, TFilterData>>;
|
||||
readonly onDidChangeCollapseState: Event<ICollapseStateChangeEvent<T, TFilterData>>;
|
||||
readonly onDidChangeRenderNodeCount: Event<ITreeNode<T, TFilterData>>;
|
||||
|
||||
getListIndex(location: TRef): number;
|
||||
getListRenderCount(location: TRef): number;
|
||||
getNode(location?: TRef): ITreeNode<T, any>;
|
||||
getNodeLocation(node: ITreeNode<T, any>): TRef;
|
||||
getParentNodeLocation(location: TRef): TRef;
|
||||
@@ -100,9 +121,7 @@ export interface ITreeModel<T, TFilterData, TRef> {
|
||||
|
||||
isCollapsible(location: TRef): boolean;
|
||||
isCollapsed(location: TRef): boolean;
|
||||
setCollapsed(location: TRef, collapsed: boolean): boolean;
|
||||
toggleCollapsed(location: TRef): void;
|
||||
collapseAll(): void;
|
||||
setCollapsed(location: TRef, collapsed?: boolean, recursive?: boolean): boolean;
|
||||
|
||||
refilter(): void;
|
||||
}
|
||||
@@ -127,3 +146,42 @@ export interface ITreeContextMenuEvent<T> {
|
||||
element: T | null;
|
||||
anchor: HTMLElement | { x: number; y: number; } | undefined;
|
||||
}
|
||||
|
||||
export interface ITreeNavigator<T> {
|
||||
current(): T | null;
|
||||
previous(): T | null;
|
||||
parent(): T | null;
|
||||
first(): T | null;
|
||||
last(): T | null;
|
||||
next(): T | null;
|
||||
}
|
||||
|
||||
export interface IDataSource<TInput, T> {
|
||||
getChildren(element: TInput | T): T[];
|
||||
}
|
||||
|
||||
export interface IAsyncDataSource<TInput, T> {
|
||||
hasChildren(element: TInput | T): boolean;
|
||||
getChildren(element: TInput | T): T[] | Promise<T[]>;
|
||||
}
|
||||
|
||||
export const enum TreeDragOverBubble {
|
||||
Down,
|
||||
Up
|
||||
}
|
||||
|
||||
export interface ITreeDragOverReaction extends IListDragOverReaction {
|
||||
bubble?: TreeDragOverBubble;
|
||||
autoExpand?: boolean;
|
||||
}
|
||||
|
||||
export const TreeDragOverReactions = {
|
||||
acceptBubbleUp(): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Up }; },
|
||||
acceptBubbleDown(autoExpand = false): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Down, autoExpand }; },
|
||||
acceptCopyBubbleUp(): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Up, effect: ListDragOverEffect.Copy }; },
|
||||
acceptCopyBubbleDown(autoExpand = false): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Down, effect: ListDragOverEffect.Copy, autoExpand }; }
|
||||
};
|
||||
|
||||
export interface ITreeDragAndDrop<T> extends IListDragAndDrop<T> {
|
||||
onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction;
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@ export interface IAction extends IDisposable {
|
||||
enabled: boolean;
|
||||
checked: boolean;
|
||||
radio: boolean;
|
||||
run(event?: any): Thenable<any>;
|
||||
run(event?: any): Promise<any>;
|
||||
}
|
||||
|
||||
export interface IActionRunner extends IDisposable {
|
||||
run(action: IAction, context?: any): Thenable<any>;
|
||||
run(action: IAction, context?: any): Promise<any>;
|
||||
onDidRun: Event<IRunEvent>;
|
||||
onDidBeforeRun: Event<IRunEvent>;
|
||||
}
|
||||
@@ -60,9 +60,9 @@ export class Action implements IAction {
|
||||
protected _enabled: boolean;
|
||||
protected _checked: boolean;
|
||||
protected _radio: boolean;
|
||||
protected _actionCallback?: (event?: any) => Thenable<any>;
|
||||
protected _actionCallback?: (event?: any) => Promise<any>;
|
||||
|
||||
constructor(id: string, label: string = '', cssClass: string = '', enabled: boolean = true, actionCallback?: (event?: any) => Thenable<any>) {
|
||||
constructor(id: string, label: string = '', cssClass: string = '', enabled: boolean = true, actionCallback?: (event?: any) => Promise<any>) {
|
||||
this._id = id;
|
||||
this._label = label;
|
||||
this._cssClass = cssClass;
|
||||
@@ -164,7 +164,7 @@ export class Action implements IAction {
|
||||
}
|
||||
}
|
||||
|
||||
run(event?: any, _data?: ITelemetryData): Thenable<any> {
|
||||
run(event?: any, _data?: ITelemetryData): Promise<any> {
|
||||
if (this._actionCallback) {
|
||||
return this._actionCallback(event);
|
||||
}
|
||||
@@ -191,7 +191,7 @@ export class ActionRunner extends Disposable implements IActionRunner {
|
||||
private _onDidRun = this._register(new Emitter<IRunEvent>());
|
||||
readonly onDidRun: Event<IRunEvent> = this._onDidRun.event;
|
||||
|
||||
run(action: IAction, context?: any): Thenable<any> {
|
||||
run(action: IAction, context?: any): Promise<any> {
|
||||
if (!action.enabled) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
@@ -205,7 +205,7 @@ export class ActionRunner extends Disposable implements IActionRunner {
|
||||
});
|
||||
}
|
||||
|
||||
protected runAction(action: IAction, context?: any): Thenable<any> {
|
||||
protected runAction(action: IAction, context?: any): Promise<any> {
|
||||
const res = context ? action.run(context) : action.run();
|
||||
return Promise.resolve(res);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export function equals<T>(one: ReadonlyArray<T> | undefined, other: ReadonlyArra
|
||||
return true;
|
||||
}
|
||||
|
||||
export function binarySearch<T>(array: T[], key: T, comparator: (op1: T, op2: T) => number): number {
|
||||
export function binarySearch<T>(array: ReadonlyArray<T>, key: T, comparator: (op1: T, op2: T) => number): number {
|
||||
let low = 0,
|
||||
high = array.length - 1;
|
||||
|
||||
@@ -69,7 +69,7 @@ export function binarySearch<T>(array: T[], key: T, comparator: (op1: T, op2: T)
|
||||
* are located before all elements where p(x) is true.
|
||||
* @returns the least x for which p(x) is true or array.length if no element fullfills the given function.
|
||||
*/
|
||||
export function findFirstInSorted<T>(array: T[], p: (x: T) => boolean): number {
|
||||
export function findFirstInSorted<T>(array: ReadonlyArray<T>, p: (x: T) => boolean): number {
|
||||
let low = 0, high = array.length;
|
||||
if (high === 0) {
|
||||
return 0; // no children
|
||||
@@ -135,7 +135,7 @@ function _sort<T>(a: T[], compare: Compare<T>, lo: number, hi: number, aux: T[])
|
||||
}
|
||||
|
||||
|
||||
export function groupBy<T>(data: T[], compare: (a: T, b: T) => number): T[][] {
|
||||
export function groupBy<T>(data: ReadonlyArray<T>, compare: (a: T, b: T) => number): T[][] {
|
||||
const result: T[][] = [];
|
||||
let currentGroup: T[] | undefined = undefined;
|
||||
for (const element of mergeSort(data.slice(0), compare)) {
|
||||
@@ -156,7 +156,7 @@ interface IMutableSplice<T> extends ISplice<T> {
|
||||
/**
|
||||
* Diffs two *sorted* arrays and computes the splices which apply the diff.
|
||||
*/
|
||||
export function sortedDiff<T>(before: T[], after: T[], compare: (a: T, b: T) => number): ISplice<T>[] {
|
||||
export function sortedDiff<T>(before: ReadonlyArray<T>, after: ReadonlyArray<T>, compare: (a: T, b: T) => number): ISplice<T>[] {
|
||||
const result: IMutableSplice<T>[] = [];
|
||||
|
||||
function pushSplice(start: number, deleteCount: number, toInsert: T[]): void {
|
||||
@@ -211,11 +211,8 @@ export function sortedDiff<T>(before: T[], after: T[], compare: (a: T, b: T) =>
|
||||
/**
|
||||
* Takes two *sorted* arrays and computes their delta (removed, added elements).
|
||||
* Finishes in `Math.min(before.length, after.length)` steps.
|
||||
* @param before
|
||||
* @param after
|
||||
* @param compare
|
||||
*/
|
||||
export function delta<T>(before: T[], after: T[], compare: (a: T, b: T) => number): { removed: T[], added: T[] } {
|
||||
export function delta<T>(before: ReadonlyArray<T>, after: ReadonlyArray<T>, compare: (a: T, b: T) => number): { removed: T[], added: T[] } {
|
||||
const splices = sortedDiff(before, after, compare);
|
||||
const removed: T[] = [];
|
||||
const added: T[] = [];
|
||||
@@ -238,7 +235,7 @@ export function delta<T>(before: T[], after: T[], compare: (a: T, b: T) => numbe
|
||||
* @param n The number of elements to return.
|
||||
* @return The first n elemnts from array when sorted with compare.
|
||||
*/
|
||||
export function top<T>(array: T[], compare: (a: T, b: T) => number, n: number): T[] {
|
||||
export function top<T>(array: ReadonlyArray<T>, compare: (a: T, b: T) => number, n: number): T[] {
|
||||
if (n === 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -284,7 +281,7 @@ export function topAsync<T>(array: T[], compare: (a: T, b: T) => number, n: numb
|
||||
});
|
||||
}
|
||||
|
||||
function topStep<T>(array: T[], compare: (a: T, b: T) => number, result: T[], i: number, m: number): void {
|
||||
function topStep<T>(array: ReadonlyArray<T>, compare: (a: T, b: T) => number, result: T[], i: number, m: number): void {
|
||||
for (const n = result.length; i < m; i++) {
|
||||
const element = array[i];
|
||||
if (compare(element, result[n - 1]) < 0) {
|
||||
@@ -298,7 +295,7 @@ function topStep<T>(array: T[], compare: (a: T, b: T) => number, result: T[], i:
|
||||
/**
|
||||
* @returns a new array with all falsy values removed. The original array IS NOT modified.
|
||||
*/
|
||||
export function coalesce<T>(array: (T | undefined | null)[]): T[] {
|
||||
export function coalesce<T>(array: Array<T | undefined | null>): T[] {
|
||||
if (!array) {
|
||||
return array;
|
||||
}
|
||||
@@ -308,7 +305,7 @@ export function coalesce<T>(array: (T | undefined | null)[]): T[] {
|
||||
/**
|
||||
* Remove all falsey values from `array`. The original array IS modified.
|
||||
*/
|
||||
export function coalesceInPlace<T>(array: (T | undefined | null)[]): void {
|
||||
export function coalesceInPlace<T>(array: Array<T | undefined | null>): void {
|
||||
if (!array) {
|
||||
return;
|
||||
}
|
||||
@@ -330,15 +327,14 @@ export function move(array: any[], from: number, to: number): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{false}} if the provided object is an array
|
||||
* and not empty.
|
||||
* @returns false if the provided object is an array and not empty.
|
||||
*/
|
||||
export function isFalsyOrEmpty(obj: any): boolean {
|
||||
return !Array.isArray(obj) || obj.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{true}} if the provided object is an array and has at least one element.
|
||||
* @returns True if the provided object is an array and has at least one element.
|
||||
*/
|
||||
export function isNonEmptyArray<T>(obj: ReadonlyArray<T> | undefined | null): obj is Array<T> {
|
||||
return Array.isArray(obj) && obj.length > 0;
|
||||
@@ -348,7 +344,7 @@ export function isNonEmptyArray<T>(obj: ReadonlyArray<T> | undefined | null): ob
|
||||
* Removes duplicates from the given array. The optional keyFn allows to specify
|
||||
* how elements are checked for equalness by returning a unique string for each.
|
||||
*/
|
||||
export function distinct<T>(array: T[], keyFn?: (t: T) => string): T[] {
|
||||
export function distinct<T>(array: ReadonlyArray<T>, keyFn?: (t: T) => string): T[] {
|
||||
if (!keyFn) {
|
||||
return array.filter((element, position) => {
|
||||
return array.indexOf(element) === position;
|
||||
@@ -383,7 +379,7 @@ export function uniqueFilter<T>(keyFn: (t: T) => string): (t: T) => boolean {
|
||||
};
|
||||
}
|
||||
|
||||
export function firstIndex<T>(array: T[] | ReadonlyArray<T>, fn: (item: T) => boolean): number {
|
||||
export function firstIndex<T>(array: ReadonlyArray<T>, fn: (item: T) => boolean): number {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const element = array[i];
|
||||
|
||||
@@ -395,14 +391,14 @@ export function firstIndex<T>(array: T[] | ReadonlyArray<T>, fn: (item: T) => bo
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function first<T>(array: T[] | ReadonlyArray<T>, fn: (item: T) => boolean, notFoundValue: T): T;
|
||||
export function first<T>(array: T[] | ReadonlyArray<T>, fn: (item: T) => boolean): T | null;
|
||||
export function first<T>(array: T[] | ReadonlyArray<T>, fn: (item: T) => boolean, notFoundValue: T | null = null): T | null {
|
||||
export function first<T>(array: ReadonlyArray<T>, fn: (item: T) => boolean, notFoundValue: T): T;
|
||||
export function first<T>(array: ReadonlyArray<T>, fn: (item: T) => boolean): T | null;
|
||||
export function first<T>(array: ReadonlyArray<T>, fn: (item: T) => boolean, notFoundValue: T | null = null): T | null {
|
||||
const index = firstIndex(array, fn);
|
||||
return index < 0 ? notFoundValue : array[index];
|
||||
}
|
||||
|
||||
export function commonPrefixLength<T>(one: T[], other: T[], equals: (a: T, b: T) => boolean = (a, b) => a === b): number {
|
||||
export function commonPrefixLength<T>(one: ReadonlyArray<T>, other: ReadonlyArray<T>, equals: (a: T, b: T) => boolean = (a, b) => a === b): number {
|
||||
let result = 0;
|
||||
|
||||
for (let i = 0, len = Math.min(one.length, other.length); i < len && equals(one[i], other[i]); i++) {
|
||||
@@ -443,17 +439,17 @@ export function range(arg: number, to?: number): number[] {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function fill<T>(num: number, valueFn: () => T, arr: T[] = []): T[] {
|
||||
export function fill<T>(num: number, value: T, arr: T[] = []): T[] {
|
||||
for (let i = 0; i < num; i++) {
|
||||
arr[i] = valueFn();
|
||||
arr[i] = value;
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
export function index<T>(array: T[], indexer: (t: T) => string): { [key: string]: T; };
|
||||
export function index<T, R>(array: T[], indexer: (t: T) => string, merger?: (t: T, r: R) => R): { [key: string]: R; };
|
||||
export function index<T, R>(array: T[], indexer: (t: T) => string, merger: (t: T, r: R) => R = t => t as any): { [key: string]: R; } {
|
||||
export function index<T>(array: ReadonlyArray<T>, indexer: (t: T) => string): { [key: string]: T; };
|
||||
export function index<T, R>(array: ReadonlyArray<T>, indexer: (t: T) => string, merger?: (t: T, r: R) => R): { [key: string]: R; };
|
||||
export function index<T, R>(array: ReadonlyArray<T>, indexer: (t: T) => string, merger: (t: T, r: R) => R = t => t as any): { [key: string]: R; } {
|
||||
return array.reduce((r, t) => {
|
||||
const key = indexer(t);
|
||||
r[key] = merger(t, r[key]);
|
||||
@@ -488,7 +484,6 @@ export function arrayInsert<T>(target: T[], insertIndex: number, insertArr: T[])
|
||||
|
||||
/**
|
||||
* Uses Fisher-Yates shuffle to shuffle the given array
|
||||
* @param array
|
||||
*/
|
||||
export function shuffle<T>(array: T[], _seed?: number): void {
|
||||
let rand: () => number;
|
||||
@@ -498,7 +493,7 @@ export function shuffle<T>(array: T[], _seed?: number): void {
|
||||
// Seeded random number generator in JS. Modified from:
|
||||
// https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript
|
||||
rand = () => {
|
||||
var x = Math.sin(seed++) * 179426549; // throw away most significant digits and reduce any potential bias
|
||||
const x = Math.sin(seed++) * 179426549; // throw away most significant digits and reduce any potential bias
|
||||
return x - Math.floor(x);
|
||||
};
|
||||
} else {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* Throws an error with the provided message if the provided value does not evaluate to a true Javascript value.
|
||||
*/
|
||||
export function ok(value?: any, message?: string) {
|
||||
if (!value || value === null) {
|
||||
if (!value) {
|
||||
throw new Error(message ? 'Assertion failed (' + message + ')' : 'Assertion Failed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export function isThenable<T>(obj: any): obj is Thenable<T> {
|
||||
return obj && typeof (<Thenable<any>>obj).then === 'function';
|
||||
export function isThenable<T>(obj: any): obj is Promise<T> {
|
||||
return obj && typeof (<Promise<any>>obj).then === 'function';
|
||||
}
|
||||
|
||||
export interface CancelablePromise<T> extends Promise<T> {
|
||||
cancel(): void;
|
||||
}
|
||||
|
||||
export function createCancelablePromise<T>(callback: (token: CancellationToken) => Thenable<T>): CancelablePromise<T> {
|
||||
export function createCancelablePromise<T>(callback: (token: CancellationToken) => Promise<T>): CancelablePromise<T> {
|
||||
const source = new CancellationTokenSource();
|
||||
|
||||
const thenable = callback(source.token);
|
||||
@@ -38,16 +38,19 @@ export function createCancelablePromise<T>(callback: (token: CancellationToken)
|
||||
cancel() {
|
||||
source.cancel();
|
||||
}
|
||||
then<TResult1 = T, TResult2 = never>(resolve?: ((value: T) => TResult1 | Thenable<TResult1>) | undefined | null, reject?: ((reason: any) => TResult2 | Thenable<TResult2>) | undefined | null): Promise<TResult1 | TResult2> {
|
||||
then<TResult1 = T, TResult2 = never>(resolve?: ((value: T) => TResult1 | Promise<TResult1>) | undefined | null, reject?: ((reason: any) => TResult2 | Promise<TResult2>) | undefined | null): Promise<TResult1 | TResult2> {
|
||||
return promise.then(resolve, reject);
|
||||
}
|
||||
catch<TResult = never>(reject?: ((reason: any) => TResult | Thenable<TResult>) | undefined | null): Promise<T | TResult> {
|
||||
catch<TResult = never>(reject?: ((reason: any) => TResult | Promise<TResult>) | undefined | null): Promise<T | TResult> {
|
||||
return this.then(undefined, reject);
|
||||
}
|
||||
finally(onfinally?: (() => void) | undefined | null): Promise<T> {
|
||||
return always(promise, onfinally || (() => { }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function asThenable<T>(callback: () => T | Thenable<T>): Promise<T> {
|
||||
export function asPromise<T>(callback: () => T | Thenable<T>): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
let item = callback();
|
||||
if (isThenable<T>(item)) {
|
||||
@@ -90,9 +93,9 @@ export interface ITask<T> {
|
||||
*/
|
||||
export class Throttler {
|
||||
|
||||
private activePromise: Thenable<any> | null;
|
||||
private queuedPromise: Thenable<any> | null;
|
||||
private queuedPromiseFactory: ITask<Thenable<any>> | null;
|
||||
private activePromise: Promise<any> | null;
|
||||
private queuedPromise: Promise<any> | null;
|
||||
private queuedPromiseFactory: ITask<Promise<any>> | null;
|
||||
|
||||
constructor() {
|
||||
this.activePromise = null;
|
||||
@@ -100,7 +103,7 @@ export class Throttler {
|
||||
this.queuedPromiseFactory = null;
|
||||
}
|
||||
|
||||
queue<T>(promiseFactory: ITask<Thenable<T>>): Thenable<T> {
|
||||
queue<T>(promiseFactory: ITask<Promise<T>>): Promise<T> {
|
||||
if (this.activePromise) {
|
||||
this.queuedPromiseFactory = promiseFactory;
|
||||
|
||||
@@ -142,7 +145,7 @@ export class Sequencer {
|
||||
|
||||
private current: Promise<any> = Promise.resolve(null);
|
||||
|
||||
queue<T>(promiseTask: ITask<Thenable<T>>): Thenable<T> {
|
||||
queue<T>(promiseTask: ITask<Promise<T>>): Promise<T> {
|
||||
return this.current = this.current.then(() => promiseTask());
|
||||
}
|
||||
}
|
||||
@@ -173,10 +176,10 @@ export class Sequencer {
|
||||
export class Delayer<T> implements IDisposable {
|
||||
|
||||
private timeout: any;
|
||||
private completionPromise: Thenable<any> | null;
|
||||
private doResolve: ((value?: any | Thenable<any>) => void) | null;
|
||||
private completionPromise: Promise<any> | null;
|
||||
private doResolve: ((value?: any | Promise<any>) => void) | null;
|
||||
private doReject: (err: any) => void;
|
||||
private task: ITask<T | Thenable<T>> | null;
|
||||
private task: ITask<T | Promise<T>> | null;
|
||||
|
||||
constructor(public defaultDelay: number) {
|
||||
this.timeout = null;
|
||||
@@ -185,7 +188,7 @@ export class Delayer<T> implements IDisposable {
|
||||
this.task = null;
|
||||
}
|
||||
|
||||
trigger(task: ITask<T | Thenable<T>>, delay: number = this.defaultDelay): Thenable<T> {
|
||||
trigger(task: ITask<T | Promise<T>>, delay: number = this.defaultDelay): Promise<T> {
|
||||
this.task = task;
|
||||
this.cancelTimeout();
|
||||
|
||||
@@ -247,7 +250,7 @@ export class Delayer<T> implements IDisposable {
|
||||
*/
|
||||
export class ThrottledDelayer<T> {
|
||||
|
||||
private delayer: Delayer<Thenable<T>>;
|
||||
private delayer: Delayer<Promise<T>>;
|
||||
private throttler: Throttler;
|
||||
|
||||
constructor(defaultDelay: number) {
|
||||
@@ -255,8 +258,8 @@ export class ThrottledDelayer<T> {
|
||||
this.throttler = new Throttler();
|
||||
}
|
||||
|
||||
trigger(promiseFactory: ITask<Thenable<T>>, delay?: number): Thenable<T> {
|
||||
return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as any as Thenable<T>;
|
||||
trigger(promiseFactory: ITask<Promise<T>>, delay?: number): Promise<T> {
|
||||
return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as any as Promise<T>;
|
||||
}
|
||||
|
||||
isTriggered(): boolean {
|
||||
@@ -303,8 +306,8 @@ export class Barrier {
|
||||
}
|
||||
|
||||
export function timeout(millis: number): CancelablePromise<void>;
|
||||
export function timeout(millis: number, token: CancellationToken): Thenable<void>;
|
||||
export function timeout(millis: number, token?: CancellationToken): CancelablePromise<void> | Thenable<void> {
|
||||
export function timeout(millis: number, token: CancellationToken): Promise<void>;
|
||||
export function timeout(millis: number, token?: CancellationToken): CancelablePromise<void> | Promise<void> {
|
||||
if (!token) {
|
||||
return createCancelablePromise(token => timeout(millis, token));
|
||||
}
|
||||
@@ -318,13 +321,9 @@ export function timeout(millis: number, token?: CancellationToken): CancelablePr
|
||||
});
|
||||
}
|
||||
|
||||
export function disposableTimeout(handler: Function, timeout = 0): IDisposable {
|
||||
export function disposableTimeout(handler: () => void, timeout = 0): IDisposable {
|
||||
const timer = setTimeout(handler, timeout);
|
||||
return {
|
||||
dispose() {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
};
|
||||
return toDisposable(() => clearTimeout(timer));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,7 +333,7 @@ export function disposableTimeout(handler: Function, timeout = 0): IDisposable {
|
||||
* @param promise a promise
|
||||
* @param callback a function that will be call in the success and error case.
|
||||
*/
|
||||
export function always<T>(promise: Thenable<T>, callback: () => void): Promise<T> {
|
||||
export function always<T>(promise: Promise<T>, callback: () => void): Promise<T> {
|
||||
function safeCallback() {
|
||||
try {
|
||||
callback();
|
||||
@@ -346,7 +345,7 @@ export function always<T>(promise: Thenable<T>, callback: () => void): Promise<T
|
||||
return Promise.resolve(promise);
|
||||
}
|
||||
|
||||
export function ignoreErrors<T>(promise: Thenable<T>): Thenable<T | undefined> {
|
||||
export function ignoreErrors<T>(promise: Promise<T>): Promise<T | undefined> {
|
||||
return promise.then(undefined, _ => undefined);
|
||||
}
|
||||
|
||||
@@ -355,16 +354,16 @@ export function ignoreErrors<T>(promise: Thenable<T>): Thenable<T | undefined> {
|
||||
* promise will complete to an array of results from each promise.
|
||||
*/
|
||||
|
||||
export function sequence<T>(promiseFactories: ITask<Thenable<T>>[]): Promise<T[]> {
|
||||
export function sequence<T>(promiseFactories: ITask<Promise<T>>[]): Promise<T[]> {
|
||||
const results: T[] = [];
|
||||
let index = 0;
|
||||
const len = promiseFactories.length;
|
||||
|
||||
function next(): Thenable<T> | null {
|
||||
function next(): Promise<T> | null {
|
||||
return index < len ? promiseFactories[index++]() : null;
|
||||
}
|
||||
|
||||
function thenHandler(result: any): Thenable<any> {
|
||||
function thenHandler(result: any): Promise<any> {
|
||||
if (result !== undefined && result !== null) {
|
||||
results.push(result);
|
||||
}
|
||||
@@ -380,7 +379,7 @@ export function sequence<T>(promiseFactories: ITask<Thenable<T>>[]): Promise<T[]
|
||||
return Promise.resolve(null).then(thenHandler);
|
||||
}
|
||||
|
||||
export function first<T>(promiseFactories: ITask<Thenable<T>>[], shouldStop: (t: T) => boolean = t => !!t, defaultValue: T | null = null): Promise<T | null> {
|
||||
export function first<T>(promiseFactories: ITask<Promise<T>>[], shouldStop: (t: T) => boolean = t => !!t, defaultValue: T | null = null): Promise<T | null> {
|
||||
let index = 0;
|
||||
const len = promiseFactories.length;
|
||||
|
||||
@@ -405,8 +404,8 @@ export function first<T>(promiseFactories: ITask<Thenable<T>>[], shouldStop: (t:
|
||||
}
|
||||
|
||||
interface ILimitedTaskFactory<T> {
|
||||
factory: ITask<Thenable<T>>;
|
||||
c: (value?: T | Thenable<T>) => void;
|
||||
factory: ITask<Promise<T>>;
|
||||
c: (value?: T | Promise<T>) => void;
|
||||
e: (error?: any) => void;
|
||||
}
|
||||
|
||||
@@ -438,7 +437,7 @@ export class Limiter<T> {
|
||||
// return this.runningPromises + this.outstandingPromises.length;
|
||||
}
|
||||
|
||||
queue(factory: ITask<Thenable<T>>): Thenable<T> {
|
||||
queue(factory: ITask<Promise<T>>): Promise<T> {
|
||||
this._size++;
|
||||
|
||||
return new Promise<T>((c, e) => {
|
||||
@@ -685,8 +684,8 @@ export function nfcall(fn: Function, ...args: any[]): any {
|
||||
return new Promise((c, e) => fn(...args, (err: any, result: any) => err ? e(err) : c(result)));
|
||||
}
|
||||
|
||||
export function ninvoke(thisArg: any, fn: Function, ...args: any[]): Thenable<any>;
|
||||
export function ninvoke<T>(thisArg: any, fn: Function, ...args: any[]): Thenable<T>;
|
||||
export function ninvoke(thisArg: any, fn: Function, ...args: any[]): Promise<any>;
|
||||
export function ninvoke<T>(thisArg: any, fn: Function, ...args: any[]): Promise<T>;
|
||||
export function ninvoke(thisArg: any, fn: Function, ...args: any[]): any {
|
||||
return new Promise((resolve, reject) => fn.call(thisArg, ...args, (err: any, result: any) => err ? reject(err) : resolve(result)));
|
||||
}
|
||||
@@ -712,8 +711,8 @@ declare function cancelIdleCallback(handle: number): void;
|
||||
didTimeout: true,
|
||||
timeRemaining() { return 15; }
|
||||
});
|
||||
runWhenIdle = (runner, timeout = 0) => {
|
||||
let handle = setTimeout(() => runner(dummyIdle), timeout);
|
||||
runWhenIdle = (runner) => {
|
||||
let handle = setTimeout(() => runner(dummyIdle));
|
||||
let disposed = false;
|
||||
return {
|
||||
dispose() {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"name": "vs/base",
|
||||
"dependencies": [
|
||||
{ "name": "vs", "internal": false }
|
||||
{
|
||||
"name": "vs",
|
||||
"internal": false
|
||||
}
|
||||
],
|
||||
"libs": [
|
||||
"lib.core.d.ts"
|
||||
@@ -9,7 +12,5 @@
|
||||
"sources": [
|
||||
"**/*.ts"
|
||||
],
|
||||
"declares": [
|
||||
"vs/base/winjs.base.d.ts"
|
||||
]
|
||||
}
|
||||
"declares": []
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ export function first<T>(from: IStringDictionary<T> | INumberDictionary<T>): T |
|
||||
* Iterates over each entry in the provided set. The iterator allows
|
||||
* to remove elements and will stop when the callback returns {{false}}.
|
||||
*/
|
||||
export function forEach<T>(from: IStringDictionary<T> | INumberDictionary<T>, callback: (entry: { key: any; value: T; }, remove: Function) => any): void {
|
||||
export function forEach<T>(from: IStringDictionary<T> | INumberDictionary<T>, callback: (entry: { key: any; value: T; }, remove: () => void) => any): void {
|
||||
for (let key in from) {
|
||||
if (hasOwnProperty.call(from, key)) {
|
||||
const result = callback({ key: key, value: (from as any)[key] }, function () {
|
||||
|
||||
@@ -388,7 +388,7 @@ export class Color {
|
||||
const colorA = rgba.a;
|
||||
|
||||
let a = thisA + colorA * (1 - thisA);
|
||||
if (a < 1.0e-6) {
|
||||
if (a < 1e-6) {
|
||||
return Color.transparent;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export function setFileNameComparer(collator: IdleValue<{ collator: Intl.Collato
|
||||
intlFileNameCollator = collator;
|
||||
}
|
||||
|
||||
export function compareFileNames(one: string, other: string, caseSensitive = false): number {
|
||||
export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
|
||||
if (intlFileNameCollator) {
|
||||
const a = one || '';
|
||||
const b = other || '';
|
||||
@@ -33,7 +33,7 @@ export function compareFileNames(one: string, other: string, caseSensitive = fal
|
||||
|
||||
const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
|
||||
|
||||
export function noIntlCompareFileNames(one: string, other: string, caseSensitive = false): number {
|
||||
export function noIntlCompareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
|
||||
if (!caseSensitive) {
|
||||
one = one && one.toLowerCase();
|
||||
other = other && other.toLowerCase();
|
||||
@@ -53,7 +53,7 @@ export function noIntlCompareFileNames(one: string, other: string, caseSensitive
|
||||
return oneExtension < otherExtension ? -1 : 1;
|
||||
}
|
||||
|
||||
export function compareFileExtensions(one: string, other: string): number {
|
||||
export function compareFileExtensions(one: string | null, other: string | null): number {
|
||||
if (intlFileNameCollator) {
|
||||
const [oneName, oneExtension] = extractNameAndExtension(one);
|
||||
const [otherName, otherExtension] = extractNameAndExtension(other);
|
||||
@@ -81,7 +81,7 @@ export function compareFileExtensions(one: string, other: string): number {
|
||||
return noIntlCompareFileExtensions(one, other);
|
||||
}
|
||||
|
||||
function noIntlCompareFileExtensions(one: string, other: string): number {
|
||||
function noIntlCompareFileExtensions(one: string | null, other: string | null): number {
|
||||
const [oneName, oneExtension] = extractNameAndExtension(one && one.toLowerCase());
|
||||
const [otherName, otherExtension] = extractNameAndExtension(other && other.toLowerCase());
|
||||
|
||||
@@ -96,7 +96,7 @@ function noIntlCompareFileExtensions(one: string, other: string): number {
|
||||
return oneName < otherName ? -1 : 1;
|
||||
}
|
||||
|
||||
function extractNameAndExtension(str?: string): [string, string] {
|
||||
function extractNameAndExtension(str?: string | null): [string, string] {
|
||||
const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
|
||||
|
||||
return [(match && match[1]) || '', (match && match[3]) || ''];
|
||||
|
||||
@@ -71,7 +71,7 @@ export function debounce<T>(delay: number, reducer?: IDebouceReducer<T>, initial
|
||||
|
||||
return function (this: any, ...args: any[]) {
|
||||
if (!this[resultKey]) {
|
||||
this[resultKey] = initialValueProvider ? initialValueProvider() : void 0;
|
||||
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
|
||||
}
|
||||
|
||||
clearTimeout(this[timerKey]);
|
||||
@@ -83,7 +83,7 @@ export function debounce<T>(delay: number, reducer?: IDebouceReducer<T>, initial
|
||||
|
||||
this[timerKey] = setTimeout(() => {
|
||||
fn.apply(this, args);
|
||||
this[resultKey] = initialValueProvider ? initialValueProvider() : void 0;
|
||||
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
|
||||
}, delay);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -763,7 +763,7 @@ export class LcsDiff {
|
||||
change.modifiedStart++;
|
||||
}
|
||||
|
||||
let mergedChangeArr: (DiffChange | null)[] = [null];
|
||||
let mergedChangeArr: Array<DiffChange | null> = [null];
|
||||
if (i < changes.length - 1 && this.ChangesOverlap(changes[i], changes[i + 1], mergedChangeArr)) {
|
||||
changes[i] = mergedChangeArr[0]!;
|
||||
changes.splice(i + 1, 1);
|
||||
@@ -913,7 +913,7 @@ export class LcsDiff {
|
||||
* @param mergedChange The merged change if the two overlap, null otherwise
|
||||
* @returns True if the two changes overlap
|
||||
*/
|
||||
private ChangesOverlap(left: DiffChange, right: DiffChange, mergedChangeArr: (DiffChange | null)[]): boolean {
|
||||
private ChangesOverlap(left: DiffChange, right: DiffChange, mergedChangeArr: Array<DiffChange | null>): boolean {
|
||||
Debug.Assert(left.originalStart <= right.originalStart, 'Left change is not less than or equal to right change');
|
||||
Debug.Assert(left.modifiedStart <= right.modifiedStart, 'Left change is not less than or equal to right change');
|
||||
|
||||
|
||||
@@ -3,59 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TPromise, IPromiseError, IPromiseErrorDetail } from 'vs/base/common/winjs.base';
|
||||
|
||||
// ------ BEGIN Hook up error listeners to winjs promises
|
||||
|
||||
let outstandingPromiseErrors: { [id: string]: IPromiseErrorDetail; } = {};
|
||||
function promiseErrorHandler(e: IPromiseError): void {
|
||||
|
||||
//
|
||||
// e.detail looks like: { exception, error, promise, handler, id, parent }
|
||||
//
|
||||
const details = e.detail;
|
||||
const id = details.id;
|
||||
|
||||
// If the error has a parent promise then this is not the origination of the
|
||||
// error so we check if it has a handler, and if so we mark that the error
|
||||
// was handled by removing it from outstandingPromiseErrors
|
||||
//
|
||||
if (details.parent) {
|
||||
if (details.handler && outstandingPromiseErrors) {
|
||||
delete outstandingPromiseErrors[id];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Indicate that this error was originated and needs to be handled
|
||||
outstandingPromiseErrors[id] = details;
|
||||
|
||||
// The first time the queue fills up this iteration, schedule a timeout to
|
||||
// check if any errors are still unhandled.
|
||||
if (Object.keys(outstandingPromiseErrors).length === 1) {
|
||||
setTimeout(function () {
|
||||
const errors = outstandingPromiseErrors;
|
||||
outstandingPromiseErrors = {};
|
||||
Object.keys(errors).forEach(function (errorId) {
|
||||
const error = errors[errorId];
|
||||
if (error.exception) {
|
||||
onUnexpectedError(error.exception);
|
||||
} else if (error.error) {
|
||||
onUnexpectedError(error.error);
|
||||
}
|
||||
console.log('WARNING: Promise with no error callback:' + error.id);
|
||||
console.log(error);
|
||||
if (error.exception) {
|
||||
console.log(error.exception.stack);
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
TPromise.addEventListener('error', promiseErrorHandler);
|
||||
|
||||
// ------ END Hook up error listeners to winjs promises
|
||||
|
||||
export interface ErrorListenerCallback {
|
||||
(error: any): void;
|
||||
}
|
||||
|
||||
@@ -19,9 +19,348 @@ export interface Event<T> {
|
||||
export namespace Event {
|
||||
const _disposable = { dispose() { } };
|
||||
export const None: Event<any> = function () { return _disposable; };
|
||||
|
||||
/**
|
||||
* Given an event, returns another event which only fires once.
|
||||
*/
|
||||
export function once<T>(event: Event<T>): Event<T> {
|
||||
return (listener, thisArgs = null, disposables?) => {
|
||||
// we need this, in case the event fires during the listener call
|
||||
let didFire = false;
|
||||
|
||||
const result = event(e => {
|
||||
if (didFire) {
|
||||
return;
|
||||
} else if (result) {
|
||||
result.dispose();
|
||||
} else {
|
||||
didFire = true;
|
||||
}
|
||||
|
||||
return listener.call(thisArgs, e);
|
||||
}, null, disposables);
|
||||
|
||||
if (didFire) {
|
||||
result.dispose();
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an event and a `map` function, returns another event which maps each element
|
||||
* throught the mapping function.
|
||||
*/
|
||||
export function map<I, O>(event: Event<I>, map: (i: I) => O): Event<O> {
|
||||
return (listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an event and an `each` function, returns another identical event and calls
|
||||
* the `each` function per each element.
|
||||
*/
|
||||
export function forEach<I>(event: Event<I>, each: (i: I) => void): Event<I> {
|
||||
return (listener, thisArgs = null, disposables?) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an event and a `filter` function, returns another event which emits those
|
||||
* elements for which the `filter` function returns `true`.
|
||||
*/
|
||||
export function filter<T>(event: Event<T>, filter: (e: T) => boolean): Event<T>;
|
||||
export function filter<T, R>(event: Event<T | R>, filter: (e: T | R) => e is R): Event<R>;
|
||||
export function filter<T>(event: Event<T>, filter: (e: T) => boolean): Event<T> {
|
||||
return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an event, returns the same event but typed as `Event<void>`.
|
||||
*/
|
||||
export function signal<T>(event: Event<T>): Event<void> {
|
||||
return event as Event<any> as Event<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a collection of events, returns a single event which emits
|
||||
* whenever any of the provided events emit.
|
||||
*/
|
||||
export function any<T>(...events: Event<T>[]): Event<T> {
|
||||
return (listener, thisArgs = null, disposables?) => combinedDisposable(events.map(event => event(e => listener.call(thisArgs, e), null, disposables)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an event and a `merge` function, returns another event which maps each element
|
||||
* and the cummulative result throught the `merge` function. Similar to `map`, but with memory.
|
||||
*/
|
||||
export function reduce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, initial?: O): Event<O> {
|
||||
let output: O | undefined = initial;
|
||||
|
||||
return map<I, O>(event, e => {
|
||||
output = merge(output, e);
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Debounces the provided event, given a `merge` function.
|
||||
*
|
||||
* @param event The input event.
|
||||
* @param merge The reducing function.
|
||||
* @param delay The debouncing delay in millis.
|
||||
* @param leading Whether the event should fire in the leading phase of the timeout.
|
||||
* @param leakWarningThreshold The leak warning threshold override.
|
||||
*/
|
||||
export function debounce<T>(event: Event<T>, merge: (last: T, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number): Event<T>;
|
||||
export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay?: number, leading?: boolean, leakWarningThreshold?: number): Event<O>;
|
||||
export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay: number = 100, leading = false, leakWarningThreshold?: number): Event<O> {
|
||||
|
||||
let subscription: IDisposable;
|
||||
let output: O | undefined = undefined;
|
||||
let handle: any = undefined;
|
||||
let numDebouncedCalls = 0;
|
||||
|
||||
const emitter = new Emitter<O>({
|
||||
leakWarningThreshold,
|
||||
onFirstListenerAdd() {
|
||||
subscription = event(cur => {
|
||||
numDebouncedCalls++;
|
||||
output = merge(output, cur);
|
||||
|
||||
if (leading && !handle) {
|
||||
emitter.fire(output);
|
||||
}
|
||||
|
||||
clearTimeout(handle);
|
||||
handle = setTimeout(() => {
|
||||
let _output = output;
|
||||
output = undefined;
|
||||
handle = undefined;
|
||||
if (!leading || numDebouncedCalls > 1) {
|
||||
emitter.fire(_output!);
|
||||
}
|
||||
|
||||
numDebouncedCalls = 0;
|
||||
}, delay);
|
||||
});
|
||||
},
|
||||
onLastListenerRemove() {
|
||||
subscription.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an event, it returns another event which fires only once and as soon as
|
||||
* the input event emits. The event data is the number of millis it took for the
|
||||
* event to fire.
|
||||
*/
|
||||
export function stopwatch<T>(event: Event<T>): Event<number> {
|
||||
const start = new Date().getTime();
|
||||
return map(once(event), _ => new Date().getTime() - start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an event, it returns another event which fires only when the event
|
||||
* element changes.
|
||||
*/
|
||||
export function latch<T>(event: Event<T>): Event<T> {
|
||||
let firstCall = true;
|
||||
let cache: T;
|
||||
|
||||
return filter(event, value => {
|
||||
let shouldEmit = firstCall || value !== cache;
|
||||
firstCall = false;
|
||||
cache = value;
|
||||
return shouldEmit;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Buffers the provided event until a first listener comes
|
||||
* along, at which point fire all the events at once and
|
||||
* pipe the event from then on.
|
||||
*
|
||||
* ```typescript
|
||||
* const emitter = new Emitter<number>();
|
||||
* const event = emitter.event;
|
||||
* const bufferedEvent = buffer(event);
|
||||
*
|
||||
* emitter.fire(1);
|
||||
* emitter.fire(2);
|
||||
* emitter.fire(3);
|
||||
* // nothing...
|
||||
*
|
||||
* const listener = bufferedEvent(num => console.log(num));
|
||||
* // 1, 2, 3
|
||||
*
|
||||
* emitter.fire(4);
|
||||
* // 4
|
||||
* ```
|
||||
*/
|
||||
export function buffer<T>(event: Event<T>, nextTick = false, _buffer: T[] = []): Event<T> {
|
||||
let buffer: T[] | null = _buffer.slice();
|
||||
|
||||
let listener: IDisposable | null = event(e => {
|
||||
if (buffer) {
|
||||
buffer.push(e);
|
||||
} else {
|
||||
emitter.fire(e);
|
||||
}
|
||||
});
|
||||
|
||||
const flush = () => {
|
||||
if (buffer) {
|
||||
buffer.forEach(e => emitter.fire(e));
|
||||
}
|
||||
buffer = null;
|
||||
};
|
||||
|
||||
const emitter = new Emitter<T>({
|
||||
onFirstListenerAdd() {
|
||||
if (!listener) {
|
||||
listener = event(e => emitter.fire(e));
|
||||
}
|
||||
},
|
||||
|
||||
onFirstListenerDidAdd() {
|
||||
if (buffer) {
|
||||
if (nextTick) {
|
||||
setTimeout(flush);
|
||||
} else {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onLastListenerRemove() {
|
||||
if (listener) {
|
||||
listener.dispose();
|
||||
}
|
||||
listener = null;
|
||||
}
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to `buffer` but it buffers indefinitely and repeats
|
||||
* the buffered events to every new listener.
|
||||
*/
|
||||
export function echo<T>(event: Event<T>, nextTick = false, buffer: T[] = []): Event<T> {
|
||||
buffer = buffer.slice();
|
||||
|
||||
event(e => {
|
||||
buffer.push(e);
|
||||
emitter.fire(e);
|
||||
});
|
||||
|
||||
const flush = (listener: (e: T) => any, thisArgs?: any) => buffer.forEach(e => listener.call(thisArgs, e));
|
||||
|
||||
const emitter = new Emitter<T>({
|
||||
onListenerDidAdd(emitter, listener: (e: T) => any, thisArgs?: any) {
|
||||
if (nextTick) {
|
||||
setTimeout(() => flush(listener, thisArgs));
|
||||
} else {
|
||||
flush(listener, thisArgs);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
export interface IChainableEvent<T> {
|
||||
event: Event<T>;
|
||||
map<O>(fn: (i: T) => O): IChainableEvent<O>;
|
||||
forEach(fn: (i: T) => void): IChainableEvent<T>;
|
||||
filter(fn: (e: T) => boolean): IChainableEvent<T>;
|
||||
reduce<R>(merge: (last: R | undefined, event: T) => R, initial?: R): IChainableEvent<R>;
|
||||
latch(): IChainableEvent<T>;
|
||||
on(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable;
|
||||
once(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable;
|
||||
}
|
||||
|
||||
class ChainableEvent<T> implements IChainableEvent<T> {
|
||||
|
||||
get event(): Event<T> { return this._event; }
|
||||
|
||||
constructor(private _event: Event<T>) { }
|
||||
|
||||
map<O>(fn: (i: T) => O): IChainableEvent<O> {
|
||||
return new ChainableEvent(map(this._event, fn));
|
||||
}
|
||||
|
||||
forEach(fn: (i: T) => void): IChainableEvent<T> {
|
||||
return new ChainableEvent(forEach(this._event, fn));
|
||||
}
|
||||
|
||||
filter(fn: (e: T) => boolean): IChainableEvent<T> {
|
||||
return new ChainableEvent(filter(this._event, fn));
|
||||
}
|
||||
|
||||
reduce<R>(merge: (last: R | undefined, event: T) => R, initial?: R): IChainableEvent<R> {
|
||||
return new ChainableEvent(reduce(this._event, merge, initial));
|
||||
}
|
||||
|
||||
latch(): IChainableEvent<T> {
|
||||
return new ChainableEvent(latch(this._event));
|
||||
}
|
||||
|
||||
on(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[]) {
|
||||
return this._event(listener, thisArgs, disposables);
|
||||
}
|
||||
|
||||
once(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[]) {
|
||||
return once(this._event)(listener, thisArgs, disposables);
|
||||
}
|
||||
}
|
||||
|
||||
export function chain<T>(event: Event<T>): IChainableEvent<T> {
|
||||
return new ChainableEvent(event);
|
||||
}
|
||||
|
||||
export interface NodeEventEmitter {
|
||||
on(event: string | symbol, listener: Function): this;
|
||||
removeListener(event: string | symbol, listener: Function): this;
|
||||
}
|
||||
|
||||
export function fromNodeEventEmitter<T>(emitter: NodeEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> {
|
||||
const fn = (...args: any[]) => result.fire(map(...args));
|
||||
const onFirstListenerAdd = () => emitter.on(eventName, fn);
|
||||
const onLastListenerRemove = () => emitter.removeListener(eventName, fn);
|
||||
const result = new Emitter<T>({ onFirstListenerAdd, onLastListenerRemove });
|
||||
|
||||
return result.event;
|
||||
}
|
||||
|
||||
export function fromPromise<T = any>(promise: Promise<T>): Event<undefined> {
|
||||
const emitter = new Emitter<undefined>();
|
||||
let shouldEmit = false;
|
||||
|
||||
promise
|
||||
.then(undefined, () => null)
|
||||
.then(() => {
|
||||
if (!shouldEmit) {
|
||||
setTimeout(() => emitter.fire(undefined), 0);
|
||||
} else {
|
||||
emitter.fire(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
shouldEmit = true;
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
export function toPromise<T>(event: Event<T>): Promise<T> {
|
||||
return new Promise(c => once(event)(c));
|
||||
}
|
||||
}
|
||||
|
||||
type Listener = [Function, any] | Function;
|
||||
type Listener<T> = [(e: T) => void, any] | ((e: T) => void);
|
||||
|
||||
export interface EmitterOptions {
|
||||
onFirstListenerAdd?: Function;
|
||||
@@ -80,7 +419,7 @@ class LeakageMonitor {
|
||||
if (this._warnCountdown <= 0) {
|
||||
// only warn on first exceed and then every time the limit
|
||||
// is exceeded by 50% again
|
||||
this._warnCountdown = threshold * .5;
|
||||
this._warnCountdown = threshold * 0.5;
|
||||
|
||||
// find most frequent listener and print warning
|
||||
let topStack: string;
|
||||
@@ -128,12 +467,12 @@ export class Emitter<T> {
|
||||
|
||||
private static readonly _noop = function () { };
|
||||
|
||||
private readonly _options: EmitterOptions | undefined;
|
||||
private readonly _leakageMon: LeakageMonitor | undefined;
|
||||
private readonly _options?: EmitterOptions;
|
||||
private readonly _leakageMon?: LeakageMonitor;
|
||||
private _disposed: boolean = false;
|
||||
private _event: Event<T> | undefined;
|
||||
private _deliveryQueue: [Listener, (T | undefined)][] | undefined;
|
||||
protected _listeners: LinkedList<Listener> | undefined;
|
||||
private _event?: Event<T>;
|
||||
private _deliveryQueue: [Listener<T>, T][];
|
||||
protected _listeners?: LinkedList<Listener<T>>;
|
||||
|
||||
constructor(options?: EmitterOptions) {
|
||||
this._options = options;
|
||||
@@ -207,7 +546,7 @@ export class Emitter<T> {
|
||||
* To be kept private to fire an event to
|
||||
* subscribers
|
||||
*/
|
||||
fire(event?: T): any {
|
||||
fire(event: T): void {
|
||||
if (this._listeners) {
|
||||
// put all [listener,event]-pairs into delivery queue
|
||||
// then emit all event. an inner/nested event might be
|
||||
@@ -251,14 +590,14 @@ export class Emitter<T> {
|
||||
}
|
||||
|
||||
export interface IWaitUntil {
|
||||
waitUntil(thenable: Thenable<any>): void;
|
||||
waitUntil(thenable: Promise<any>): void;
|
||||
}
|
||||
|
||||
export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {
|
||||
|
||||
private _asyncDeliveryQueue: [Listener, T, Thenable<any>[]][];
|
||||
private _asyncDeliveryQueue: [Listener<T>, T, Promise<any>[]][];
|
||||
|
||||
async fireAsync(eventFn: (thenables: Thenable<any>[], listener: Function) => T): Promise<void> {
|
||||
async fireAsync(eventFn: (thenables: Promise<any>[], listener: Function) => T): Promise<void> {
|
||||
if (!this._listeners) {
|
||||
return;
|
||||
}
|
||||
@@ -271,7 +610,7 @@ export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {
|
||||
}
|
||||
|
||||
for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) {
|
||||
let thenables: Thenable<void>[] = [];
|
||||
let thenables: Promise<void>[] = [];
|
||||
this._asyncDeliveryQueue.push([e.value, eventFn(thenables, typeof e.value === 'function' ? e.value : e.value[0]), thenables]);
|
||||
}
|
||||
|
||||
@@ -359,98 +698,6 @@ export class EventMultiplexer<T> implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
export function fromPromise<T =any>(promise: Thenable<T>): Event<T> {
|
||||
const emitter = new Emitter<T>();
|
||||
let shouldEmit = false;
|
||||
|
||||
promise
|
||||
.then(undefined, () => null)
|
||||
.then(() => {
|
||||
if (!shouldEmit) {
|
||||
setTimeout(() => emitter.fire(), 0);
|
||||
} else {
|
||||
emitter.fire();
|
||||
}
|
||||
});
|
||||
|
||||
shouldEmit = true;
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
export function toPromise<T>(event: Event<T>): Thenable<T> {
|
||||
return new Promise(c => once(event)(c));
|
||||
}
|
||||
|
||||
export function once<T>(event: Event<T>): Event<T> {
|
||||
return (listener, thisArgs = null, disposables?) => {
|
||||
// we need this, in case the event fires during the listener call
|
||||
let didFire = false;
|
||||
|
||||
const result = event(e => {
|
||||
if (didFire) {
|
||||
return;
|
||||
} else if (result) {
|
||||
result.dispose();
|
||||
} else {
|
||||
didFire = true;
|
||||
}
|
||||
|
||||
return listener.call(thisArgs, e);
|
||||
}, null, disposables);
|
||||
|
||||
if (didFire) {
|
||||
result.dispose();
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
export function anyEvent<T>(...events: Event<T>[]): Event<T> {
|
||||
return (listener, thisArgs = null, disposables?) => combinedDisposable(events.map(event => event(e => listener.call(thisArgs, e), null, disposables)));
|
||||
}
|
||||
|
||||
export function debounceEvent<T>(event: Event<T>, merger: (last: T, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number): Event<T>;
|
||||
export function debounceEvent<I, O>(event: Event<I>, merger: (last: O | undefined, event: I) => O, delay?: number, leading?: boolean, leakWarningThreshold?: number): Event<O>;
|
||||
export function debounceEvent<I, O>(event: Event<I>, merger: (last: O | undefined, event: I) => O, delay: number = 100, leading = false, leakWarningThreshold?: number): Event<O> {
|
||||
|
||||
let subscription: IDisposable;
|
||||
let output: O | undefined = undefined;
|
||||
let handle: any = undefined;
|
||||
let numDebouncedCalls = 0;
|
||||
|
||||
const emitter = new Emitter<O>({
|
||||
leakWarningThreshold,
|
||||
onFirstListenerAdd() {
|
||||
subscription = event(cur => {
|
||||
numDebouncedCalls++;
|
||||
output = merger(output, cur);
|
||||
|
||||
if (leading && !handle) {
|
||||
emitter.fire(output);
|
||||
}
|
||||
|
||||
clearTimeout(handle);
|
||||
handle = setTimeout(() => {
|
||||
let _output = output;
|
||||
output = undefined;
|
||||
handle = undefined;
|
||||
if (!leading || numDebouncedCalls > 1) {
|
||||
emitter.fire(_output);
|
||||
}
|
||||
|
||||
numDebouncedCalls = 0;
|
||||
}, delay);
|
||||
});
|
||||
},
|
||||
onLastListenerRemove() {
|
||||
subscription.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* The EventBufferer is useful in situations in which you want
|
||||
* to delay firing your events during some code.
|
||||
@@ -485,12 +732,12 @@ export class EventBufferer {
|
||||
} else {
|
||||
listener.call(thisArgs, i);
|
||||
}
|
||||
}, void 0, disposables);
|
||||
}, undefined, disposables);
|
||||
};
|
||||
}
|
||||
|
||||
bufferEvents<R = void>(fn: () => R): R {
|
||||
const buffer: Function[] = [];
|
||||
const buffer: Array<() => R> = [];
|
||||
this.buffers.push(buffer);
|
||||
const r = fn();
|
||||
this.buffers.pop();
|
||||
@@ -499,169 +746,12 @@ export class EventBufferer {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IChainableEvent<T> {
|
||||
event: Event<T>;
|
||||
map<O>(fn: (i: T) => O): IChainableEvent<O>;
|
||||
forEach(fn: (i: T) => void): IChainableEvent<T>;
|
||||
filter(fn: (e: T) => boolean): IChainableEvent<T>;
|
||||
latch(): IChainableEvent<T>;
|
||||
on(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable;
|
||||
once(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable;
|
||||
}
|
||||
|
||||
export function mapEvent<I, O>(event: Event<I>, map: (i: I) => O): Event<O> {
|
||||
return (listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables);
|
||||
}
|
||||
|
||||
export function forEach<I>(event: Event<I>, each: (i: I) => void): Event<I> {
|
||||
return (listener, thisArgs = null, disposables?) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables);
|
||||
}
|
||||
|
||||
export function filterEvent<T>(event: Event<T>, filter: (e: T) => boolean): Event<T>;
|
||||
export function filterEvent<T, R>(event: Event<T | R>, filter: (e: T | R) => e is R): Event<R>;
|
||||
export function filterEvent<T>(event: Event<T>, filter: (e: T) => boolean): Event<T> {
|
||||
return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables);
|
||||
}
|
||||
|
||||
export function signalEvent<T>(event: Event<T>): Event<void> {
|
||||
return event as Event<any> as Event<void>;
|
||||
}
|
||||
|
||||
class ChainableEvent<T> implements IChainableEvent<T> {
|
||||
|
||||
get event(): Event<T> { return this._event; }
|
||||
|
||||
constructor(private _event: Event<T>) { }
|
||||
|
||||
map<O>(fn: (i: T) => O): IChainableEvent<O> {
|
||||
return new ChainableEvent(mapEvent(this._event, fn));
|
||||
}
|
||||
|
||||
forEach(fn: (i: T) => void): IChainableEvent<T> {
|
||||
return new ChainableEvent(forEach(this._event, fn));
|
||||
}
|
||||
|
||||
filter(fn: (e: T) => boolean): IChainableEvent<T> {
|
||||
return new ChainableEvent(filterEvent(this._event, fn));
|
||||
}
|
||||
|
||||
latch(): IChainableEvent<T> {
|
||||
return new ChainableEvent(latch(this._event));
|
||||
}
|
||||
|
||||
on(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[]) {
|
||||
return this._event(listener, thisArgs, disposables);
|
||||
}
|
||||
|
||||
once(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[]) {
|
||||
return once(this._event)(listener, thisArgs, disposables);
|
||||
}
|
||||
}
|
||||
|
||||
export function chain<T>(event: Event<T>): IChainableEvent<T> {
|
||||
return new ChainableEvent(event);
|
||||
}
|
||||
|
||||
export function stopwatch<T>(event: Event<T>): Event<number> {
|
||||
const start = new Date().getTime();
|
||||
return mapEvent(once(event), _ => new Date().getTime() - start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Buffers the provided event until a first listener comes
|
||||
* along, at which point fire all the events at once and
|
||||
* pipe the event from then on.
|
||||
*
|
||||
* ```typescript
|
||||
* const emitter = new Emitter<number>();
|
||||
* const event = emitter.event;
|
||||
* const bufferedEvent = buffer(event);
|
||||
*
|
||||
* emitter.fire(1);
|
||||
* emitter.fire(2);
|
||||
* emitter.fire(3);
|
||||
* // nothing...
|
||||
*
|
||||
* const listener = bufferedEvent(num => console.log(num));
|
||||
* // 1, 2, 3
|
||||
*
|
||||
* emitter.fire(4);
|
||||
* // 4
|
||||
* ```
|
||||
* A Relay is an event forwarder which functions as a replugabble event pipe.
|
||||
* Once created, you can connect an input event to it and it will simply forward
|
||||
* events from that input event through its own `event` property. The `input`
|
||||
* can be changed at any point in time.
|
||||
*/
|
||||
export function buffer<T>(event: Event<T>, nextTick = false, _buffer: T[] = []): Event<T> {
|
||||
let buffer: T[] | null = _buffer.slice();
|
||||
|
||||
let listener: IDisposable | null = event(e => {
|
||||
if (buffer) {
|
||||
buffer.push(e);
|
||||
} else {
|
||||
emitter.fire(e);
|
||||
}
|
||||
});
|
||||
|
||||
const flush = () => {
|
||||
if (buffer) {
|
||||
buffer.forEach(e => emitter.fire(e));
|
||||
}
|
||||
buffer = null;
|
||||
};
|
||||
|
||||
const emitter = new Emitter<T>({
|
||||
onFirstListenerAdd() {
|
||||
if (!listener) {
|
||||
listener = event(e => emitter.fire(e));
|
||||
}
|
||||
},
|
||||
|
||||
onFirstListenerDidAdd() {
|
||||
if (buffer) {
|
||||
if (nextTick) {
|
||||
setTimeout(flush);
|
||||
} else {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onLastListenerRemove() {
|
||||
if (listener) {
|
||||
listener.dispose();
|
||||
}
|
||||
listener = null;
|
||||
}
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to `buffer` but it buffers indefinitely and repeats
|
||||
* the buffered events to every new listener.
|
||||
*/
|
||||
export function echo<T>(event: Event<T>, nextTick = false, buffer: T[] = []): Event<T> {
|
||||
buffer = buffer.slice();
|
||||
|
||||
event(e => {
|
||||
buffer.push(e);
|
||||
emitter.fire(e);
|
||||
});
|
||||
|
||||
const flush = (listener: (e: T) => any, thisArgs?: any) => buffer.forEach(e => listener.call(thisArgs, e));
|
||||
|
||||
const emitter = new Emitter<T>({
|
||||
onListenerDidAdd(emitter, listener: (e: T) => any, thisArgs?: any) {
|
||||
if (nextTick) {
|
||||
setTimeout(() => flush(listener, thisArgs));
|
||||
} else {
|
||||
flush(listener, thisArgs);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
export class Relay<T> implements IDisposable {
|
||||
|
||||
private listening = false;
|
||||
@@ -695,29 +785,3 @@ export class Relay<T> implements IDisposable {
|
||||
this.emitter.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export interface NodeEventEmitter {
|
||||
on(event: string | symbol, listener: Function): this;
|
||||
removeListener(event: string | symbol, listener: Function): this;
|
||||
}
|
||||
|
||||
export function fromNodeEventEmitter<T>(emitter: NodeEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> {
|
||||
const fn = (...args: any[]) => result.fire(map(...args));
|
||||
const onFirstListenerAdd = () => emitter.on(eventName, fn);
|
||||
const onLastListenerRemove = () => emitter.removeListener(eventName, fn);
|
||||
const result = new Emitter<T>({ onFirstListenerAdd, onLastListenerRemove });
|
||||
|
||||
return result.event;
|
||||
}
|
||||
|
||||
export function latch<T>(event: Event<T>): Event<T> {
|
||||
let firstCall = true;
|
||||
let cache: T;
|
||||
|
||||
return filterEvent(event, value => {
|
||||
let shouldEmit = firstCall || value !== cache;
|
||||
firstCall = false;
|
||||
cache = value;
|
||||
return shouldEmit;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -317,7 +317,7 @@ function nextWord(word: string, start: number): number {
|
||||
|
||||
// Fuzzy
|
||||
|
||||
export const fuzzyContiguousFilter = or(matchesPrefix, matchesCamelCase, matchesContiguousSubString);
|
||||
const fuzzyContiguousFilter = or(matchesPrefix, matchesCamelCase, matchesContiguousSubString);
|
||||
const fuzzySeparateFilter = or(matchesPrefix, matchesCamelCase, matchesSubString);
|
||||
const fuzzyRegExpCache = new LRUCache<string, RegExp>(10000); // bounded to 10000 elements
|
||||
|
||||
@@ -343,54 +343,67 @@ export function matchesFuzzy(word: string, wordToMatchAgainst: string, enableSep
|
||||
return enableSeparateSubstringMatching ? fuzzySeparateFilter(word, wordToMatchAgainst) : fuzzyContiguousFilter(word, wordToMatchAgainst);
|
||||
}
|
||||
|
||||
export function anyScore(pattern: string, word: string, patternMaxWhitespaceIgnore?: number): FuzzyScore {
|
||||
pattern = pattern.toLowerCase();
|
||||
word = word.toLowerCase();
|
||||
/**
|
||||
* Match pattern againt word in a fuzzy way. As in IntelliSense and faster and more
|
||||
* powerfull than `matchesFuzzy`
|
||||
*/
|
||||
export function matchesFuzzy2(pattern: string, word: string): IMatch[] | null {
|
||||
let score = fuzzyScore(pattern, pattern.toLowerCase(), 0, word, word.toLowerCase(), 0, true);
|
||||
return score ? createMatches(score) : null;
|
||||
}
|
||||
|
||||
const matches: number[] = [];
|
||||
let idx = 0;
|
||||
for (let pos = 0; pos < pattern.length; ++pos) {
|
||||
const thisIdx = word.indexOf(pattern.charAt(pos), idx);
|
||||
if (thisIdx >= 0) {
|
||||
matches.push(thisIdx);
|
||||
idx = thisIdx + 1;
|
||||
export function anyScore(pattern: string, lowPattern: string, _patternPos: number, word: string, lowWord: string, _wordPos: number): FuzzyScore {
|
||||
const result = fuzzyScore(pattern, lowPattern, 0, word, lowWord, 0, true);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
let matches = 0;
|
||||
let score = 0;
|
||||
let idx = _wordPos;
|
||||
for (let patternPos = 0; patternPos < lowPattern.length && patternPos < _maxLen; ++patternPos) {
|
||||
const wordPos = lowWord.indexOf(lowPattern.charAt(patternPos), idx);
|
||||
if (wordPos >= 0) {
|
||||
score += 1;
|
||||
matches += 2 ** wordPos;
|
||||
idx = wordPos + 1;
|
||||
}
|
||||
}
|
||||
return [matches.length, matches];
|
||||
return [score, matches, _wordPos];
|
||||
}
|
||||
|
||||
//#region --- fuzzyScore ---
|
||||
|
||||
export function createMatches(offsetOrScore: number[] | FuzzyScore): IMatch[] {
|
||||
let ret: IMatch[] = [];
|
||||
if (!offsetOrScore) {
|
||||
return ret;
|
||||
export function createMatches(score: undefined | FuzzyScore): IMatch[] {
|
||||
if (typeof score === 'undefined') {
|
||||
return [];
|
||||
}
|
||||
let offsets: number[];
|
||||
if (Array.isArray(offsetOrScore[1])) {
|
||||
offsets = (offsetOrScore as FuzzyScore)[1];
|
||||
} else {
|
||||
offsets = offsetOrScore as number[];
|
||||
}
|
||||
let last: IMatch | undefined;
|
||||
for (const pos of offsets) {
|
||||
if (last && last.end === pos) {
|
||||
last.end += 1;
|
||||
} else {
|
||||
last = { start: pos, end: pos + 1 };
|
||||
ret.push(last);
|
||||
|
||||
const matches = score[1].toString(2);
|
||||
const wordStart = score[2];
|
||||
const res: IMatch[] = [];
|
||||
|
||||
for (let pos = wordStart; pos < _maxLen; pos++) {
|
||||
if (matches[matches.length - (pos + 1)] === '1') {
|
||||
const last = res[res.length - 1];
|
||||
if (last && last.end === pos) {
|
||||
last.end = pos + 1;
|
||||
} else {
|
||||
res.push({ start: pos, end: pos + 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
return res;
|
||||
}
|
||||
|
||||
const _maxLen = 53;
|
||||
|
||||
function initTable() {
|
||||
const table: number[][] = [];
|
||||
const row: number[] = [0];
|
||||
for (let i = 1; i <= 100; i++) {
|
||||
for (let i = 1; i <= _maxLen; i++) {
|
||||
row.push(-i);
|
||||
}
|
||||
for (let i = 0; i <= 100; i++) {
|
||||
for (let i = 0; i <= _maxLen; i++) {
|
||||
let thisRow = row.slice(0);
|
||||
thisRow[0] = -i;
|
||||
table.push(thisRow);
|
||||
@@ -438,6 +451,7 @@ function isSeparatorAtPos(value: string, index: number): boolean {
|
||||
case CharCode.SingleQuote:
|
||||
case CharCode.DoubleQuote:
|
||||
case CharCode.Colon:
|
||||
case CharCode.DollarSign:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -458,18 +472,49 @@ function isWhitespaceAtPos(value: string, index: number): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
function isUpperCaseAtPos(pos: number, word: string, wordLow: string): boolean {
|
||||
return word[pos] !== wordLow[pos];
|
||||
}
|
||||
|
||||
function isPatternInWord(patternLow: string, patternPos: number, patternLen: number, wordLow: string, wordPos: number, wordLen: number): boolean {
|
||||
while (patternPos < patternLen && wordPos < wordLen) {
|
||||
if (patternLow[patternPos] === wordLow[wordPos]) {
|
||||
patternPos += 1;
|
||||
}
|
||||
wordPos += 1;
|
||||
}
|
||||
return patternPos === patternLen; // pattern must be exhausted
|
||||
}
|
||||
|
||||
const enum Arrow { Top = 0b1, Diag = 0b10, Left = 0b100 }
|
||||
|
||||
export type FuzzyScore = [number, number[]];
|
||||
/**
|
||||
* A tuple of three values.
|
||||
* 0. the score
|
||||
* 1. the matches encoded as bitmask (2^53)
|
||||
* 2. the offset at which matching started
|
||||
*/
|
||||
export type FuzzyScore = [number, number, number];
|
||||
|
||||
export namespace FuzzyScore {
|
||||
/**
|
||||
* No matches and value `-100`
|
||||
*/
|
||||
export const Default: [-100, 0, 0] = [-100, 0, 0];
|
||||
|
||||
export function isDefault(score?: FuzzyScore): score is [-100, 0, 0] {
|
||||
return !score || (score[0] === -100 && score[1] === 0 && score[2] === 0);
|
||||
}
|
||||
}
|
||||
|
||||
export interface FuzzyScorer {
|
||||
(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined;
|
||||
}
|
||||
|
||||
export function fuzzyScore(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined {
|
||||
export function fuzzyScore(pattern: string, patternLow: string, patternPos: number, word: string, wordLow: string, wordPos: number, firstMatchCanBeWeak: boolean): FuzzyScore | undefined {
|
||||
|
||||
const patternLen = pattern.length > 100 ? 100 : pattern.length;
|
||||
const wordLen = word.length > 100 ? 100 : word.length;
|
||||
const patternLen = pattern.length > _maxLen ? _maxLen : pattern.length;
|
||||
const wordLen = word.length > _maxLen ? _maxLen : word.length;
|
||||
|
||||
if (patternPos >= patternLen || wordPos >= wordLen || patternLen > wordLen) {
|
||||
return undefined;
|
||||
@@ -478,20 +523,12 @@ export function fuzzyScore(pattern: string, lowPattern: string, patternPos: numb
|
||||
// Run a simple check if the characters of pattern occur
|
||||
// (in order) at all in word. If that isn't the case we
|
||||
// stop because no match will be possible
|
||||
const patternStartPos = patternPos;
|
||||
const wordStartPos = wordPos;
|
||||
while (patternPos < patternLen && wordPos < wordLen) {
|
||||
if (lowPattern[patternPos] === lowWord[wordPos]) {
|
||||
patternPos += 1;
|
||||
}
|
||||
wordPos += 1;
|
||||
}
|
||||
if (patternPos !== patternLen) {
|
||||
if (!isPatternInWord(patternLow, patternPos, patternLen, wordLow, wordPos, wordLen)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
patternPos = patternStartPos;
|
||||
wordPos = wordStartPos;
|
||||
const patternStartPos = patternPos;
|
||||
const wordStartPos = wordPos;
|
||||
|
||||
// There will be a mach, fill in tables
|
||||
for (patternPos = patternStartPos + 1; patternPos <= patternLen; patternPos++) {
|
||||
@@ -499,24 +536,27 @@ export function fuzzyScore(pattern: string, lowPattern: string, patternPos: numb
|
||||
for (wordPos = 1; wordPos <= wordLen; wordPos++) {
|
||||
|
||||
let score = -1;
|
||||
let lowWordChar = lowWord[wordPos - 1];
|
||||
if (lowPattern[patternPos - 1] === lowWordChar) {
|
||||
if (patternLow[patternPos - 1] === wordLow[wordPos - 1]) {
|
||||
|
||||
if (wordPos === (patternPos - patternStartPos)) {
|
||||
// common prefix: `foobar <-> foobaz`
|
||||
// ^^^^^
|
||||
if (pattern[patternPos - 1] === word[wordPos - 1]) {
|
||||
score = 7;
|
||||
} else {
|
||||
score = 5;
|
||||
}
|
||||
} else if (lowWordChar !== word[wordPos - 1] && (wordPos === 1 || lowWord[wordPos - 2] === word[wordPos - 2])) {
|
||||
} else if (isUpperCaseAtPos(wordPos - 1, word, wordLow) && (wordPos === 1 || !isUpperCaseAtPos(wordPos - 2, word, wordLow))) {
|
||||
// hitting upper-case: `foo <-> forOthers`
|
||||
// ^^ ^
|
||||
if (pattern[patternPos - 1] === word[wordPos - 1]) {
|
||||
score = 7;
|
||||
} else {
|
||||
score = 5;
|
||||
}
|
||||
} else if (isSeparatorAtPos(lowWord, wordPos - 2) || isWhitespaceAtPos(lowWord, wordPos - 2)) {
|
||||
} else if (isSeparatorAtPos(wordLow, wordPos - 2) || isWhitespaceAtPos(wordLow, wordPos - 2)) {
|
||||
// post separator: `foo <-> bar_foo`
|
||||
// ^^^
|
||||
score = 5;
|
||||
|
||||
} else {
|
||||
@@ -564,29 +604,26 @@ export function fuzzyScore(pattern: string, lowPattern: string, patternPos: numb
|
||||
console.log(printTable(_scores, pattern, patternLen, word, wordLen));
|
||||
}
|
||||
|
||||
// _bucket is an array of [PrefixArray] we use to keep
|
||||
// track of scores and matches. After calling `_findAllMatches`
|
||||
// the best match (if available) is the first item in the array
|
||||
_matchesCount = 0;
|
||||
_topScore = -100;
|
||||
_patternStartPos = patternStartPos;
|
||||
_firstMatchCanBeWeak = firstMatchCanBeWeak;
|
||||
_findAllMatches(patternLen, wordLen, patternLen === wordLen ? 1 : 0, new LazyArray(), false);
|
||||
|
||||
_findAllMatches2(patternLen, wordLen, patternLen === wordLen ? 1 : 0, 0, false);
|
||||
if (_matchesCount === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [_topScore, _topMatch.toArray()];
|
||||
return [_topScore, _topMatch2, wordStartPos];
|
||||
}
|
||||
|
||||
|
||||
let _matchesCount: number = 0;
|
||||
let _topMatch: LazyArray;
|
||||
let _topMatch2: number = 0;
|
||||
let _topScore: number = 0;
|
||||
let _patternStartPos: number = 0;
|
||||
let _firstMatchCanBeWeak: boolean = false;
|
||||
|
||||
function _findAllMatches(patternPos: number, wordPos: number, total: number, matches: LazyArray, lastMatched: boolean): void {
|
||||
function _findAllMatches2(patternPos: number, wordPos: number, total: number, matches: number, lastMatched: boolean): void {
|
||||
|
||||
if (_matchesCount >= 10 || total < -25) {
|
||||
// stop when having already 10 results, or
|
||||
@@ -602,11 +639,11 @@ function _findAllMatches(patternPos: number, wordPos: number, total: number, mat
|
||||
let arrow = _arrows[patternPos][wordPos];
|
||||
|
||||
if (arrow === Arrow.Left) {
|
||||
// left
|
||||
// left -> no match, skip a word character
|
||||
wordPos -= 1;
|
||||
if (lastMatched) {
|
||||
total -= 5; // new gap penalty
|
||||
} else if (!matches.isEmpty()) {
|
||||
} else if (matches !== 0) {
|
||||
total -= 1; // gap penalty after first match
|
||||
}
|
||||
lastMatched = false;
|
||||
@@ -616,11 +653,11 @@ function _findAllMatches(patternPos: number, wordPos: number, total: number, mat
|
||||
|
||||
if (arrow & Arrow.Left) {
|
||||
// left
|
||||
_findAllMatches(
|
||||
_findAllMatches2(
|
||||
patternPos,
|
||||
wordPos - 1,
|
||||
!matches.isEmpty() ? total - 1 : total, // gap penalty after first match
|
||||
matches.slice(),
|
||||
matches !== 0 ? total - 1 : total, // gap penalty after first match
|
||||
matches,
|
||||
lastMatched
|
||||
);
|
||||
}
|
||||
@@ -629,9 +666,11 @@ function _findAllMatches(patternPos: number, wordPos: number, total: number, mat
|
||||
total += score;
|
||||
patternPos -= 1;
|
||||
wordPos -= 1;
|
||||
matches.unshift(wordPos);
|
||||
lastMatched = true;
|
||||
|
||||
// match -> set a 1 at the word pos
|
||||
matches += 2 ** wordPos;
|
||||
|
||||
// count simple matches and boost a row of
|
||||
// simple matches when they yield in a
|
||||
// strong match.
|
||||
@@ -662,47 +701,7 @@ function _findAllMatches(patternPos: number, wordPos: number, total: number, mat
|
||||
_matchesCount += 1;
|
||||
if (total > _topScore) {
|
||||
_topScore = total;
|
||||
_topMatch = matches;
|
||||
}
|
||||
}
|
||||
|
||||
class LazyArray {
|
||||
|
||||
private _parent: LazyArray;
|
||||
private _parentLen: number;
|
||||
private _data: number[];
|
||||
|
||||
isEmpty(): boolean {
|
||||
return !this._data && (!this._parent || this._parent.isEmpty());
|
||||
}
|
||||
|
||||
unshift(n: number) {
|
||||
if (!this._data) {
|
||||
this._data = [n];
|
||||
} else {
|
||||
this._data.unshift(n);
|
||||
}
|
||||
}
|
||||
|
||||
slice(): LazyArray {
|
||||
const ret = new LazyArray();
|
||||
ret._parent = this;
|
||||
ret._parentLen = this._data ? this._data.length : 0; return ret;
|
||||
}
|
||||
|
||||
toArray(): number[] {
|
||||
if (!this._data) {
|
||||
return this._parent.toArray();
|
||||
}
|
||||
const bucket: number[][] = [];
|
||||
let element = <LazyArray>this;
|
||||
while (element) {
|
||||
if (element._parent && element._parent._data) {
|
||||
bucket.push(element._parent._data.slice(element._parent._data.length - element._parentLen));
|
||||
}
|
||||
element = element._parent;
|
||||
}
|
||||
return Array.prototype.concat.apply(this._data, bucket);
|
||||
_topMatch2 = matches;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,11 +58,8 @@ export function splitGlobAware(pattern: string, splitChar: string): string[] {
|
||||
let inBraces = false;
|
||||
let inBrackets = false;
|
||||
|
||||
let char: string;
|
||||
let curVal = '';
|
||||
for (let i = 0; i < pattern.length; i++) {
|
||||
char = pattern[i];
|
||||
|
||||
for (const char of pattern) {
|
||||
switch (char) {
|
||||
case splitChar:
|
||||
if (!inBraces && !inBrackets) {
|
||||
@@ -136,10 +133,7 @@ function parseRegExp(pattern: string): string {
|
||||
let inBrackets = false;
|
||||
let bracketVal = '';
|
||||
|
||||
let char: string;
|
||||
for (let i = 0; i < segment.length; i++) {
|
||||
char = segment[i];
|
||||
|
||||
for (const char of segment) {
|
||||
// Support brace expansion
|
||||
if (char !== '}' && inBraces) {
|
||||
braceVal += char;
|
||||
@@ -658,7 +652,7 @@ function parseExpressionPattern(pattern: string, value: any, options: IGlobOptio
|
||||
return parsedPattern;
|
||||
}
|
||||
|
||||
function aggregateBasenameMatches(parsedPatterns: (ParsedStringPattern | ParsedExpressionPattern)[], result?: string): (ParsedStringPattern | ParsedExpressionPattern)[] {
|
||||
function aggregateBasenameMatches(parsedPatterns: Array<ParsedStringPattern | ParsedExpressionPattern>, result?: string): Array<ParsedStringPattern | ParsedExpressionPattern> {
|
||||
const basenamePatterns = parsedPatterns.filter(parsedPattern => !!(<ParsedStringPattern>parsedPattern).basenames);
|
||||
if (basenamePatterns.length < 2) {
|
||||
return parsedPatterns;
|
||||
|
||||
@@ -16,7 +16,6 @@ export class MarkdownString implements IMarkdownString {
|
||||
|
||||
value: string;
|
||||
isTrusted?: boolean;
|
||||
sanitize: boolean = true;
|
||||
|
||||
constructor(value: string = '') {
|
||||
this.value = value;
|
||||
@@ -58,7 +57,7 @@ export function isMarkdownString(thing: any): thing is IMarkdownString {
|
||||
return true;
|
||||
} else if (thing && typeof thing === 'object') {
|
||||
return typeof (<IMarkdownString>thing).value === 'string'
|
||||
&& (typeof (<IMarkdownString>thing).isTrusted === 'boolean' || (<IMarkdownString>thing).isTrusted === void 0);
|
||||
&& (typeof (<IMarkdownString>thing).isTrusted === 'boolean' || (<IMarkdownString>thing).isTrusted === undefined);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -205,10 +205,10 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON
|
||||
token: SyntaxKind = SyntaxKind.Unknown,
|
||||
scanError: ScanError = ScanError.None;
|
||||
|
||||
function scanHexDigits(count: number, exact?: boolean): number {
|
||||
function scanHexDigits(count: number): number {
|
||||
let digits = 0;
|
||||
let value = 0;
|
||||
while (digits < count || !exact) {
|
||||
while (digits < count) {
|
||||
let ch = text.charCodeAt(pos);
|
||||
if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) {
|
||||
value = value * 16 + ch - CharacterCodes._0;
|
||||
@@ -331,7 +331,7 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON
|
||||
result += '\t';
|
||||
break;
|
||||
case CharacterCodes.u:
|
||||
let ch = scanHexDigits(4, true);
|
||||
let ch = scanHexDigits(4);
|
||||
if (ch >= 0) {
|
||||
result += String.fromCharCode(ch);
|
||||
} else {
|
||||
@@ -344,7 +344,7 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON
|
||||
start = pos;
|
||||
continue;
|
||||
}
|
||||
if (ch >= 0 && ch <= 0x1f) {
|
||||
if (ch >= 0 && ch <= 0x1F) {
|
||||
if (isLineBreak(ch)) {
|
||||
result += text.substring(start, pos);
|
||||
scanError = ScanError.UnexpectedEndOfString;
|
||||
@@ -668,7 +668,7 @@ const enum CharacterCodes {
|
||||
W = 0x57,
|
||||
X = 0x58,
|
||||
Y = 0x59,
|
||||
Z = 0x5a,
|
||||
Z = 0x5A,
|
||||
|
||||
ampersand = 0x26, // &
|
||||
asterisk = 0x2A, // *
|
||||
@@ -722,13 +722,13 @@ interface NodeImpl extends Node {
|
||||
export function getLocation(text: string, position: number): Location {
|
||||
let segments: Segment[] = []; // strings or numbers
|
||||
let earlyReturnException = new Object();
|
||||
let previousNode: NodeImpl | undefined = void 0;
|
||||
let previousNode: NodeImpl | undefined = undefined;
|
||||
const previousNodeInst: NodeImpl = {
|
||||
value: {},
|
||||
offset: 0,
|
||||
length: 0,
|
||||
type: 'object',
|
||||
parent: void 0
|
||||
parent: undefined
|
||||
};
|
||||
let isAtPropertyKey = false;
|
||||
function setPreviousNode(value: string, offset: number, length: number, type: NodeType) {
|
||||
@@ -736,7 +736,7 @@ export function getLocation(text: string, position: number): Location {
|
||||
previousNodeInst.offset = offset;
|
||||
previousNodeInst.length = length;
|
||||
previousNodeInst.type = type;
|
||||
previousNodeInst.colonOffset = void 0;
|
||||
previousNodeInst.colonOffset = undefined;
|
||||
previousNode = previousNodeInst;
|
||||
}
|
||||
try {
|
||||
@@ -746,7 +746,7 @@ export function getLocation(text: string, position: number): Location {
|
||||
if (position <= offset) {
|
||||
throw earlyReturnException;
|
||||
}
|
||||
previousNode = void 0;
|
||||
previousNode = undefined;
|
||||
isAtPropertyKey = position > offset;
|
||||
segments.push(''); // push a placeholder (will be replaced)
|
||||
},
|
||||
@@ -764,21 +764,21 @@ export function getLocation(text: string, position: number): Location {
|
||||
if (position <= offset) {
|
||||
throw earlyReturnException;
|
||||
}
|
||||
previousNode = void 0;
|
||||
previousNode = undefined;
|
||||
segments.pop();
|
||||
},
|
||||
onArrayBegin: (offset: number, length: number) => {
|
||||
if (position <= offset) {
|
||||
throw earlyReturnException;
|
||||
}
|
||||
previousNode = void 0;
|
||||
previousNode = undefined;
|
||||
segments.push(0);
|
||||
},
|
||||
onArrayEnd: (offset: number, length: number) => {
|
||||
if (position <= offset) {
|
||||
throw earlyReturnException;
|
||||
}
|
||||
previousNode = void 0;
|
||||
previousNode = undefined;
|
||||
segments.pop();
|
||||
},
|
||||
onLiteralValue: (value: any, offset: number, length: number) => {
|
||||
@@ -798,7 +798,7 @@ export function getLocation(text: string, position: number): Location {
|
||||
if (sep === ':' && previousNode && previousNode.type === 'property') {
|
||||
previousNode.colonOffset = offset;
|
||||
isAtPropertyKey = false;
|
||||
previousNode = void 0;
|
||||
previousNode = undefined;
|
||||
} else if (sep === ',') {
|
||||
let last = segments[segments.length - 1];
|
||||
if (typeof last === 'number') {
|
||||
@@ -807,7 +807,7 @@ export function getLocation(text: string, position: number): Location {
|
||||
isAtPropertyKey = true;
|
||||
segments[segments.length - 1] = '';
|
||||
}
|
||||
previousNode = void 0;
|
||||
previousNode = undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -891,7 +891,7 @@ export function parse(text: string, errors: ParseError[] = [], options: ParseOpt
|
||||
* Parses the given text and returns a tree representation the JSON content. On invalid input, the parser tries to be as fault tolerant as possible, but still return a result.
|
||||
*/
|
||||
export function parseTree(text: string, errors: ParseError[] = [], options: ParseOptions = ParseOptions.DEFAULT): Node {
|
||||
let currentParent: NodeImpl = { type: 'array', offset: -1, length: -1, children: [], parent: void 0 }; // artificial root
|
||||
let currentParent: NodeImpl = { type: 'array', offset: -1, length: -1, children: [], parent: undefined }; // artificial root
|
||||
|
||||
function ensurePropertyComplete(endOffset: number) {
|
||||
if (currentParent.type === 'property') {
|
||||
@@ -957,13 +957,13 @@ export function parseTree(text: string, errors: ParseError[] = [], options: Pars
|
||||
*/
|
||||
export function findNodeAtLocation(root: Node, path: JSONPath): Node | undefined {
|
||||
if (!root) {
|
||||
return void 0;
|
||||
return undefined;
|
||||
}
|
||||
let node = root;
|
||||
for (let segment of path) {
|
||||
if (typeof segment === 'string') {
|
||||
if (node.type !== 'object' || !Array.isArray(node.children)) {
|
||||
return void 0;
|
||||
return undefined;
|
||||
}
|
||||
let found = false;
|
||||
for (const propertyNode of node.children) {
|
||||
@@ -974,12 +974,12 @@ export function findNodeAtLocation(root: Node, path: JSONPath): Node | undefined
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return void 0;
|
||||
return undefined;
|
||||
}
|
||||
} else {
|
||||
let index = <number>segment;
|
||||
if (node.type !== 'array' || index < 0 || !Array.isArray(node.children) || index >= node.children.length) {
|
||||
return void 0;
|
||||
return undefined;
|
||||
}
|
||||
node = node.children[index];
|
||||
}
|
||||
@@ -1029,7 +1029,7 @@ export function getNodeValue(node: Node): any {
|
||||
case 'boolean':
|
||||
return node.value;
|
||||
default:
|
||||
return void 0;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1055,7 +1055,7 @@ export function findNodeAtOffset(node: Node, offset: number, includeRightBound =
|
||||
}
|
||||
return node;
|
||||
}
|
||||
return void 0;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -1322,7 +1322,7 @@ export function stripComments(text: string, replaceCh?: string): string {
|
||||
if (offset !== pos) {
|
||||
parts.push(text.substring(offset, pos));
|
||||
}
|
||||
if (replaceCh !== void 0) {
|
||||
if (replaceCh !== undefined) {
|
||||
parts.push(_scanner.getTokenValue().replace(/[^\r\n]/g, replaceCh));
|
||||
}
|
||||
offset = _scanner.getPosition();
|
||||
|
||||
@@ -8,20 +8,20 @@ import { Edit, format, isEOL, FormattingOptions } from './jsonFormatter';
|
||||
|
||||
|
||||
export function removeProperty(text: string, path: JSONPath, formattingOptions: FormattingOptions): Edit[] {
|
||||
return setProperty(text, path, void 0, formattingOptions);
|
||||
return setProperty(text, path, undefined, formattingOptions);
|
||||
}
|
||||
|
||||
export function setProperty(text: string, originalPath: JSONPath, value: any, formattingOptions: FormattingOptions, getInsertionIndex?: (properties: string[]) => number): Edit[] {
|
||||
let path = originalPath.slice();
|
||||
let errors: ParseError[] = [];
|
||||
let root = parseTree(text, errors);
|
||||
let parent: Node | undefined = void 0;
|
||||
let parent: Node | undefined = undefined;
|
||||
|
||||
let lastSegment: Segment | undefined = void 0;
|
||||
let lastSegment: Segment | undefined = undefined;
|
||||
while (path.length > 0) {
|
||||
lastSegment = path.pop();
|
||||
parent = findNodeAtLocation(root, path);
|
||||
if (parent === void 0 && value !== void 0) {
|
||||
if (parent === undefined && value !== undefined) {
|
||||
if (typeof lastSegment === 'string') {
|
||||
value = { [lastSegment]: value };
|
||||
} else {
|
||||
@@ -34,14 +34,14 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo
|
||||
|
||||
if (!parent) {
|
||||
// empty document
|
||||
if (value === void 0) { // delete
|
||||
if (value === undefined) { // delete
|
||||
throw new Error('Can not delete in empty document');
|
||||
}
|
||||
return withFormatting(text, { offset: root ? root.offset : 0, length: root ? root.length : 0, content: JSON.stringify(value) }, formattingOptions);
|
||||
} else if (parent.type === 'object' && typeof lastSegment === 'string' && Array.isArray(parent.children)) {
|
||||
let existing = findNodeAtLocation(parent, [lastSegment]);
|
||||
if (existing !== void 0) {
|
||||
if (value === void 0) { // delete
|
||||
if (existing !== undefined) {
|
||||
if (value === undefined) { // delete
|
||||
if (!existing.parent) {
|
||||
throw new Error('Malformed AST');
|
||||
}
|
||||
@@ -66,7 +66,7 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo
|
||||
return withFormatting(text, { offset: existing.offset, length: existing.length, content: JSON.stringify(value) }, formattingOptions);
|
||||
}
|
||||
} else {
|
||||
if (value === void 0) { // delete
|
||||
if (value === undefined) { // delete
|
||||
return []; // property does not exist, nothing to do
|
||||
}
|
||||
let newProperty = `${JSON.stringify(lastSegment)}: ${JSON.stringify(value)}`;
|
||||
@@ -96,7 +96,7 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo
|
||||
}
|
||||
return withFormatting(text, edit, formattingOptions);
|
||||
} else {
|
||||
if (value === void 0 && parent.children.length >= 0) {
|
||||
if (value === undefined && parent.children.length >= 0) {
|
||||
//Removal
|
||||
let removalIndex = lastSegment;
|
||||
let toRemove = parent.children[removalIndex];
|
||||
|
||||
@@ -395,7 +395,7 @@ const enum BinaryKeybindingsMask {
|
||||
Shift = (1 << 10) >>> 0,
|
||||
Alt = (1 << 9) >>> 0,
|
||||
WinCtrl = (1 << 8) >>> 0,
|
||||
KeyCode = 0x000000ff
|
||||
KeyCode = 0x000000FF
|
||||
}
|
||||
|
||||
export const enum KeyMod {
|
||||
@@ -406,7 +406,7 @@ export const enum KeyMod {
|
||||
}
|
||||
|
||||
export function KeyChord(firstPart: number, secondPart: number): number {
|
||||
let chordPart = ((secondPart & 0x0000ffff) << 16) >>> 0;
|
||||
let chordPart = ((secondPart & 0x0000FFFF) << 16) >>> 0;
|
||||
return (firstPart | chordPart) >>> 0;
|
||||
}
|
||||
|
||||
@@ -414,8 +414,8 @@ export function createKeybinding(keybinding: number, OS: OperatingSystem): Keybi
|
||||
if (keybinding === 0) {
|
||||
return null;
|
||||
}
|
||||
const firstPart = (keybinding & 0x0000ffff) >>> 0;
|
||||
const chordPart = (keybinding & 0xffff0000) >>> 16;
|
||||
const firstPart = (keybinding & 0x0000FFFF) >>> 0;
|
||||
const chordPart = (keybinding & 0xFFFF0000) >>> 16;
|
||||
if (chordPart !== 0) {
|
||||
return new ChordKeybinding(
|
||||
createSimpleKeybinding(firstPart, OS),
|
||||
|
||||
@@ -70,7 +70,9 @@ export function getPathLabel(resource: URI | string, userHomeProvider?: IUserHom
|
||||
return res;
|
||||
}
|
||||
|
||||
export function getBaseLabel(resource: URI | string): string | undefined {
|
||||
export function getBaseLabel(resource: URI | string): string;
|
||||
export function getBaseLabel(resource: URI | string | undefined): string | undefined;
|
||||
export function getBaseLabel(resource: URI | string | undefined): string | undefined {
|
||||
if (!resource) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -108,7 +110,7 @@ export function tildify(path: string, userHome: string): string {
|
||||
}
|
||||
|
||||
// Keep a normalized user home path as cache to prevent accumulated string creation
|
||||
let normalizedUserHome = normalizedUserHomeCached.original === userHome ? normalizedUserHomeCached.normalized : void 0;
|
||||
let normalizedUserHome = normalizedUserHomeCached.original === userHome ? normalizedUserHomeCached.normalized : undefined;
|
||||
if (!normalizedUserHome) {
|
||||
normalizedUserHome = `${rtrim(userHome, sep)}${sep}`;
|
||||
normalizedUserHomeCached = { original: userHome, normalized: normalizedUserHome };
|
||||
@@ -284,11 +286,8 @@ export function template(template: string, values: { [key: string]: string | ISe
|
||||
const segments: ISegment[] = [];
|
||||
|
||||
let inVariable = false;
|
||||
let char: string;
|
||||
let curVal = '';
|
||||
for (let i = 0; i < template.length; i++) {
|
||||
char = template[i];
|
||||
|
||||
for (const char of template) {
|
||||
// Beginning of variable
|
||||
if (char === '$' || (inVariable && char === '{')) {
|
||||
if (curVal) {
|
||||
@@ -364,7 +363,7 @@ export function mnemonicMenuLabel(label: string, forceDisableMnemonics?: boolean
|
||||
|
||||
/**
|
||||
* Handles mnemonics for buttons. Depending on OS:
|
||||
* - Windows: Supported via & character (replace && with &)
|
||||
* - Windows: Supported via & character (replace && with & and & with && for escaping)
|
||||
* - Linux: Supported via _ character (replace && with _)
|
||||
* - macOS: Unsupported (replace && with empty string)
|
||||
*/
|
||||
@@ -373,7 +372,11 @@ export function mnemonicButtonLabel(label: string): string {
|
||||
return label.replace(/\(&&\w\)|&&/g, '');
|
||||
}
|
||||
|
||||
return label.replace(/&&/g, isWindows ? '&' : '_');
|
||||
if (isWindows) {
|
||||
return label.replace(/&&|&/g, m => m === '&' ? '&&' : '&');
|
||||
}
|
||||
|
||||
return label.replace(/&&/g, '_');
|
||||
}
|
||||
|
||||
export function unmnemonicLabel(label: string): string {
|
||||
|
||||
@@ -15,7 +15,7 @@ export function isDisposable<E extends object>(thing: E): thing is E & IDisposab
|
||||
}
|
||||
|
||||
export function dispose<T extends IDisposable>(disposable: T): T;
|
||||
export function dispose<T extends IDisposable>(...disposables: (T | undefined)[]): T[];
|
||||
export function dispose<T extends IDisposable>(...disposables: Array<T | undefined>): T[];
|
||||
export function dispose<T extends IDisposable>(disposables: T[]): T[];
|
||||
export function dispose<T extends IDisposable>(first: T | T[], ...rest: T[]): T | T[] | undefined {
|
||||
if (Array.isArray(first)) {
|
||||
@@ -49,12 +49,20 @@ export abstract class Disposable implements IDisposable {
|
||||
protected _toDispose: IDisposable[] = [];
|
||||
protected get toDispose(): IDisposable[] { return this._toDispose; }
|
||||
|
||||
private _lifecycle_disposable_isDisposed = false;
|
||||
|
||||
public dispose(): void {
|
||||
this._lifecycle_disposable_isDisposed = true;
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
protected _register<T extends IDisposable>(t: T): T {
|
||||
this._toDispose.push(t);
|
||||
if (this._lifecycle_disposable_isDisposed) {
|
||||
console.warn('Registering disposable on object that has already been disposed.');
|
||||
t.dispose();
|
||||
} else {
|
||||
this._toDispose.push(t);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
@@ -32,17 +32,18 @@ export class LinkedList<E> {
|
||||
clear(): void {
|
||||
this._first = undefined;
|
||||
this._last = undefined;
|
||||
this._size = 0;
|
||||
}
|
||||
|
||||
unshift(element: E) {
|
||||
return this.insert(element, false);
|
||||
unshift(element: E): () => void {
|
||||
return this._insert(element, false);
|
||||
}
|
||||
|
||||
push(element: E) {
|
||||
return this.insert(element, true);
|
||||
push(element: E): () => void {
|
||||
return this._insert(element, true);
|
||||
}
|
||||
|
||||
private insert(element: E, atTheEnd: boolean) {
|
||||
private _insert(element: E, atTheEnd: boolean): () => void {
|
||||
const newNode = new Node(element);
|
||||
if (!this._first) {
|
||||
this._first = newNode;
|
||||
@@ -63,41 +64,63 @@ export class LinkedList<E> {
|
||||
oldFirst.prev = newNode;
|
||||
}
|
||||
this._size += 1;
|
||||
return this._remove.bind(this, newNode);
|
||||
}
|
||||
|
||||
return () => {
|
||||
let candidate: Node<E> | undefined = this._first;
|
||||
while (candidate instanceof Node) {
|
||||
if (candidate !== newNode) {
|
||||
candidate = candidate.next;
|
||||
continue;
|
||||
}
|
||||
if (candidate.prev && candidate.next) {
|
||||
// middle
|
||||
let anchor = candidate.prev;
|
||||
anchor.next = candidate.next;
|
||||
candidate.next.prev = anchor;
|
||||
|
||||
} else if (!candidate.prev && !candidate.next) {
|
||||
// only node
|
||||
this._first = undefined;
|
||||
this._last = undefined;
|
||||
shift(): E | undefined {
|
||||
if (!this._first) {
|
||||
return undefined;
|
||||
} else {
|
||||
const res = this._first.element;
|
||||
this._remove(this._first);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!candidate.next) {
|
||||
// last
|
||||
this._last = this._last!.prev!;
|
||||
this._last.next = undefined;
|
||||
pop(): E | undefined {
|
||||
if (!this._last) {
|
||||
return undefined;
|
||||
} else {
|
||||
const res = this._last.element;
|
||||
this._remove(this._last);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!candidate.prev) {
|
||||
// first
|
||||
this._first = this._first!.next!;
|
||||
this._first.prev = undefined;
|
||||
}
|
||||
|
||||
// done
|
||||
this._size -= 1;
|
||||
break;
|
||||
private _remove(node: Node<E>): void {
|
||||
let candidate: Node<E> | undefined = this._first;
|
||||
while (candidate instanceof Node) {
|
||||
if (candidate !== node) {
|
||||
candidate = candidate.next;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if (candidate.prev && candidate.next) {
|
||||
// middle
|
||||
let anchor = candidate.prev;
|
||||
anchor.next = candidate.next;
|
||||
candidate.next.prev = anchor;
|
||||
|
||||
} else if (!candidate.prev && !candidate.next) {
|
||||
// only node
|
||||
this._first = undefined;
|
||||
this._last = undefined;
|
||||
|
||||
} else if (!candidate.next) {
|
||||
// last
|
||||
this._last = this._last!.prev!;
|
||||
this._last.next = undefined;
|
||||
|
||||
} else if (!candidate.prev) {
|
||||
// first
|
||||
this._first = this._first!.next!;
|
||||
this._first.prev = undefined;
|
||||
}
|
||||
|
||||
// done
|
||||
this._size -= 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
iterator(): Iterator<E> {
|
||||
|
||||
@@ -24,7 +24,7 @@ export function keys<K, V>(map: Map<K, V>): K[] {
|
||||
|
||||
export function getOrSet<K, V>(map: Map<K, V>, key: K, value: V): V {
|
||||
let result = map.get(key);
|
||||
if (result === void 0) {
|
||||
if (result === undefined) {
|
||||
result = value;
|
||||
map.set(key, result);
|
||||
}
|
||||
@@ -431,7 +431,7 @@ export class ResourceMap<T> {
|
||||
this.map.set(this.toKey(resource), value);
|
||||
}
|
||||
|
||||
get(resource: URI): T {
|
||||
get(resource: URI): T | undefined {
|
||||
return this.map.get(this.toKey(resource));
|
||||
}
|
||||
|
||||
@@ -687,7 +687,7 @@ export class LinkedMap<K, V> {
|
||||
this._head = current;
|
||||
this._size = currentSize;
|
||||
if (current) {
|
||||
current.previous = void 0;
|
||||
current.previous = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -719,8 +719,8 @@ export class LinkedMap<K, V> {
|
||||
|
||||
private removeItem(item: Item<K, V>): void {
|
||||
if (item === this._head && item === this._tail) {
|
||||
this._head = void 0;
|
||||
this._tail = void 0;
|
||||
this._head = undefined;
|
||||
this._tail = undefined;
|
||||
}
|
||||
else if (item === this._head) {
|
||||
this._head = item.next;
|
||||
@@ -759,7 +759,7 @@ export class LinkedMap<K, V> {
|
||||
if (item === this._tail) {
|
||||
// previous must be defined since item was not head but is tail
|
||||
// So there are more than on item in the map
|
||||
previous!.next = void 0;
|
||||
previous!.next = undefined;
|
||||
this._tail = previous;
|
||||
}
|
||||
else {
|
||||
@@ -769,7 +769,7 @@ export class LinkedMap<K, V> {
|
||||
}
|
||||
|
||||
// Insert the node at head
|
||||
item.previous = void 0;
|
||||
item.previous = undefined;
|
||||
item.next = this._head;
|
||||
this._head.previous = item;
|
||||
this._head = item;
|
||||
@@ -785,14 +785,14 @@ export class LinkedMap<K, V> {
|
||||
if (item === this._head) {
|
||||
// next must be defined since item was not tail but is head
|
||||
// So there are more than on item in the map
|
||||
next!.previous = void 0;
|
||||
next!.previous = undefined;
|
||||
this._head = next;
|
||||
} else {
|
||||
// Both next and previous are not undefined since item was neither head nor tail.
|
||||
next!.previous = previous;
|
||||
previous!.next = next;
|
||||
}
|
||||
item.next = void 0;
|
||||
item.next = undefined;
|
||||
item.previous = this._tail;
|
||||
this._tail.next = item;
|
||||
this._tail = item;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { regExpFlags } from 'vs/base/common/strings';
|
||||
|
||||
export function stringify(obj: any): string {
|
||||
return JSON.stringify(obj, replacer);
|
||||
@@ -24,8 +25,8 @@ function replacer(key: string, value: any): any {
|
||||
if (value instanceof RegExp) {
|
||||
return {
|
||||
$mid: 2,
|
||||
source: (<RegExp>value).source,
|
||||
flags: ((<RegExp>value).global ? 'g' : '') + ((<RegExp>value).ignoreCase ? 'i' : '') + ((<RegExp>value).multiline ? 'm' : ''),
|
||||
source: value.source,
|
||||
flags: regExpFlags(value),
|
||||
};
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -13,20 +13,20 @@ export const MIME_BINARY = 'application/octet-stream';
|
||||
export const MIME_UNKNOWN = 'application/unknown';
|
||||
|
||||
export interface ITextMimeAssociation {
|
||||
id: string;
|
||||
mime: string;
|
||||
filename?: string;
|
||||
extension?: string;
|
||||
filepattern?: string;
|
||||
firstline?: RegExp;
|
||||
userConfigured?: boolean;
|
||||
readonly id: string;
|
||||
readonly mime: string;
|
||||
readonly filename?: string;
|
||||
readonly extension?: string;
|
||||
readonly filepattern?: string;
|
||||
readonly firstline?: RegExp;
|
||||
readonly userConfigured?: boolean;
|
||||
}
|
||||
|
||||
interface ITextMimeAssociationItem extends ITextMimeAssociation {
|
||||
filenameLowercase?: string;
|
||||
extensionLowercase?: string;
|
||||
filepatternLowercase?: string;
|
||||
filepatternOnPath?: boolean;
|
||||
readonly filenameLowercase?: string;
|
||||
readonly extensionLowercase?: string;
|
||||
readonly filepatternLowercase?: string;
|
||||
readonly filepatternOnPath?: boolean;
|
||||
}
|
||||
|
||||
let registeredAssociations: ITextMimeAssociationItem[] = [];
|
||||
@@ -82,9 +82,9 @@ function toTextMimeAssociationItem(association: ITextMimeAssociation): ITextMime
|
||||
filepattern: association.filepattern,
|
||||
firstline: association.firstline,
|
||||
userConfigured: association.userConfigured,
|
||||
filenameLowercase: association.filename ? association.filename.toLowerCase() : void 0,
|
||||
extensionLowercase: association.extension ? association.extension.toLowerCase() : void 0,
|
||||
filepatternLowercase: association.filepattern ? association.filepattern.toLowerCase() : void 0,
|
||||
filenameLowercase: association.filename ? association.filename.toLowerCase() : undefined,
|
||||
extensionLowercase: association.extension ? association.extension.toLowerCase() : undefined,
|
||||
filepatternLowercase: association.filepattern ? association.filepattern.toLowerCase() : undefined,
|
||||
filepatternOnPath: association.filepattern ? association.filepattern.indexOf(paths.sep) >= 0 : false
|
||||
};
|
||||
}
|
||||
@@ -106,7 +106,7 @@ export function clearTextMimes(onlyUserConfigured?: boolean): void {
|
||||
/**
|
||||
* Given a file, return the best matching mime type for it
|
||||
*/
|
||||
export function guessMimeTypes(path: string, firstLine?: string, skipUserAssociations: boolean = false): string[] {
|
||||
export function guessMimeTypes(path: string | null, firstLine?: string): string[] {
|
||||
if (!path) {
|
||||
return [MIME_UNKNOWN];
|
||||
}
|
||||
@@ -114,12 +114,10 @@ export function guessMimeTypes(path: string, firstLine?: string, skipUserAssocia
|
||||
path = path.toLowerCase();
|
||||
const filename = paths.basename(path);
|
||||
|
||||
if (!skipUserAssociations) {
|
||||
// 1.) User configured mappings have highest priority
|
||||
const configuredMime = guessMimeTypeByPath(path, filename, userRegisteredAssociations);
|
||||
if (configuredMime) {
|
||||
return [configuredMime, MIME_TEXT];
|
||||
}
|
||||
// 1.) User configured mappings have highest priority
|
||||
const configuredMime = guessMimeTypeByPath(path, filename, userRegisteredAssociations);
|
||||
if (configuredMime) {
|
||||
return [configuredMime, MIME_TEXT];
|
||||
}
|
||||
|
||||
// 2.) Registered mappings have middle priority
|
||||
@@ -199,8 +197,7 @@ function guessMimeTypeByFirstline(firstLine: string): string | null {
|
||||
}
|
||||
|
||||
if (firstLine.length > 0) {
|
||||
for (let i = 0; i < registeredAssociations.length; ++i) {
|
||||
const association = registeredAssociations[i];
|
||||
for (const association of registeredAssociations) {
|
||||
if (!association.firstline) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -42,4 +42,6 @@ export namespace Schemas {
|
||||
export const untitled: string = 'untitled';
|
||||
|
||||
export const data: string = 'data';
|
||||
|
||||
export const command: string = 'command';
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ function _cloneAndChange(obj: any, changer: (orig: any) => any, seen: Set<any>):
|
||||
|
||||
if (isArray(obj)) {
|
||||
const r1: any[] = [];
|
||||
for (let i1 = 0; i1 < obj.length; i1++) {
|
||||
r1.push(_cloneAndChange(obj[i1], changer, seen));
|
||||
for (const e of obj) {
|
||||
r1.push(_cloneAndChange(e, changer, seen));
|
||||
}
|
||||
return r1;
|
||||
}
|
||||
@@ -177,8 +177,8 @@ export function equals(one: any, other: any): boolean {
|
||||
|
||||
function arrayToHash(array: string[]): { [name: string]: true } {
|
||||
const result: any = {};
|
||||
for (let i = 0; i < array.length; ++i) {
|
||||
result[array[i]] = true;
|
||||
for (const e of array) {
|
||||
result[e] = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ function doParseOcticons(text: string, firstOcticonIndex: number): IParsedOctico
|
||||
if (chars) {
|
||||
textWithoutOcticons += chars;
|
||||
|
||||
for (let i = 0; i < chars.length; i++) {
|
||||
for (const _ of chars) {
|
||||
octiconOffsets.push(octiconsOffset); // make sure to fill in octicon offsets
|
||||
}
|
||||
}
|
||||
@@ -115,10 +115,10 @@ export function matchesFuzzyOcticonAware(query: string, target: IParsedOcticons,
|
||||
|
||||
// Map matches back to offsets with octicons and trimming
|
||||
if (matches) {
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
const octiconOffset = octiconOffsets[matches[i].start + leadingWhitespaceOffset] /* octicon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */;
|
||||
matches[i].start += octiconOffset;
|
||||
matches[i].end += octiconOffset;
|
||||
for (const match of matches) {
|
||||
const octiconOffset = octiconOffsets[match.start + leadingWhitespaceOffset] /* octicon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */;
|
||||
match.start += octiconOffset;
|
||||
match.end += octiconOffset;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,12 +15,12 @@ export interface IPager<T> {
|
||||
firstPage: T[];
|
||||
total: number;
|
||||
pageSize: number;
|
||||
getPage(pageIndex: number, cancellationToken: CancellationToken): Thenable<T[]>;
|
||||
getPage(pageIndex: number, cancellationToken: CancellationToken): Promise<T[]>;
|
||||
}
|
||||
|
||||
interface IPage<T> {
|
||||
isResolved: boolean;
|
||||
promise: Thenable<void> | null;
|
||||
promise: Promise<void> | null;
|
||||
cts: CancellationTokenSource | null;
|
||||
promiseIndexes: Set<number>;
|
||||
elements: T[];
|
||||
@@ -43,7 +43,7 @@ export interface IPagedModel<T> {
|
||||
length: number;
|
||||
isResolved(index: number): boolean;
|
||||
get(index: number): T;
|
||||
resolve(index: number, cancellationToken: CancellationToken): Thenable<T>;
|
||||
resolve(index: number, cancellationToken: CancellationToken): Promise<T>;
|
||||
}
|
||||
|
||||
export function singlePagePager<T>(elements: T[]): IPager<T> {
|
||||
@@ -51,7 +51,7 @@ export function singlePagePager<T>(elements: T[]): IPager<T> {
|
||||
firstPage: elements,
|
||||
total: elements.length,
|
||||
pageSize: elements.length,
|
||||
getPage: (pageIndex: number, cancellationToken: CancellationToken): Thenable<T[]> => {
|
||||
getPage: (pageIndex: number, cancellationToken: CancellationToken): Promise<T[]> => {
|
||||
return Promise.resolve(elements);
|
||||
}
|
||||
};
|
||||
@@ -90,7 +90,7 @@ export class PagedModel<T> implements IPagedModel<T> {
|
||||
return page.elements[indexInPage];
|
||||
}
|
||||
|
||||
resolve(index: number, cancellationToken: CancellationToken): Thenable<T> {
|
||||
resolve(index: number, cancellationToken: CancellationToken): Promise<T> {
|
||||
if (cancellationToken.isCancellationRequested) {
|
||||
return Promise.reject(canceled());
|
||||
}
|
||||
@@ -151,7 +151,7 @@ export class DelayedPagedModel<T> implements IPagedModel<T> {
|
||||
return this.model.get(index);
|
||||
}
|
||||
|
||||
resolve(index: number, cancellationToken: CancellationToken): Thenable<T> {
|
||||
resolve(index: number, cancellationToken: CancellationToken): Promise<T> {
|
||||
return new Promise((c, e) => {
|
||||
if (cancellationToken.isCancellationRequested) {
|
||||
return e(canceled());
|
||||
@@ -196,7 +196,7 @@ export function mergePagers<T>(one: IPager<T>, other: IPager<T>): IPager<T> {
|
||||
firstPage: [...one.firstPage, ...other.firstPage],
|
||||
total: one.total + other.total,
|
||||
pageSize: one.pageSize + other.pageSize,
|
||||
getPage(pageIndex: number, token): Thenable<T[]> {
|
||||
getPage(pageIndex: number, token): Promise<T[]> {
|
||||
return Promise.all([one.getPage(pageIndex, token), other.getPage(pageIndex, token)])
|
||||
.then(([onePage, otherPage]) => [...onePage, ...otherPage]);
|
||||
}
|
||||
|
||||
@@ -17,27 +17,45 @@ export const sep = '/';
|
||||
*/
|
||||
export const nativeSep = isWindows ? '\\' : '/';
|
||||
|
||||
|
||||
function isPathSeparator(code: number) {
|
||||
return code === CharCode.Slash || code === CharCode.Backslash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path the path to get the dirname from
|
||||
* @param separator the separator to use
|
||||
* @returns the directory name of a path.
|
||||
*
|
||||
* '.' is returned for empty paths or single segment relative paths (as done by NodeJS)
|
||||
* For paths consisting only of a root, the input path is returned
|
||||
*/
|
||||
export function dirname(path: string, separator = nativeSep): string {
|
||||
const idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\');
|
||||
if (idx === 0) {
|
||||
const len = path.length;
|
||||
if (len === 0) {
|
||||
return '.';
|
||||
} else if (~idx === 0) {
|
||||
return path[0];
|
||||
} else if (~idx === path.length - 1) {
|
||||
return dirname(path.substring(0, path.length - 1));
|
||||
} else {
|
||||
let res = path.substring(0, ~idx);
|
||||
if (isWindows && res[res.length - 1] === ':') {
|
||||
res += separator; // make sure drive letters end with backslash
|
||||
}
|
||||
return res;
|
||||
} else if (len === 1) {
|
||||
return isPathSeparator(path.charCodeAt(0)) ? path : '.';
|
||||
}
|
||||
const root = getRoot(path, separator);
|
||||
let rootLength = root.length;
|
||||
if (rootLength >= len) {
|
||||
return path; // matched the root
|
||||
}
|
||||
if (rootLength === 0 && isPathSeparator(path.charCodeAt(0))) {
|
||||
rootLength = 1; // absolute paths stay absolute paths.
|
||||
}
|
||||
|
||||
let i = len - 1;
|
||||
if (i > rootLength) {
|
||||
i--; // no need to look at the last character. If it's a trailing slash, we ignore it.
|
||||
while (i > rootLength && !isPathSeparator(path.charCodeAt(i))) {
|
||||
i--;
|
||||
}
|
||||
}
|
||||
if (i === 0) {
|
||||
return '.'; // it was a relative path with a single segment, no root. Nodejs returns '.' here.
|
||||
}
|
||||
return path.substr(0, i);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,7 +95,7 @@ export function normalize(path: null, toOSPath?: boolean): null;
|
||||
export function normalize(path: string, toOSPath?: boolean): string;
|
||||
export function normalize(path: string | null | undefined, toOSPath?: boolean): string | null | undefined {
|
||||
|
||||
if (path === null || path === void 0) {
|
||||
if (path === null || path === undefined) {
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -102,7 +120,7 @@ export function normalize(path: string | null | undefined, toOSPath?: boolean):
|
||||
for (let end = root.length; end <= len; end++) {
|
||||
|
||||
// either at the end or at a path-separator character
|
||||
if (end === len || path.charCodeAt(end) === CharCode.Slash || path.charCodeAt(end) === CharCode.Backslash) {
|
||||
if (end === len || isPathSeparator(path.charCodeAt(end))) {
|
||||
|
||||
if (streql(path, start, end, '..')) {
|
||||
// skip current and remove parent (if there is already something)
|
||||
@@ -148,29 +166,23 @@ export function getRoot(path: string, sep: string = '/'): string {
|
||||
}
|
||||
|
||||
let len = path.length;
|
||||
let code = path.charCodeAt(0);
|
||||
if (code === CharCode.Slash || code === CharCode.Backslash) {
|
||||
|
||||
code = path.charCodeAt(1);
|
||||
if (code === CharCode.Slash || code === CharCode.Backslash) {
|
||||
const firstLetter = path.charCodeAt(0);
|
||||
if (isPathSeparator(firstLetter)) {
|
||||
if (isPathSeparator(path.charCodeAt(1))) {
|
||||
// UNC candidate \\localhost\shares\ddd
|
||||
// ^^^^^^^^^^^^^^^^^^^
|
||||
code = path.charCodeAt(2);
|
||||
if (code !== CharCode.Slash && code !== CharCode.Backslash) {
|
||||
if (!isPathSeparator(path.charCodeAt(2))) {
|
||||
let pos = 3;
|
||||
let start = pos;
|
||||
for (; pos < len; pos++) {
|
||||
code = path.charCodeAt(pos);
|
||||
if (code === CharCode.Slash || code === CharCode.Backslash) {
|
||||
if (isPathSeparator(path.charCodeAt(pos))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
code = path.charCodeAt(pos + 1);
|
||||
if (start !== pos && code !== CharCode.Slash && code !== CharCode.Backslash) {
|
||||
if (start !== pos && !isPathSeparator(path.charCodeAt(pos + 1))) {
|
||||
pos += 1;
|
||||
for (; pos < len; pos++) {
|
||||
code = path.charCodeAt(pos);
|
||||
if (code === CharCode.Slash || code === CharCode.Backslash) {
|
||||
if (isPathSeparator(path.charCodeAt(pos))) {
|
||||
return path.slice(0, pos + 1) // consume this separator
|
||||
.replace(/[\\/]/g, sep);
|
||||
}
|
||||
@@ -183,12 +195,11 @@ export function getRoot(path: string, sep: string = '/'): string {
|
||||
// ^
|
||||
return sep;
|
||||
|
||||
} else if ((code >= CharCode.A && code <= CharCode.Z) || (code >= CharCode.a && code <= CharCode.z)) {
|
||||
} else if (isWindowsDriveLetter(firstLetter)) {
|
||||
// check for windows drive letter c:\ or c:
|
||||
|
||||
if (path.charCodeAt(1) === CharCode.Colon) {
|
||||
code = path.charCodeAt(2);
|
||||
if (code === CharCode.Slash || code === CharCode.Backslash) {
|
||||
if (isPathSeparator(path.charCodeAt(2))) {
|
||||
// C:\fff
|
||||
// ^^^
|
||||
return path.slice(0, 2) + sep;
|
||||
@@ -207,8 +218,7 @@ export function getRoot(path: string, sep: string = '/'): string {
|
||||
if (pos !== -1) {
|
||||
pos += 3; // 3 -> "://".length
|
||||
for (; pos < len; pos++) {
|
||||
code = path.charCodeAt(pos);
|
||||
if (code === CharCode.Slash || code === CharCode.Backslash) {
|
||||
if (isPathSeparator(path.charCodeAt(pos))) {
|
||||
return path.slice(0, pos + 1); // consume this separator
|
||||
}
|
||||
}
|
||||
@@ -229,9 +239,9 @@ export const join: (...parts: string[]) => string = function () {
|
||||
// add the separater between two parts unless
|
||||
// there already is one
|
||||
let last = value.charCodeAt(value.length - 1);
|
||||
if (last !== CharCode.Slash && last !== CharCode.Backslash) {
|
||||
if (!isPathSeparator(last)) {
|
||||
let next = part.charCodeAt(0);
|
||||
if (next !== CharCode.Slash && next !== CharCode.Backslash) {
|
||||
if (!isPathSeparator(next)) {
|
||||
|
||||
value += sep;
|
||||
}
|
||||
@@ -386,12 +396,12 @@ export function isAbsolute_win32(path: string): boolean {
|
||||
}
|
||||
|
||||
const char0 = path.charCodeAt(0);
|
||||
if (char0 === CharCode.Slash || char0 === CharCode.Backslash) {
|
||||
if (isPathSeparator(char0)) {
|
||||
return true;
|
||||
} else if ((char0 >= CharCode.A && char0 <= CharCode.Z) || (char0 >= CharCode.a && char0 <= CharCode.z)) {
|
||||
} else if (isWindowsDriveLetter(char0)) {
|
||||
if (path.length > 2 && path.charCodeAt(1) === CharCode.Colon) {
|
||||
const char2 = path.charCodeAt(2);
|
||||
if (char2 === CharCode.Slash || char2 === CharCode.Backslash) {
|
||||
if (isPathSeparator(char2)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -403,3 +413,7 @@ export function isAbsolute_win32(path: string): boolean {
|
||||
export function isAbsolute_posix(path: string): boolean {
|
||||
return !!(path && path.charCodeAt(0) === CharCode.Slash);
|
||||
}
|
||||
|
||||
export function isWindowsDriveLetter(char0: number): boolean {
|
||||
return char0 >= CharCode.A && char0 <= CharCode.Z || char0 >= CharCode.a && char0 <= CharCode.z;
|
||||
}
|
||||
|
||||
10
src/vs/base/common/performance.d.ts
vendored
@@ -4,22 +4,18 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface PerformanceEntry {
|
||||
readonly type: 'mark' | 'measure';
|
||||
readonly name: string;
|
||||
readonly startTime: number;
|
||||
readonly duration: number;
|
||||
readonly timestamp: number;
|
||||
}
|
||||
|
||||
export function mark(name: string): void;
|
||||
|
||||
export function measure(name: string, from?: string, to?: string): PerformanceEntry;
|
||||
|
||||
/**
|
||||
* All entries filtered by type and sorted by `startTime`.
|
||||
*/
|
||||
export function getEntries(type: 'mark' | 'measure'): PerformanceEntry[];
|
||||
export function getEntries(): PerformanceEntry[];
|
||||
|
||||
export function getEntry(type: 'mark' | 'measure', name: string): PerformanceEntry;
|
||||
export function getEntry(name: string): PerformanceEntry;
|
||||
|
||||
export function getDuration(from: string, to: string): number;
|
||||
|
||||
|
||||