Archived
Initial VS Code 1.19 source merge (#571)
* Initial 1.19 xcopy * Fix yarn build * Fix numerous build breaks * Next batch of build break fixes * More build break fixes * Runtime breaks * Additional post merge fixes * Fix windows setup file * Fix test failures. * Update license header blocks to refer to source eula
This commit is contained in:
@@ -10,7 +10,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
class WindowManager {
|
||||
|
||||
public static INSTANCE = new WindowManager();
|
||||
public static readonly INSTANCE = new WindowManager();
|
||||
|
||||
// --- Zoom Level
|
||||
private _zoomLevel: number = 0;
|
||||
|
||||
+20
-567
@@ -70,30 +70,6 @@ let DATA_BINDING_ID = '__$binding';
|
||||
let LISTENER_BINDING_ID = '__$listeners';
|
||||
let VISIBILITY_BINDING_ID = '__$visibility';
|
||||
|
||||
export class Position {
|
||||
public x: number;
|
||||
public y: number;
|
||||
|
||||
constructor(x: number, y: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
export class Box {
|
||||
public top: number;
|
||||
public right: number;
|
||||
public bottom: number;
|
||||
public left: number;
|
||||
|
||||
constructor(top: number, right: number, bottom: number, left: number) {
|
||||
this.top = top;
|
||||
this.right = right;
|
||||
this.bottom = bottom;
|
||||
this.left = left;
|
||||
}
|
||||
}
|
||||
|
||||
export class Dimension {
|
||||
public width: number;
|
||||
public height: number;
|
||||
@@ -102,15 +78,6 @@ export class Dimension {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public substract(box: Box): Dimension {
|
||||
return new Dimension(this.width - box.left - box.right, this.height - box.top - box.bottom);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IRange {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
function data(element: any): any {
|
||||
@@ -169,32 +136,6 @@ export class Builder implements IDisposable {
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Builder that performs all operations on the current element of the builder and
|
||||
* the builder or element being passed in.
|
||||
*/
|
||||
public and(element: HTMLElement): MultiBuilder;
|
||||
public and(builder: Builder): MultiBuilder;
|
||||
public and(obj: any): MultiBuilder {
|
||||
|
||||
// Convert HTMLElement to Builder as necessary
|
||||
if (!(obj instanceof Builder) && !(obj instanceof MultiBuilder)) {
|
||||
obj = new Builder((<HTMLElement>obj), this.offdom);
|
||||
}
|
||||
|
||||
// Wrap Builders into MultiBuilder
|
||||
let builders: Builder[] = [this];
|
||||
if (obj instanceof MultiBuilder) {
|
||||
for (let i = 0; i < (<MultiBuilder>obj).length; i++) {
|
||||
builders.push((<MultiBuilder>obj).item(i));
|
||||
}
|
||||
} else {
|
||||
builders.push(obj);
|
||||
}
|
||||
|
||||
return new MultiBuilder(builders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts all created elements of this builder as children to the given container. If the
|
||||
* container is not provided, the element that was passed into the Builder at construction
|
||||
@@ -362,18 +303,6 @@ export class Builder implements IDisposable {
|
||||
return this.doElement('ul', attributes, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new element of this kind as child of the current element or parent.
|
||||
* Accepts an object literal as first parameter that can be used to describe the
|
||||
* attributes of the element.
|
||||
* Accepts a function as second parameter that can be used to create child elements
|
||||
* of the element. The function will be called with a new builder created with the
|
||||
* provided element.
|
||||
*/
|
||||
public ol(attributes?: any, fn?: (builder: Builder) => void): Builder {
|
||||
return this.doElement('ol', attributes, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new element of this kind as child of the current element or parent.
|
||||
* Accepts an object literal as first parameter that can be used to describe the
|
||||
@@ -422,42 +351,6 @@ export class Builder implements IDisposable {
|
||||
return this.doElement('a', attributes, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new element of this kind as child of the current element or parent.
|
||||
* Accepts an object literal as first parameter that can be used to describe the
|
||||
* attributes of the element.
|
||||
* Accepts a function as second parameter that can be used to create child elements
|
||||
* of the element. The function will be called with a new builder created with the
|
||||
* provided element.
|
||||
*/
|
||||
public header(attributes?: any, fn?: (builder: Builder) => void): Builder {
|
||||
return this.doElement('header', attributes, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new element of this kind as child of the current element or parent.
|
||||
* Accepts an object literal as first parameter that can be used to describe the
|
||||
* attributes of the element.
|
||||
* Accepts a function as second parameter that can be used to create child elements
|
||||
* of the element. The function will be called with a new builder created with the
|
||||
* provided element.
|
||||
*/
|
||||
public section(attributes?: any, fn?: (builder: Builder) => void): Builder {
|
||||
return this.doElement('section', attributes, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new element of this kind as child of the current element or parent.
|
||||
* Accepts an object literal as first parameter that can be used to describe the
|
||||
* attributes of the element.
|
||||
* Accepts a function as second parameter that can be used to create child elements
|
||||
* of the element. The function will be called with a new builder created with the
|
||||
* provided element.
|
||||
*/
|
||||
public footer(attributes?: any, fn?: (builder: Builder) => void): Builder {
|
||||
return this.doElement('footer', attributes, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new element of given tag name as child of the current element or parent.
|
||||
* Accepts an object literal as first parameter that can be used to describe the
|
||||
@@ -514,30 +407,6 @@ export class Builder implements IDisposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current element of this builder is the active element.
|
||||
*/
|
||||
public hasFocus(): boolean {
|
||||
let activeElement: Element = document.activeElement;
|
||||
|
||||
return (activeElement === this.currentElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls select() on the current HTML element;
|
||||
*/
|
||||
public domSelect(range: IRange = null): Builder {
|
||||
let input = <HTMLInputElement>this.currentElement;
|
||||
|
||||
input.select();
|
||||
|
||||
if (range) {
|
||||
input.setSelectionRange(range.start, range.end);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls blur() on the current HTML element;
|
||||
*/
|
||||
@@ -547,21 +416,12 @@ export class Builder implements IDisposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls click() on the current HTML element;
|
||||
*/
|
||||
public domClick(): Builder {
|
||||
this.currentElement.click();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers listener on event types on the current element.
|
||||
*/
|
||||
public on(type: string, fn: (e: Event, builder: Builder, unbind: IDisposable) => void, listenerToUnbindContainer?: IDisposable[], useCapture?: boolean): Builder;
|
||||
public on(typeArray: string[], fn: (e: Event, builder: Builder, unbind: IDisposable) => void, listenerToUnbindContainer?: IDisposable[], useCapture?: boolean): Builder;
|
||||
public on(arg1: any, fn: (e: Event, builder: Builder, unbind: IDisposable) => void, listenerToUnbindContainer?: IDisposable[], useCapture?: boolean): Builder {
|
||||
public on<E extends Event = Event>(type: string, fn: (e: E, builder: Builder, unbind: IDisposable) => void, listenerToUnbindContainer?: IDisposable[], useCapture?: boolean): Builder;
|
||||
public on<E extends Event = Event>(typeArray: string[], fn: (e: E, builder: Builder, unbind: IDisposable) => void, listenerToUnbindContainer?: IDisposable[], useCapture?: boolean): Builder;
|
||||
public on<E extends Event = Event>(arg1: any, fn: (e: E, builder: Builder, unbind: IDisposable) => void, listenerToUnbindContainer?: IDisposable[], useCapture?: boolean): Builder {
|
||||
|
||||
// Event Type Array
|
||||
if (types.isArray(arg1)) {
|
||||
@@ -575,7 +435,7 @@ export class Builder implements IDisposable {
|
||||
let type = arg1;
|
||||
|
||||
// Add Listener
|
||||
let unbind: IDisposable = DOM.addDisposableListener(this.currentElement, type, (e: Event) => {
|
||||
let unbind: IDisposable = DOM.addDisposableListener(this.currentElement, type, (e) => {
|
||||
fn(e, this, unbind); // Pass in Builder as Second Argument
|
||||
}, useCapture || false);
|
||||
|
||||
@@ -637,13 +497,23 @@ export class Builder implements IDisposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
public overflow(overflow: string): Builder {
|
||||
this.currentElement.style.overflow = overflow;
|
||||
return this;
|
||||
}
|
||||
public background(color: string): Builder {
|
||||
this.currentElement.style.backgroundColor = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers listener on event types on the current element and removes
|
||||
* them after first invocation.
|
||||
*/
|
||||
public once(type: string, fn: (e: Event, builder: Builder, unbind: IDisposable) => void, listenerToUnbindContainer?: IDisposable[], useCapture?: boolean): Builder;
|
||||
public once(typesArray: string[], fn: (e: Event, builder: Builder, unbind: IDisposable) => void, listenerToUnbindContainer?: IDisposable[], useCapture?: boolean): Builder;
|
||||
public once(arg1: any, fn: (e: Event, builder: Builder, unbind: IDisposable) => void, listenerToUnbindContainer?: IDisposable[], useCapture?: boolean): Builder {
|
||||
public once<E extends Event = Event>(type: string, fn: (e: E, builder: Builder, unbind: IDisposable) => void, listenerToUnbindContainer?: IDisposable[], useCapture?: boolean): Builder;
|
||||
public once<E extends Event = Event>(typesArray: string[], fn: (e: E, builder: Builder, unbind: IDisposable) => void, listenerToUnbindContainer?: IDisposable[], useCapture?: boolean): Builder;
|
||||
public once<E extends Event = Event>(arg1: any, fn: (e: E, builder: Builder, unbind: IDisposable) => void, listenerToUnbindContainer?: IDisposable[], useCapture?: boolean): Builder {
|
||||
|
||||
// Event Type Array
|
||||
if (types.isArray(arg1)) {
|
||||
@@ -657,7 +527,7 @@ export class Builder implements IDisposable {
|
||||
let type = arg1;
|
||||
|
||||
// Add Listener
|
||||
let unbind: IDisposable = DOM.addDisposableListener(this.currentElement, type, (e: Event) => {
|
||||
let unbind: IDisposable = DOM.addDisposableListener(this.currentElement, type, (e) => {
|
||||
fn(e, this, unbind); // Pass in Builder as Second Argument
|
||||
unbind.dispose();
|
||||
}, useCapture || false);
|
||||
@@ -671,30 +541,6 @@ export class Builder implements IDisposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers listener on event types on the current element and causes
|
||||
* the event to prevent default execution (e.preventDefault()). If the
|
||||
* parameter "cancelBubble" is set to true, it will also prevent bubbling
|
||||
* of the event.
|
||||
*/
|
||||
public preventDefault(type: string, cancelBubble: boolean, listenerToUnbindContainer?: IDisposable[], useCapture?: boolean): Builder;
|
||||
public preventDefault(typesArray: string[], cancelBubble: boolean, listenerToUnbindContainer?: IDisposable[], useCapture?: boolean): Builder;
|
||||
public preventDefault(arg1: any, cancelBubble: boolean, listenerToUnbindContainer?: IDisposable[], useCapture?: boolean): Builder {
|
||||
let fn = function (e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
if (cancelBubble) {
|
||||
if (e.stopPropagation) {
|
||||
e.stopPropagation();
|
||||
} else {
|
||||
e.cancelBubble = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return this.on(arg1, fn, listenerToUnbindContainer, useCapture);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method has different characteristics based on the parameter provided:
|
||||
* a) a single string passed in as argument will return the attribute value using the
|
||||
@@ -771,24 +617,6 @@ export class Builder implements IDisposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the src attribute to the value provided for the current HTML element of the builder.
|
||||
*/
|
||||
public src(src: string): Builder {
|
||||
this.currentElement.setAttribute('src', src);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the href attribute to the value provided for the current HTML element of the builder.
|
||||
*/
|
||||
public href(href: string): Builder {
|
||||
this.currentElement.setAttribute('href', href);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title attribute to the value provided for the current HTML element of the builder.
|
||||
*/
|
||||
@@ -798,15 +626,6 @@ export class Builder implements IDisposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name attribute to the value provided for the current HTML element of the builder.
|
||||
*/
|
||||
public name(name: string): Builder {
|
||||
this.currentElement.setAttribute('name', name);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type attribute to the value provided for the current HTML element of the builder.
|
||||
*/
|
||||
@@ -825,24 +644,6 @@ export class Builder implements IDisposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the alt attribute to the value provided for the current HTML element of the builder.
|
||||
*/
|
||||
public alt(alt: string): Builder {
|
||||
this.currentElement.setAttribute('alt', alt);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name draggable to the value provided for the current HTML element of the builder.
|
||||
*/
|
||||
public draggable(isDraggable: boolean): Builder {
|
||||
this.currentElement.setAttribute('draggable', isDraggable ? 'true' : 'false');
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tabindex attribute to the value provided for the current HTML element of the builder.
|
||||
*/
|
||||
@@ -986,22 +787,6 @@ export class Builder implements IDisposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the first class to the current HTML element of the builder if the second class is currently set
|
||||
* and vice versa otherwise.
|
||||
*/
|
||||
public swapClass(classA: string, classB: string): Builder {
|
||||
if (this.hasClass(classA)) {
|
||||
this.removeClass(classA);
|
||||
this.addClass(classB);
|
||||
} else {
|
||||
this.removeClass(classB);
|
||||
this.addClass(classA);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes the provided className for the current HTML element of the builder.
|
||||
*/
|
||||
@@ -1024,15 +809,6 @@ export class Builder implements IDisposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS property background.
|
||||
*/
|
||||
public background(color: string): Builder {
|
||||
this.currentElement.style.backgroundColor = color;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS property padding.
|
||||
*/
|
||||
@@ -1195,71 +971,6 @@ export class Builder implements IDisposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS property float.
|
||||
*/
|
||||
public float(float: string): Builder {
|
||||
this.currentElement.style.cssFloat = float;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS property clear.
|
||||
*/
|
||||
public clear(clear: string): Builder {
|
||||
this.currentElement.style.clear = clear;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS property for fonts back to default.
|
||||
*/
|
||||
public normal(): Builder {
|
||||
this.currentElement.style.fontStyle = 'normal';
|
||||
this.currentElement.style.fontWeight = 'normal';
|
||||
this.currentElement.style.textDecoration = 'none';
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS property font-style to italic.
|
||||
*/
|
||||
public italic(): Builder {
|
||||
this.currentElement.style.fontStyle = 'italic';
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS property font-weight to bold.
|
||||
*/
|
||||
public bold(): Builder {
|
||||
this.currentElement.style.fontWeight = 'bold';
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS property text-decoration to underline.
|
||||
*/
|
||||
public underline(): Builder {
|
||||
this.currentElement.style.textDecoration = 'underline';
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS property overflow.
|
||||
*/
|
||||
public overflow(overflow: string): Builder {
|
||||
this.currentElement.style.overflow = overflow;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS property display.
|
||||
*/
|
||||
@@ -1269,18 +980,6 @@ export class Builder implements IDisposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public disable(): Builder {
|
||||
this.currentElement.setAttribute('disabled', 'disabled');
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public enable(): Builder {
|
||||
this.currentElement.removeAttribute('disabled');
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the current element of the builder.
|
||||
*/
|
||||
@@ -1342,26 +1041,6 @@ export class Builder implements IDisposable {
|
||||
return this.hasClass('builder-hidden') || this.currentElement.style.display === 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles visibility of the current element of the builder.
|
||||
*/
|
||||
public toggleVisibility(): Builder {
|
||||
|
||||
// Cancel any pending showDelayed() invocation
|
||||
this.cancelVisibilityPromise();
|
||||
|
||||
this.swapClass('builder-visible', 'builder-hidden');
|
||||
|
||||
if (this.isHidden()) {
|
||||
this.attr('aria-hidden', 'true');
|
||||
}
|
||||
else {
|
||||
this.attr('aria-hidden', 'false');
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private cancelVisibilityPromise(): void {
|
||||
let promise: TPromise<void> = this.getProperty(VISIBILITY_BINDING_ID);
|
||||
if (promise) {
|
||||
@@ -1485,24 +1164,6 @@ export class Builder implements IDisposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS property text-align.
|
||||
*/
|
||||
public textAlign(textAlign: string): Builder {
|
||||
this.currentElement.style.textAlign = textAlign;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS property vertical-align.
|
||||
*/
|
||||
public verticalAlign(valign: string): Builder {
|
||||
this.currentElement.style.verticalAlign = valign;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private toPixel(obj: any): string {
|
||||
if (obj.toString().indexOf('px') === -1) {
|
||||
return obj.toString() + 'px';
|
||||
@@ -1553,32 +1214,6 @@ export class Builder implements IDisposable {
|
||||
return this.innerHtml(strings.escape(html), append);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided object as property to the current element. Call getBinding()
|
||||
* to retrieve it again.
|
||||
*/
|
||||
public bind(object: any): Builder {
|
||||
bindElement(this.currentElement, object);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the binding of the current element.
|
||||
*/
|
||||
public unbind(): Builder {
|
||||
unbindElement(this.currentElement);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object that was passed into the bind() call.
|
||||
*/
|
||||
public getBinding(): any {
|
||||
return getBindingFromElement(this.currentElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to store arbritary data into the current element.
|
||||
*/
|
||||
@@ -1606,29 +1241,6 @@ export class Builder implements IDisposable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new builder with the parent element of the current element of the builder.
|
||||
*/
|
||||
public parent(offdom?: boolean): Builder {
|
||||
assert.ok(!this.offdom, 'Builder was created with offdom = true and thus has no parent set');
|
||||
|
||||
return withElement(<HTMLElement>this.currentElement.parentNode, offdom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new builder with all child elements of the current element of the builder.
|
||||
*/
|
||||
public children(offdom?: boolean): MultiBuilder {
|
||||
let children = this.currentElement.children;
|
||||
|
||||
let builders: Builder[] = [];
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
builders.push(withElement(<HTMLElement>children.item(i), offdom));
|
||||
}
|
||||
|
||||
return new MultiBuilder(builders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new builder with the child at the given index.
|
||||
*/
|
||||
@@ -1638,55 +1250,6 @@ export class Builder implements IDisposable {
|
||||
return withElement(<HTMLElement>children.item(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the current HTMLElement from the given builder from this builder if this builders
|
||||
* current HTMLElement is the direct parent.
|
||||
*/
|
||||
public removeChild(builder: Builder): Builder {
|
||||
if (this.currentElement === builder.parent().getHTMLElement()) {
|
||||
this.currentElement.removeChild(builder.getHTMLElement());
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new builder with all elements matching the provided selector scoped to the
|
||||
* current element of the builder. Use Build.withElementsBySelector() to run the selector
|
||||
* over the entire DOM.
|
||||
* The returned builder is an instance of array that can have 0 elements if the selector does not match any
|
||||
* elements.
|
||||
*/
|
||||
public select(selector: string, offdom?: boolean): MultiBuilder {
|
||||
assert.ok(types.isString(selector), 'Expected String as parameter');
|
||||
|
||||
let elements = this.currentElement.querySelectorAll(selector);
|
||||
|
||||
let builders: Builder[] = [];
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
builders.push(withElement(<HTMLElement>elements.item(i), offdom));
|
||||
}
|
||||
|
||||
return new MultiBuilder(builders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current element of the builder matches the given selector and false otherwise.
|
||||
*/
|
||||
public matches(selector: string): boolean {
|
||||
let element = this.currentElement;
|
||||
let matches = (<any>element).webkitMatchesSelector || (<any>element).mozMatchesSelector || (<any>element).msMatchesSelector || (<any>element).oMatchesSelector;
|
||||
|
||||
return matches && matches.call(element, selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current element of the builder has no children.
|
||||
*/
|
||||
public isEmpty(): boolean {
|
||||
return !this.currentElement.childNodes || this.currentElement.childNodes.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recurse through all descendant nodes and remove their data binding.
|
||||
*/
|
||||
@@ -1737,6 +1300,7 @@ export class Builder implements IDisposable {
|
||||
* Removes all HTML elements from the current element of the builder.
|
||||
*/
|
||||
public clearChildren(): Builder {
|
||||
|
||||
// Remove Elements
|
||||
if (this.currentElement) {
|
||||
DOM.clearNode(this.currentElement);
|
||||
@@ -1818,16 +1382,6 @@ export class Builder implements IDisposable {
|
||||
return new Dimension(totalWidth, totalHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size (in pixels) of the inside of the element, excluding the border and padding.
|
||||
*/
|
||||
public getContentSize(): Dimension {
|
||||
let contentWidth = DOM.getContentWidth(this.currentElement);
|
||||
let contentHeight = DOM.getContentHeight(this.currentElement);
|
||||
|
||||
return new Dimension(contentWidth, contentHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Another variant of getting the inner dimensions of an element.
|
||||
*/
|
||||
@@ -1956,74 +1510,9 @@ export class MultiBuilder extends Builder {
|
||||
this.length = this.builders.length;
|
||||
}
|
||||
|
||||
public pop(): Builder {
|
||||
let element = this.builders.pop();
|
||||
this.length = this.builders.length;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
public concat(items: Builder[]): Builder[] {
|
||||
let elements = this.builders.concat(items);
|
||||
this.length = this.builders.length;
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
public shift(): Builder {
|
||||
let element = this.builders.shift();
|
||||
this.length = this.builders.length;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
public unshift(item: Builder): number {
|
||||
let res = this.builders.unshift(item);
|
||||
this.length = this.builders.length;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public slice(start: number, end?: number): Builder[] {
|
||||
let elements = this.builders.slice(start, end);
|
||||
this.length = this.builders.length;
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
public splice(start: number, deleteCount?: number): Builder[] {
|
||||
let elements = this.builders.splice(start, deleteCount);
|
||||
this.length = this.builders.length;
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
public clone(): MultiBuilder {
|
||||
return new MultiBuilder(this);
|
||||
}
|
||||
|
||||
public and(element: HTMLElement): MultiBuilder;
|
||||
public and(builder: Builder): MultiBuilder;
|
||||
public and(obj: any): MultiBuilder {
|
||||
|
||||
// Convert HTMLElement to Builder as necessary
|
||||
if (!(obj instanceof Builder) && !(obj instanceof MultiBuilder)) {
|
||||
obj = new Builder((<HTMLElement>obj));
|
||||
}
|
||||
|
||||
let builders: Builder[] = [];
|
||||
if (obj instanceof MultiBuilder) {
|
||||
for (let i = 0; i < (<MultiBuilder>obj).length; i++) {
|
||||
builders.push((<MultiBuilder>obj).item(i));
|
||||
}
|
||||
} else {
|
||||
builders.push(obj);
|
||||
}
|
||||
|
||||
this.push.apply(this, builders);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
function withBuilder(builder: Builder, offdom?: boolean): Builder {
|
||||
@@ -2034,7 +1523,7 @@ function withBuilder(builder: Builder, offdom?: boolean): Builder {
|
||||
return new Builder(builder.getHTMLElement(), offdom);
|
||||
}
|
||||
|
||||
function withElement(element: HTMLElement, offdom?: boolean): Builder {
|
||||
export function withElement(element: HTMLElement, offdom?: boolean): Builder {
|
||||
return new Builder(element, offdom);
|
||||
}
|
||||
|
||||
@@ -2065,15 +1554,6 @@ export function getPropertyFromElement(element: HTMLElement, key: string, fallba
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a property from an element.
|
||||
*/
|
||||
export function removePropertyFromElement(element: HTMLElement, key: string): void {
|
||||
if (hasData(element)) {
|
||||
delete data(element)[key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided object as property to the given element. Call getBinding()
|
||||
* to retrieve it again.
|
||||
@@ -2082,29 +1562,6 @@ export function bindElement(element: HTMLElement, object: any): void {
|
||||
setPropertyOnElement(element, DATA_BINDING_ID, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the binding of the given element.
|
||||
*/
|
||||
export function unbindElement(element: HTMLElement): void {
|
||||
removePropertyFromElement(element, DATA_BINDING_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object that was passed into the bind() call for the element.
|
||||
*/
|
||||
export function getBindingFromElement(element: HTMLElement): any {
|
||||
return getPropertyFromElement(element, DATA_BINDING_ID);
|
||||
}
|
||||
|
||||
export const Binding = {
|
||||
setPropertyOnElement: setPropertyOnElement,
|
||||
getPropertyFromElement: getPropertyFromElement,
|
||||
removePropertyFromElement: removePropertyFromElement,
|
||||
bindElement: bindElement,
|
||||
unbindElement: unbindElement,
|
||||
getBindingFromElement: getBindingFromElement
|
||||
};
|
||||
|
||||
let SELECTOR_REGEX = /([\w\-]+)?(#([\w\-]+))?((.([\w\-]+))*)/;
|
||||
|
||||
export const $: QuickBuilder = function (arg?: any): Builder {
|
||||
@@ -2197,10 +1654,6 @@ export const $: QuickBuilder = function (arg?: any): Builder {
|
||||
}
|
||||
};
|
||||
|
||||
(<any>$).Box = Box;
|
||||
(<any>$).Dimension = Dimension;
|
||||
(<any>$).Position = Position;
|
||||
(<any>$).Builder = Builder;
|
||||
(<any>$).MultiBuilder = MultiBuilder;
|
||||
(<any>$).Build = Build;
|
||||
(<any>$).Binding = Binding;
|
||||
@@ -6,7 +6,6 @@
|
||||
'use strict';
|
||||
|
||||
import { $ } from 'vs/base/browser/builder';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
/**
|
||||
* A helper that will execute a provided function when the provided HTMLElement receives
|
||||
@@ -40,42 +39,4 @@ export class DelayedDragHandler {
|
||||
public dispose(): void {
|
||||
this.clearDragTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDraggedResource {
|
||||
resource: URI;
|
||||
isExternal: boolean;
|
||||
}
|
||||
|
||||
export function extractResources(e: DragEvent, externalOnly?: boolean): IDraggedResource[] {
|
||||
const resources: IDraggedResource[] = [];
|
||||
if (e.dataTransfer.types.length > 0) {
|
||||
|
||||
// Check for in-app DND
|
||||
if (!externalOnly) {
|
||||
const rawData = e.dataTransfer.getData('URL');
|
||||
if (rawData) {
|
||||
try {
|
||||
resources.push({ resource: URI.parse(rawData), isExternal: false });
|
||||
} catch (error) {
|
||||
// Invalid URI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for native file transfer
|
||||
if (e.dataTransfer && e.dataTransfer.files) {
|
||||
for (let i = 0; i < e.dataTransfer.files.length; i++) {
|
||||
if (e.dataTransfer.files[i] && e.dataTransfer.files[i].path) {
|
||||
try {
|
||||
resources.push({ resource: URI.file(e.dataTransfer.files[i].path), isExternal: true });
|
||||
} catch (error) {
|
||||
// Invalid URI
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resources;
|
||||
}
|
||||
+72
-93
@@ -8,13 +8,13 @@ import * as platform from 'vs/base/common/platform';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { TimeoutTimer } from 'vs/base/common/async';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { EventEmitter } from 'vs/base/common/eventEmitter';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isObject } from 'vs/base/common/types';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
|
||||
export function clearNode(node: HTMLElement) {
|
||||
while (node.firstChild) {
|
||||
@@ -22,31 +22,6 @@ export function clearNode(node: HTMLElement) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls JSON.Stringify with a replacer to break apart any circular references.
|
||||
* This prevents JSON.stringify from throwing the exception
|
||||
* "Uncaught TypeError: Converting circular structure to JSON"
|
||||
*/
|
||||
export function safeStringifyDOMAware(obj: any): string {
|
||||
let seen: any[] = [];
|
||||
return JSON.stringify(obj, (key, value) => {
|
||||
|
||||
// HTML elements are never going to serialize nicely
|
||||
if (value instanceof Element) {
|
||||
return '[Element]';
|
||||
}
|
||||
|
||||
if (isObject(value) || Array.isArray(value)) {
|
||||
if (seen.indexOf(value) !== -1) {
|
||||
return '[Circular]';
|
||||
} else {
|
||||
seen.push(value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
export function isInDOM(node: Node): boolean {
|
||||
while (node) {
|
||||
if (node === document.body) {
|
||||
@@ -57,7 +32,14 @@ export function isInDOM(node: Node): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
const _manualClassList = new class {
|
||||
interface IDomClassList {
|
||||
hasClass(node: HTMLElement, className: string): boolean;
|
||||
addClass(node: HTMLElement, className: string): void;
|
||||
removeClass(node: HTMLElement, className: string): void;
|
||||
toggleClass(node: HTMLElement, className: string, shouldHaveIt?: boolean): void;
|
||||
}
|
||||
|
||||
const _manualClassList = new class implements IDomClassList {
|
||||
|
||||
private _lastStart: number;
|
||||
private _lastEnd: number;
|
||||
@@ -159,7 +141,7 @@ const _manualClassList = new class {
|
||||
}
|
||||
};
|
||||
|
||||
const _nativeClassList = new class {
|
||||
const _nativeClassList = new class implements IDomClassList {
|
||||
hasClass(node: HTMLElement, className: string): boolean {
|
||||
return className && node.classList && node.classList.contains(className);
|
||||
}
|
||||
@@ -185,7 +167,7 @@ const _nativeClassList = new class {
|
||||
|
||||
// In IE11 there is only partial support for `classList` which makes us keep our
|
||||
// custom implementation. Otherwise use the native implementation, see: http://caniuse.com/#search=classlist
|
||||
const _classList = browser.isIE ? _manualClassList : _nativeClassList;
|
||||
const _classList: IDomClassList = browser.isIE ? _manualClassList : _nativeClassList;
|
||||
export const hasClass: (node: HTMLElement, className: string) => boolean = _classList.hasClass.bind(_classList);
|
||||
export const addClass: (node: HTMLElement, className: string) => void = _classList.addClass.bind(_classList);
|
||||
export const removeClass: (node: HTMLElement, className: string) => void = _classList.removeClass.bind(_classList);
|
||||
@@ -413,18 +395,23 @@ class AnimationFrameQueueItem implements IDisposable {
|
||||
/**
|
||||
* Add a throttled listener. `handler` is fired at most every 16ms or with the next animation frame (if browser supports it).
|
||||
*/
|
||||
export interface IEventMerger<R> {
|
||||
(lastEvent: R, currentEvent: Event): R;
|
||||
export interface IEventMerger<R, E> {
|
||||
(lastEvent: R, currentEvent: E): R;
|
||||
}
|
||||
|
||||
export interface DOMEvent {
|
||||
preventDefault(): void;
|
||||
stopPropagation(): void;
|
||||
}
|
||||
|
||||
const MINIMUM_TIME_MS = 16;
|
||||
const DEFAULT_EVENT_MERGER: IEventMerger<Event> = function (lastEvent: Event, currentEvent: Event) {
|
||||
const DEFAULT_EVENT_MERGER: IEventMerger<DOMEvent, DOMEvent> = function (lastEvent: DOMEvent, currentEvent: DOMEvent) {
|
||||
return currentEvent;
|
||||
};
|
||||
|
||||
class TimeoutThrottledDomListener<R> extends Disposable {
|
||||
class TimeoutThrottledDomListener<R, E extends DOMEvent> extends Disposable {
|
||||
|
||||
constructor(node: any, type: string, handler: (event: R) => void, eventMerger: IEventMerger<R> = <any>DEFAULT_EVENT_MERGER, minimumTimeMs: number = MINIMUM_TIME_MS) {
|
||||
constructor(node: any, type: string, handler: (event: R) => void, eventMerger: IEventMerger<R, E> = <any>DEFAULT_EVENT_MERGER, minimumTimeMs: number = MINIMUM_TIME_MS) {
|
||||
super();
|
||||
|
||||
let lastEvent: R = null;
|
||||
@@ -452,8 +439,8 @@ class TimeoutThrottledDomListener<R> extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
export function addDisposableThrottledListener<R>(node: any, type: string, handler: (event: R) => void, eventMerger?: IEventMerger<R>, minimumTimeMs?: number): IDisposable {
|
||||
return new TimeoutThrottledDomListener<R>(node, type, handler, eventMerger, minimumTimeMs);
|
||||
export function addDisposableThrottledListener<R, E extends DOMEvent = DOMEvent>(node: any, type: string, handler: (event: R) => void, eventMerger?: IEventMerger<R, E>, minimumTimeMs?: number): IDisposable {
|
||||
return new TimeoutThrottledDomListener<R, E>(node, type, handler, eventMerger, minimumTimeMs);
|
||||
}
|
||||
|
||||
export function getComputedStyle(el: HTMLElement): CSSStyleDeclaration {
|
||||
@@ -490,22 +477,13 @@ const sizeUtils = {
|
||||
getBorderTopWidth: function (element: HTMLElement): number {
|
||||
return getDimension(element, 'border-top-width', 'borderTopWidth');
|
||||
},
|
||||
getBorderRightWidth: function (element: HTMLElement): number {
|
||||
return getDimension(element, 'border-right-width', 'borderRightWidth');
|
||||
},
|
||||
getBorderBottomWidth: function (element: HTMLElement): number {
|
||||
return getDimension(element, 'border-bottom-width', 'borderBottomWidth');
|
||||
},
|
||||
|
||||
getPaddingLeft: function (element: HTMLElement): number {
|
||||
return getDimension(element, 'padding-left', 'paddingLeft');
|
||||
},
|
||||
getPaddingTop: function (element: HTMLElement): number {
|
||||
return getDimension(element, 'padding-top', 'paddingTop');
|
||||
},
|
||||
getPaddingRight: function (element: HTMLElement): number {
|
||||
return getDimension(element, 'padding-right', 'paddingRight');
|
||||
},
|
||||
getPaddingBottom: function (element: HTMLElement): number {
|
||||
return getDimension(element, 'padding-bottom', 'paddingBottom');
|
||||
},
|
||||
@@ -522,7 +500,23 @@ const sizeUtils = {
|
||||
getMarginBottom: function (element: HTMLElement): number {
|
||||
return getDimension(element, 'margin-bottom', 'marginBottom');
|
||||
},
|
||||
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
getPaddingLeft: function (element: HTMLElement): number {
|
||||
return getDimension(element, 'padding-left', 'paddingLeft');
|
||||
},
|
||||
getPaddingRight: function (element: HTMLElement): number {
|
||||
return getDimension(element, 'padding-right', 'paddingRight');
|
||||
},
|
||||
getBorderRightWidth: function (element: HTMLElement): number {
|
||||
return getDimension(element, 'border-right-width', 'borderRightWidth');
|
||||
},
|
||||
|
||||
|
||||
__commaSentinel: false
|
||||
|
||||
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
@@ -601,14 +595,6 @@ export const StandardWindow: IStandardWindow = new class {
|
||||
}
|
||||
};
|
||||
|
||||
// Adapted from WinJS
|
||||
// Gets the width of the content of the specified element. The content width does not include borders or padding.
|
||||
export function getContentWidth(element: HTMLElement): number {
|
||||
let border = sizeUtils.getBorderLeftWidth(element) + sizeUtils.getBorderRightWidth(element);
|
||||
let padding = sizeUtils.getPaddingLeft(element) + sizeUtils.getPaddingRight(element);
|
||||
return element.offsetWidth - border - padding;
|
||||
}
|
||||
|
||||
// Adapted from WinJS
|
||||
// Gets the width of the element, including margins.
|
||||
export function getTotalWidth(element: HTMLElement): number {
|
||||
@@ -629,6 +615,16 @@ export function getContentHeight(element: HTMLElement): number {
|
||||
return element.offsetHeight - border - padding;
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// Adapted from WinJS
|
||||
// Gets the width of the content of the specified element. The content width does not include borders or padding.
|
||||
export function getContentWidth(element: HTMLElement): number {
|
||||
let border = sizeUtils.getBorderLeftWidth(element) + sizeUtils.getBorderRightWidth(element);
|
||||
let padding = sizeUtils.getPaddingLeft(element) + sizeUtils.getPaddingRight(element);
|
||||
return element.offsetWidth - border - padding;
|
||||
}
|
||||
|
||||
|
||||
// Adapted from WinJS
|
||||
// Gets the height of the element, including its margins.
|
||||
export function getTotalHeight(element: HTMLElement): number {
|
||||
@@ -714,23 +710,6 @@ export function createCSSRule(selector: string, cssText: string, style: HTMLStyl
|
||||
(<CSSStyleSheet>style.sheet).insertRule(selector + '{' + cssText + '}', 0);
|
||||
}
|
||||
|
||||
export function getCSSRule(selector: string, style: HTMLStyleElement = sharedStyle): any {
|
||||
if (!style) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let rules = getDynamicStyleSheetRules(style);
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
let rule = rules[i];
|
||||
let normalizedSelectorText = rule.selectorText.replace(/::/gi, ':');
|
||||
if (normalizedSelectorText === selector) {
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function removeCSSRulesContainingSelector(ruleName: string, style = sharedStyle): void {
|
||||
if (!style) {
|
||||
return;
|
||||
@@ -830,8 +809,8 @@ export const EventHelper = {
|
||||
};
|
||||
|
||||
export interface IFocusTracker {
|
||||
addBlurListener(fn: () => void): IDisposable;
|
||||
addFocusListener(fn: () => void): IDisposable;
|
||||
onDidFocus: Event<void>;
|
||||
onDidBlur: Event<void>;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
@@ -853,49 +832,49 @@ export function restoreParentsScrollTop(node: Element, state: number[]): void {
|
||||
}
|
||||
}
|
||||
|
||||
class FocusTracker extends Disposable implements IFocusTracker {
|
||||
class FocusTracker implements IFocusTracker {
|
||||
|
||||
private _eventEmitter: EventEmitter;
|
||||
private _onDidFocus = new Emitter<void>();
|
||||
readonly onDidFocus: Event<void> = this._onDidFocus.event;
|
||||
|
||||
private _onDidBlur = new Emitter<void>();
|
||||
readonly onDidBlur: Event<void> = this._onDidBlur.event;
|
||||
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(element: HTMLElement | Window) {
|
||||
super();
|
||||
|
||||
let hasFocus = false;
|
||||
let loosingFocus = false;
|
||||
|
||||
this._eventEmitter = this._register(new EventEmitter());
|
||||
|
||||
let onFocus = (event: Event) => {
|
||||
let onFocus = () => {
|
||||
loosingFocus = false;
|
||||
if (!hasFocus) {
|
||||
hasFocus = true;
|
||||
this._eventEmitter.emit('focus', {});
|
||||
this._onDidFocus.fire();
|
||||
}
|
||||
};
|
||||
|
||||
let onBlur = (event: Event) => {
|
||||
let onBlur = () => {
|
||||
if (hasFocus) {
|
||||
loosingFocus = true;
|
||||
window.setTimeout(() => {
|
||||
if (loosingFocus) {
|
||||
loosingFocus = false;
|
||||
hasFocus = false;
|
||||
this._eventEmitter.emit('blur', {});
|
||||
this._onDidBlur.fire();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
this._register(addDisposableListener(element, EventType.FOCUS, onFocus, true));
|
||||
this._register(addDisposableListener(element, EventType.BLUR, onBlur, true));
|
||||
domEvent(element, EventType.FOCUS, true)(onFocus, null, this.disposables);
|
||||
domEvent(element, EventType.BLUR, true)(onBlur, null, this.disposables);
|
||||
}
|
||||
|
||||
public addFocusListener(fn: () => void): IDisposable {
|
||||
return this._eventEmitter.addListener('focus', fn);
|
||||
}
|
||||
|
||||
public addBlurListener(fn: () => void): IDisposable {
|
||||
return this._eventEmitter.addListener('blur', fn);
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
this._onDidFocus.dispose();
|
||||
this._onDidBlur.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1024,7 +1003,7 @@ export function getElementsByTagName(tag: string): HTMLElement[] {
|
||||
return Array.prototype.slice.call(document.getElementsByTagName(tag), 0);
|
||||
}
|
||||
|
||||
export function finalHandler<T extends Event>(fn: (event: T) => any): (event: T) => any {
|
||||
export function finalHandler<T extends DOMEvent>(fn: (event: T) => any): (event: T) => any {
|
||||
return e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -64,14 +64,6 @@ export class FastDomNode<T extends HTMLElement> {
|
||||
this.domNode.style.width = this._width + 'px';
|
||||
}
|
||||
|
||||
public unsetWidth(): void {
|
||||
if (this._width === -1) {
|
||||
return;
|
||||
}
|
||||
this._width = -1;
|
||||
this.domNode.style.width = '';
|
||||
}
|
||||
|
||||
public setHeight(height: number): void {
|
||||
if (this._height === height) {
|
||||
return;
|
||||
@@ -80,14 +72,6 @@ export class FastDomNode<T extends HTMLElement> {
|
||||
this.domNode.style.height = this._height + 'px';
|
||||
}
|
||||
|
||||
public unsetHeight(): void {
|
||||
if (this._height === -1) {
|
||||
return;
|
||||
}
|
||||
this._height = -1;
|
||||
this.domNode.style.height = '';
|
||||
}
|
||||
|
||||
public setTop(top: number): void {
|
||||
if (this._top === top) {
|
||||
return;
|
||||
@@ -217,18 +201,10 @@ export class FastDomNode<T extends HTMLElement> {
|
||||
this.domNode.setAttribute(name, value);
|
||||
}
|
||||
|
||||
public getAttribute(name: string): string {
|
||||
return this.domNode.getAttribute(name);
|
||||
}
|
||||
|
||||
public removeAttribute(name: string): void {
|
||||
this.domNode.removeAttribute(name);
|
||||
}
|
||||
|
||||
public hasAttribute(name: string): boolean {
|
||||
return this.domNode.hasAttribute(name);
|
||||
}
|
||||
|
||||
public appendChild(child: FastDomNode<any>): void {
|
||||
this.domNode.appendChild(child.domNode);
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ export class GlobalMouseMoveMonitor<R> extends Disposable {
|
||||
for (let i = 0; i < windowChain.length; i++) {
|
||||
this.hooks.push(dom.addDisposableThrottledListener(windowChain[i].window.document, 'mousemove',
|
||||
(data: R) => this.mouseMoveCallback(data),
|
||||
(lastEvent: R, currentEvent: MouseEvent) => this.mouseMoveEventMerger(lastEvent, currentEvent)
|
||||
(lastEvent: R, currentEvent) => this.mouseMoveEventMerger(lastEvent, currentEvent as MouseEvent)
|
||||
));
|
||||
this.hooks.push(dom.addDisposableListener(windowChain[i].window.document, 'mouseup', (e: MouseEvent) => this.stopMonitoring(true)));
|
||||
}
|
||||
|
||||
@@ -50,8 +50,6 @@ export function renderFormattedText(formattedText: string, options: RenderOption
|
||||
export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions = {}): HTMLElement {
|
||||
const element = createElement(options);
|
||||
|
||||
const { codeBlockRenderer, actionCallback } = options;
|
||||
|
||||
// signal to code-block render that the
|
||||
// element has been created
|
||||
let signalInnerHTML: Function;
|
||||
|
||||
@@ -163,7 +163,7 @@ function extractKeyCode(e: KeyboardEvent): KeyCode {
|
||||
return KeyCodeUtils.fromString(char);
|
||||
}
|
||||
return KEY_CODE_MAP[e.keyCode] || KeyCode.Unknown;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IKeyboardEvent {
|
||||
readonly browserEvent: KeyboardEvent;
|
||||
|
||||
@@ -114,14 +114,6 @@ export class DragMouseEvent extends StandardMouseEvent {
|
||||
|
||||
}
|
||||
|
||||
export class DropMouseEvent extends DragMouseEvent {
|
||||
|
||||
constructor(e: MouseEvent) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface IWebKitMouseWheelEvent {
|
||||
wheelDeltaY: number;
|
||||
wheelDeltaX: number;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import arrays = require('vs/base/common/arrays');
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import DomUtils = require('vs/base/browser/dom');
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
|
||||
export namespace EventType {
|
||||
export const Tap = '-monaco-gesturetap';
|
||||
@@ -65,55 +66,53 @@ interface TouchEvent extends Event {
|
||||
|
||||
export class Gesture implements IDisposable {
|
||||
|
||||
private static readonly SCROLL_FRICTION = -0.005;
|
||||
private static INSTANCE: Gesture;
|
||||
private static HOLD_DELAY = 700;
|
||||
private static SCROLL_FRICTION = -0.005;
|
||||
|
||||
private targetElement: HTMLElement;
|
||||
private callOnTarget: IDisposable[];
|
||||
private dispatched: boolean;
|
||||
private targets: HTMLElement[];
|
||||
private toDispose: IDisposable[];
|
||||
private handle: IDisposable;
|
||||
|
||||
private activeTouches: { [id: number]: TouchData; };
|
||||
|
||||
constructor(target: HTMLElement) {
|
||||
this.callOnTarget = [];
|
||||
private constructor() {
|
||||
this.toDispose = [];
|
||||
this.activeTouches = {};
|
||||
this.target = target;
|
||||
this.handle = null;
|
||||
this.targets = [];
|
||||
this.toDispose.push(DomUtils.addDisposableListener(document, 'touchstart', (e) => this.onTouchStart(e)));
|
||||
this.toDispose.push(DomUtils.addDisposableListener(document, 'touchend', (e) => this.onTouchEnd(e)));
|
||||
this.toDispose.push(DomUtils.addDisposableListener(document, 'touchmove', (e) => this.onTouchMove(e)));
|
||||
}
|
||||
|
||||
public static addTarget(element: HTMLElement): void {
|
||||
if (!Gesture.isTouchDevice()) {
|
||||
return;
|
||||
}
|
||||
if (!Gesture.INSTANCE) {
|
||||
Gesture.INSTANCE = new Gesture();
|
||||
}
|
||||
|
||||
Gesture.INSTANCE.targets.push(element);
|
||||
}
|
||||
|
||||
@memoize
|
||||
private static isTouchDevice(): boolean {
|
||||
return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.target = null;
|
||||
if (this.handle) {
|
||||
this.handle.dispose();
|
||||
dispose(this.toDispose);
|
||||
this.handle = null;
|
||||
}
|
||||
}
|
||||
|
||||
public set target(element: HTMLElement) {
|
||||
this.callOnTarget = dispose(this.callOnTarget);
|
||||
|
||||
this.activeTouches = {};
|
||||
|
||||
this.targetElement = element;
|
||||
|
||||
if (!this.targetElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.callOnTarget.push(DomUtils.addDisposableListener(this.targetElement, 'touchstart', (e) => this.onTouchStart(e)));
|
||||
this.callOnTarget.push(DomUtils.addDisposableListener(this.targetElement, 'touchend', (e) => this.onTouchEnd(e)));
|
||||
this.callOnTarget.push(DomUtils.addDisposableListener(this.targetElement, 'touchmove', (e) => this.onTouchMove(e)));
|
||||
}
|
||||
|
||||
private static newGestureEvent(type: string): GestureEvent {
|
||||
let event = <GestureEvent>(<any>document.createEvent('CustomEvent'));
|
||||
event.initEvent(type, false, true);
|
||||
return event;
|
||||
}
|
||||
|
||||
private onTouchStart(e: TouchEvent): void {
|
||||
let timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
|
||||
e.preventDefault();
|
||||
|
||||
if (this.handle) {
|
||||
this.handle.dispose();
|
||||
@@ -134,17 +133,21 @@ export class Gesture implements IDisposable {
|
||||
rollingPageY: [touch.pageY]
|
||||
};
|
||||
|
||||
let evt = Gesture.newGestureEvent(EventType.Start);
|
||||
let evt = this.newGestureEvent(EventType.Start, touch.target);
|
||||
evt.pageX = touch.pageX;
|
||||
evt.pageY = touch.pageY;
|
||||
this.targetElement.dispatchEvent(evt);
|
||||
this.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
if (this.dispatched) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.dispatched = false;
|
||||
}
|
||||
}
|
||||
|
||||
private onTouchEnd(e: TouchEvent): void {
|
||||
let timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let activeTouchCount = Object.keys(this.activeTouches).length;
|
||||
|
||||
@@ -164,21 +167,19 @@ export class Gesture implements IDisposable {
|
||||
&& Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)) < 30
|
||||
&& Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)) < 30) {
|
||||
|
||||
let evt = Gesture.newGestureEvent(EventType.Tap);
|
||||
evt.initialTarget = data.initialTarget;
|
||||
let evt = this.newGestureEvent(EventType.Tap, data.initialTarget);
|
||||
evt.pageX = arrays.tail(data.rollingPageX);
|
||||
evt.pageY = arrays.tail(data.rollingPageY);
|
||||
this.targetElement.dispatchEvent(evt);
|
||||
this.dispatchEvent(evt);
|
||||
|
||||
} else if (holdTime >= Gesture.HOLD_DELAY
|
||||
&& Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)) < 30
|
||||
&& Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)) < 30) {
|
||||
|
||||
let evt = Gesture.newGestureEvent(EventType.Contextmenu);
|
||||
evt.initialTarget = data.initialTarget;
|
||||
let evt = this.newGestureEvent(EventType.Contextmenu, data.initialTarget);
|
||||
evt.pageX = arrays.tail(data.rollingPageX);
|
||||
evt.pageY = arrays.tail(data.rollingPageY);
|
||||
this.targetElement.dispatchEvent(evt);
|
||||
this.dispatchEvent(evt);
|
||||
|
||||
} else if (activeTouchCount === 1) {
|
||||
let finalX = arrays.tail(data.rollingPageX);
|
||||
@@ -188,7 +189,9 @@ export class Gesture implements IDisposable {
|
||||
let deltaX = finalX - data.rollingPageX[0];
|
||||
let deltaY = finalY - data.rollingPageY[0];
|
||||
|
||||
this.inertia(timestamp, // time now
|
||||
// We need to get all the dispatch targets on the start of the inertia event
|
||||
const dispatchTo = this.targets.filter(t => data.initialTarget instanceof Node && t.contains(data.initialTarget));
|
||||
this.inertia(dispatchTo, timestamp, // time now
|
||||
Math.abs(deltaX) / deltaT, // speed
|
||||
deltaX > 0 ? 1 : -1, // x direction
|
||||
finalX, // x now
|
||||
@@ -198,12 +201,36 @@ export class Gesture implements IDisposable {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
this.dispatchEvent(this.newGestureEvent(EventType.End, data.initialTarget));
|
||||
// forget about this touch
|
||||
delete this.activeTouches[touch.identifier];
|
||||
}
|
||||
|
||||
if (this.dispatched) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.dispatched = false;
|
||||
}
|
||||
}
|
||||
|
||||
private inertia(t1: number, vX: number, dirX: number, x: number, vY: number, dirY: number, y: number): void {
|
||||
private newGestureEvent(type: string, intialTarget?: EventTarget): GestureEvent {
|
||||
let event = <GestureEvent>(<any>document.createEvent('CustomEvent'));
|
||||
event.initEvent(type, false, true);
|
||||
event.initialTarget = intialTarget;
|
||||
return event;
|
||||
}
|
||||
|
||||
private dispatchEvent(event: GestureEvent): void {
|
||||
this.targets.forEach(target => {
|
||||
if (event.initialTarget instanceof Node && target.contains(event.initialTarget)) {
|
||||
target.dispatchEvent(event);
|
||||
this.dispatched = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private inertia(dispatchTo: EventTarget[], t1: number, vX: number, dirX: number, x: number, vY: number, dirY: number, y: number): void {
|
||||
this.handle = DomUtils.scheduleAtNextAnimationFrame(() => {
|
||||
let now = Date.now();
|
||||
|
||||
@@ -226,21 +253,19 @@ export class Gesture implements IDisposable {
|
||||
}
|
||||
|
||||
// dispatch translation event
|
||||
let evt = Gesture.newGestureEvent(EventType.Change);
|
||||
let evt = this.newGestureEvent(EventType.Change);
|
||||
evt.translationX = delta_pos_x;
|
||||
evt.translationY = delta_pos_y;
|
||||
this.targetElement.dispatchEvent(evt);
|
||||
dispatchTo.forEach(d => d.dispatchEvent(evt));
|
||||
|
||||
if (!stopped) {
|
||||
this.inertia(now, vX, dirX, x + delta_pos_x, vY, dirY, y + delta_pos_y);
|
||||
this.inertia(dispatchTo, now, vX, dirX, x + delta_pos_x, vY, dirY, y + delta_pos_y);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onTouchMove(e: TouchEvent): void {
|
||||
let timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
for (let i = 0, len = e.changedTouches.length; i < len; i++) {
|
||||
|
||||
@@ -253,12 +278,12 @@ export class Gesture implements IDisposable {
|
||||
|
||||
let data = this.activeTouches[touch.identifier];
|
||||
|
||||
let evt = Gesture.newGestureEvent(EventType.Change);
|
||||
let evt = this.newGestureEvent(EventType.Change, data.initialTarget);
|
||||
evt.translationX = touch.pageX - arrays.tail(data.rollingPageX);
|
||||
evt.translationY = touch.pageY - arrays.tail(data.rollingPageY);
|
||||
evt.pageX = touch.pageX;
|
||||
evt.pageY = touch.pageY;
|
||||
this.targetElement.dispatchEvent(evt);
|
||||
this.dispatchEvent(evt);
|
||||
|
||||
// only keep a few data points, to average the final speed
|
||||
if (data.rollingPageX.length > 3) {
|
||||
@@ -271,5 +296,11 @@ export class Gesture implements IDisposable {
|
||||
data.rollingPageY.push(touch.pageY);
|
||||
data.rollingTimestamps.push(timestamp);
|
||||
}
|
||||
|
||||
if (this.dispatched) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.dispatched = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,16 +11,15 @@ import lifecycle = require('vs/base/common/lifecycle');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner } from 'vs/base/common/actions';
|
||||
import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, IRunEvent } from 'vs/base/common/actions';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { EventType as CommonEventType } from 'vs/base/common/events';
|
||||
import types = require('vs/base/common/types');
|
||||
import { IEventEmitter, EventEmitter } from 'vs/base/common/eventEmitter';
|
||||
import { Gesture, EventType } from 'vs/base/browser/touch';
|
||||
import { EventType, Gesture } from 'vs/base/browser/touch';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface IActionItem extends IEventEmitter {
|
||||
export interface IActionItem {
|
||||
actionRunner: IActionRunner;
|
||||
setActionContext(context: any): void;
|
||||
render(element: HTMLElement): void;
|
||||
@@ -35,19 +34,16 @@ export interface IBaseActionItemOptions {
|
||||
isMenu?: boolean;
|
||||
}
|
||||
|
||||
export class BaseActionItem extends EventEmitter implements IActionItem {
|
||||
export class BaseActionItem implements IActionItem {
|
||||
|
||||
public builder: Builder;
|
||||
public _callOnDispose: lifecycle.IDisposable[];
|
||||
public _context: any;
|
||||
public _action: IAction;
|
||||
|
||||
private gesture: Gesture;
|
||||
private _actionRunner: IActionRunner;
|
||||
|
||||
constructor(context: any, action: IAction, protected options?: IBaseActionItemOptions) {
|
||||
super();
|
||||
|
||||
this._callOnDispose = [];
|
||||
this._context = context || this;
|
||||
this._action = action;
|
||||
@@ -109,7 +105,7 @@ export class BaseActionItem extends EventEmitter implements IActionItem {
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
this.builder = $(container);
|
||||
this.gesture = new Gesture(container);
|
||||
Gesture.addTarget(container);
|
||||
|
||||
const enableDragging = this.options && this.options.draggable;
|
||||
if (enableDragging) {
|
||||
@@ -118,17 +114,18 @@ export class BaseActionItem extends EventEmitter implements IActionItem {
|
||||
|
||||
this.builder.on(EventType.Tap, e => this.onClick(e));
|
||||
|
||||
this.builder.on(DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
|
||||
this.builder.on(DOM.EventType.MOUSE_DOWN, (e) => {
|
||||
if (!enableDragging) {
|
||||
DOM.EventHelper.stop(e, true); // do not run when dragging is on because that would disable it
|
||||
}
|
||||
|
||||
if (this._action.enabled && e.button === 0) {
|
||||
const mouseEvent = e as MouseEvent;
|
||||
if (this._action.enabled && mouseEvent.button === 0) {
|
||||
this.builder.addClass('active');
|
||||
}
|
||||
});
|
||||
|
||||
this.builder.on(DOM.EventType.CLICK, (e: MouseEvent) => {
|
||||
this.builder.on(DOM.EventType.CLICK, (e) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
// See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard
|
||||
// > Writing to the clipboard
|
||||
@@ -145,13 +142,13 @@ export class BaseActionItem extends EventEmitter implements IActionItem {
|
||||
}
|
||||
});
|
||||
|
||||
this.builder.on([DOM.EventType.MOUSE_UP, DOM.EventType.MOUSE_OUT], (e: MouseEvent) => {
|
||||
this.builder.on([DOM.EventType.MOUSE_UP, DOM.EventType.MOUSE_OUT], (e) => {
|
||||
DOM.EventHelper.stop(e);
|
||||
this.builder.removeClass('active');
|
||||
});
|
||||
}
|
||||
|
||||
public onClick(event: Event): void {
|
||||
public onClick(event: DOM.EventLike): void {
|
||||
DOM.EventHelper.stop(event, true);
|
||||
|
||||
let context: any;
|
||||
@@ -198,25 +195,18 @@ export class BaseActionItem extends EventEmitter implements IActionItem {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (this.builder) {
|
||||
this.builder.destroy();
|
||||
this.builder = null;
|
||||
}
|
||||
|
||||
if (this.gesture) {
|
||||
this.gesture.dispose();
|
||||
this.gesture = null;
|
||||
}
|
||||
|
||||
this._callOnDispose = lifecycle.dispose(this._callOnDispose);
|
||||
}
|
||||
}
|
||||
|
||||
export class Separator extends Action {
|
||||
|
||||
public static ID = 'vs.actions.separator';
|
||||
public static readonly ID = 'vs.actions.separator';
|
||||
|
||||
constructor(label?: string, order?: number) {
|
||||
super(Separator.ID, label, label ? 'separator text' : 'separator');
|
||||
@@ -339,14 +329,6 @@ export class ActionItem extends BaseActionItem {
|
||||
this.$e.removeClass('checked');
|
||||
}
|
||||
}
|
||||
|
||||
public _updateRadio(): void {
|
||||
if (this.getAction().radio) {
|
||||
this.$e.addClass('radio');
|
||||
} else {
|
||||
this.$e.removeClass('radio');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export enum ActionsOrientation {
|
||||
@@ -379,7 +361,7 @@ export interface IActionOptions extends IActionItemOptions {
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export class ActionBar extends EventEmitter implements IActionRunner {
|
||||
export class ActionBar implements IActionRunner {
|
||||
|
||||
public options: IActionBarOptions;
|
||||
|
||||
@@ -398,8 +380,12 @@ export class ActionBar extends EventEmitter implements IActionRunner {
|
||||
|
||||
private toDispose: lifecycle.IDisposable[];
|
||||
|
||||
private _onDidBlur = new Emitter<void>();
|
||||
private _onDidCancel = new Emitter<void>();
|
||||
private _onDidRun = new Emitter<IRunEvent>();
|
||||
private _onDidBeforeRun = new Emitter<IRunEvent>();
|
||||
|
||||
constructor(container: HTMLElement | Builder, options: IActionBarOptions = defaultOptions) {
|
||||
super();
|
||||
this.options = options;
|
||||
this._context = options.context;
|
||||
this.toDispose = [];
|
||||
@@ -410,7 +396,8 @@ export class ActionBar extends EventEmitter implements IActionRunner {
|
||||
this.toDispose.push(this._actionRunner);
|
||||
}
|
||||
|
||||
this.toDispose.push(this.addEmitter(this._actionRunner));
|
||||
this.toDispose.push(this._actionRunner.onDidRun(e => this._onDidRun.fire(e)));
|
||||
this.toDispose.push(this._actionRunner.onDidBeforeRun(e => this._onDidBeforeRun.fire(e)));
|
||||
|
||||
this.items = [];
|
||||
this.focusedItem = undefined;
|
||||
@@ -447,8 +434,8 @@ export class ActionBar extends EventEmitter implements IActionRunner {
|
||||
break;
|
||||
}
|
||||
|
||||
$(this.domNode).on(DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
$(this.domNode).on(DOM.EventType.KEY_DOWN, (e) => {
|
||||
let event = new StandardKeyboardEvent(e as KeyboardEvent);
|
||||
let eventHandled = true;
|
||||
|
||||
if (event.equals(previousKey)) {
|
||||
@@ -469,8 +456,8 @@ export class ActionBar extends EventEmitter implements IActionRunner {
|
||||
}
|
||||
});
|
||||
|
||||
$(this.domNode).on(DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
$(this.domNode).on(DOM.EventType.KEY_UP, (e) => {
|
||||
let event = new StandardKeyboardEvent(e as KeyboardEvent);
|
||||
|
||||
// Run action on Enter/Space
|
||||
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
||||
@@ -486,14 +473,14 @@ export class ActionBar extends EventEmitter implements IActionRunner {
|
||||
});
|
||||
|
||||
this.focusTracker = DOM.trackFocus(this.domNode);
|
||||
this.focusTracker.addBlurListener(() => {
|
||||
this.toDispose.push(this.focusTracker.onDidBlur(() => {
|
||||
if (document.activeElement === this.domNode || !DOM.isAncestor(document.activeElement, this.domNode)) {
|
||||
this.emit(DOM.EventType.BLUR, {});
|
||||
this._onDidBlur.fire();
|
||||
this.focusedItem = undefined;
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
this.focusTracker.addFocusListener(() => this.updateFocusedItem());
|
||||
this.toDispose.push(this.focusTracker.onDidFocus(() => this.updateFocusedItem()));
|
||||
|
||||
this.actionsList = document.createElement('ul');
|
||||
this.actionsList.className = 'actions-container';
|
||||
@@ -511,6 +498,22 @@ export class ActionBar extends EventEmitter implements IActionRunner {
|
||||
((container instanceof Builder) ? container.getHTMLElement() : container).appendChild(this.domNode);
|
||||
}
|
||||
|
||||
public get onDidBlur(): Event<void> {
|
||||
return this._onDidBlur.event;
|
||||
}
|
||||
|
||||
public get onDidCancel(): Event<void> {
|
||||
return this._onDidCancel.event;
|
||||
}
|
||||
|
||||
public get onDidRun(): Event<IRunEvent> {
|
||||
return this._onDidRun.event;
|
||||
}
|
||||
|
||||
public get onDidBeforeRun(): Event<IRunEvent> {
|
||||
return this._onDidBeforeRun.event;
|
||||
}
|
||||
|
||||
public setAriaLabel(label: string): void {
|
||||
if (label) {
|
||||
this.actionsList.setAttribute('aria-label', label);
|
||||
@@ -565,7 +568,7 @@ export class ActionBar extends EventEmitter implements IActionRunner {
|
||||
actionItemElement.setAttribute('role', 'presentation');
|
||||
|
||||
// Prevent native context menu on actions
|
||||
$(actionItemElement).on(DOM.EventType.CONTEXT_MENU, (e: Event) => {
|
||||
$(actionItemElement).on(DOM.EventType.CONTEXT_MENU, (e: DOM.EventLike) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
@@ -582,7 +585,6 @@ export class ActionBar extends EventEmitter implements IActionRunner {
|
||||
|
||||
item.actionRunner = this._actionRunner;
|
||||
item.setActionContext(this.context);
|
||||
this.addEmitter(item);
|
||||
item.render(actionItemElement);
|
||||
|
||||
if (index === null || index < 0 || index >= this.actionsList.children.length) {
|
||||
@@ -725,7 +727,7 @@ export class ActionBar extends EventEmitter implements IActionRunner {
|
||||
(<HTMLElement>document.activeElement).blur(); // remove focus from focused action
|
||||
}
|
||||
|
||||
this.emit(CommonEventType.CANCEL);
|
||||
this._onDidCancel.fire();
|
||||
}
|
||||
|
||||
public run(action: IAction, context?: any): TPromise<void> {
|
||||
@@ -746,8 +748,6 @@ export class ActionBar extends EventEmitter implements IActionRunner {
|
||||
this.toDispose = lifecycle.dispose(this.toDispose);
|
||||
|
||||
this.getContainer().destroy();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./button';
|
||||
import { EventEmitter } from 'vs/base/common/eventEmitter';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface IButtonOptions extends IButtonStyles {
|
||||
}
|
||||
@@ -30,7 +30,8 @@ const defaultOptions: IButtonStyles = {
|
||||
buttonForeground: Color.white
|
||||
};
|
||||
|
||||
export class Button extends EventEmitter {
|
||||
export class Button {
|
||||
|
||||
// {{SQL CARBON EDIT}} -- changed access modifier to protected
|
||||
protected $el: Builder;
|
||||
private options: IButtonOptions;
|
||||
@@ -40,11 +41,12 @@ export class Button extends EventEmitter {
|
||||
private buttonForeground: Color;
|
||||
private buttonBorder: Color;
|
||||
|
||||
private _onDidClick = new Emitter<any>();
|
||||
readonly onDidClick: Event<any> = this._onDidClick.event;
|
||||
|
||||
constructor(container: Builder, options?: IButtonOptions);
|
||||
constructor(container: HTMLElement, options?: IButtonOptions);
|
||||
constructor(container: any, options?: IButtonOptions) {
|
||||
super();
|
||||
|
||||
this.options = options || Object.create(null);
|
||||
mixin(this.options, defaultOptions, false);
|
||||
|
||||
@@ -64,14 +66,14 @@ export class Button extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit(DOM.EventType.CLICK, e);
|
||||
this._onDidClick.fire(e);
|
||||
});
|
||||
|
||||
this.$el.on(DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
this.$el.on(DOM.EventType.KEY_DOWN, (e) => {
|
||||
let event = new StandardKeyboardEvent(e as KeyboardEvent);
|
||||
let eventHandled = false;
|
||||
if (this.enabled && event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
||||
this.emit(DOM.EventType.CLICK, e);
|
||||
this._onDidClick.fire(e);
|
||||
eventHandled = true;
|
||||
} else if (event.equals(KeyCode.Escape)) {
|
||||
this.$el.domBlur();
|
||||
@@ -83,7 +85,7 @@ export class Button extends EventEmitter {
|
||||
}
|
||||
});
|
||||
|
||||
this.$el.on(DOM.EventType.MOUSE_OVER, (e: MouseEvent) => {
|
||||
this.$el.on(DOM.EventType.MOUSE_OVER, (e) => {
|
||||
if (!this.$el.hasClass('disabled')) {
|
||||
const hoverBackground = this.buttonHoverBackground ? this.buttonHoverBackground.toString() : null;
|
||||
if (hoverBackground) {
|
||||
@@ -92,7 +94,7 @@ export class Button extends EventEmitter {
|
||||
}
|
||||
});
|
||||
|
||||
this.$el.on(DOM.EventType.MOUSE_OUT, (e: MouseEvent) => {
|
||||
this.$el.on(DOM.EventType.MOUSE_OUT, (e) => {
|
||||
this.applyStyles(); // restore standard styles
|
||||
});
|
||||
|
||||
@@ -167,6 +169,6 @@ export class Button extends EventEmitter {
|
||||
this.$el = null;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
this._onDidClick.dispose();
|
||||
}
|
||||
}
|
||||
@@ -39,13 +39,13 @@ export class Checkbox extends Widget {
|
||||
|
||||
constructor(opts: ICheckboxOpts) {
|
||||
super();
|
||||
this._opts = objects.clone(opts);
|
||||
this._opts = objects.deepClone(opts);
|
||||
objects.mixin(this._opts, defaultOpts, false);
|
||||
this._checked = this._opts.isChecked;
|
||||
|
||||
this.domNode = document.createElement('div');
|
||||
this.domNode.title = this._opts.title;
|
||||
this.domNode.className = this._className();
|
||||
this.domNode.className = 'custom-checkbox ' + this._opts.actionClassName + ' ' + (this._checked ? 'checked' : 'unchecked');
|
||||
this.domNode.tabIndex = 0;
|
||||
this.domNode.setAttribute('role', 'checkbox');
|
||||
this.domNode.setAttribute('aria-checked', String(this._checked));
|
||||
@@ -88,12 +88,13 @@ export class Checkbox extends Widget {
|
||||
public set checked(newIsChecked: boolean) {
|
||||
this._checked = newIsChecked;
|
||||
this.domNode.setAttribute('aria-checked', String(this._checked));
|
||||
this.domNode.className = this._className();
|
||||
this.applyStyles();
|
||||
}
|
||||
if (this._checked) {
|
||||
this.domNode.classList.add('checked');
|
||||
} else {
|
||||
this.domNode.classList.remove('checked');
|
||||
}
|
||||
|
||||
private _className(): string {
|
||||
return 'custom-checkbox ' + this._opts.actionClassName + ' ' + (this._checked ? 'checked' : 'unchecked');
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public width(): number {
|
||||
|
||||
@@ -10,7 +10,6 @@ import 'vs/css!./contextview';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { EventEmitter } from 'vs/base/common/eventEmitter';
|
||||
|
||||
export interface IAnchor {
|
||||
x: number;
|
||||
@@ -103,10 +102,10 @@ function layout(view: ISize, around: IView, viewport: IView, anchorPosition: Anc
|
||||
return { top: top, left: left };
|
||||
}
|
||||
|
||||
export class ContextView extends EventEmitter {
|
||||
export class ContextView {
|
||||
|
||||
private static BUBBLE_UP_EVENTS = ['click', 'keydown', 'focus', 'blur'];
|
||||
private static BUBBLE_DOWN_EVENTS = ['click'];
|
||||
private static readonly BUBBLE_UP_EVENTS = ['click', 'keydown', 'focus', 'blur'];
|
||||
private static readonly BUBBLE_DOWN_EVENTS = ['click'];
|
||||
|
||||
private $container: Builder;
|
||||
private $view: Builder;
|
||||
@@ -115,7 +114,6 @@ export class ContextView extends EventEmitter {
|
||||
private toDisposeOnClean: IDisposable;
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
super();
|
||||
this.$view = $('.context-view').hide();
|
||||
this.setContainer(container);
|
||||
|
||||
@@ -265,7 +263,6 @@ export class ContextView extends EventEmitter {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this.hide();
|
||||
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
|
||||
@@ -11,7 +11,6 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Gesture, EventType as GestureEventType } from 'vs/base/browser/touch';
|
||||
import { ActionRunner, IAction } from 'vs/base/common/actions';
|
||||
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { EventEmitter } from 'vs/base/common/eventEmitter';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { IMenuOptions } from 'vs/base/browser/ui/menu/menu';
|
||||
@@ -68,7 +67,7 @@ export class BaseDropdown extends ActionRunner {
|
||||
this._toDispose.push(cleanupFn);
|
||||
}
|
||||
|
||||
this._toDispose.push(new Gesture(this.$label.getHTMLElement()));
|
||||
Gesture.addTarget(this.$label.getHTMLElement());
|
||||
}
|
||||
|
||||
public get toDispose(): IDisposable[] {
|
||||
@@ -244,22 +243,4 @@ export class DropdownMenu extends BaseDropdown {
|
||||
public hide(): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
export class DropdownGroup extends EventEmitter {
|
||||
|
||||
private el: HTMLElement;
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
super();
|
||||
|
||||
this.el = document.createElement('div');
|
||||
this.el.className = 'dropdown-group';
|
||||
|
||||
container.appendChild(this.el);
|
||||
}
|
||||
|
||||
public get element(): HTMLElement {
|
||||
return this.el;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { isFunction } from 'vs/base/common/types';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { DropdownMenu, IDropdownMenuOptions } from 'vs/base/browser/ui/dropdown/dropdown';
|
||||
|
||||
export interface ILinksDropdownMenuOptions extends IDropdownMenuOptions {
|
||||
tooltip: string;
|
||||
}
|
||||
|
||||
export class LinksDropdownMenu extends DropdownMenu {
|
||||
|
||||
constructor(container: HTMLElement, options: ILinksDropdownMenuOptions) {
|
||||
super(container, options);
|
||||
|
||||
this.tooltip = options.tooltip;
|
||||
}
|
||||
|
||||
protected onEvent(e: Event, activeElement: HTMLElement): void {
|
||||
if (e instanceof KeyboardEvent && ((<KeyboardEvent>e).ctrlKey || (isMacintosh && (<KeyboardEvent>e).metaKey))) {
|
||||
return; // allow to use Ctrl/Meta in workspace dropdown menu
|
||||
}
|
||||
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
export class LinkDropdownAction extends Action {
|
||||
|
||||
constructor(id: string, name: string, clazz: string, url: () => string, forceOpenInNewTab?: boolean);
|
||||
constructor(id: string, name: string, clazz: string, url: string, forceOpenInNewTab?: boolean);
|
||||
constructor(id: string, name: string, clazz: string, url: any, forceOpenInNewTab?: boolean) {
|
||||
super(id, name, clazz, true, (e: Event) => {
|
||||
let urlString = url;
|
||||
|
||||
if (isFunction(url)) {
|
||||
urlString = url();
|
||||
}
|
||||
|
||||
if (forceOpenInNewTab || (e instanceof MouseEvent && ((<MouseEvent>e).ctrlKey || (isMacintosh && (<MouseEvent>e).metaKey)))) {
|
||||
window.open(urlString, '_blank');
|
||||
} else {
|
||||
window.location.href = urlString;
|
||||
}
|
||||
|
||||
return TPromise.as(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -268,8 +268,7 @@ export class FindInput extends Widget {
|
||||
placeholder: this.placeholder || '',
|
||||
ariaLabel: this.label || '',
|
||||
validationOptions: {
|
||||
validation: this.validation || null,
|
||||
showMessage: true
|
||||
validation: this.validation || null
|
||||
},
|
||||
inputBackground: this.inputBackground,
|
||||
inputForeground: this.inputForeground,
|
||||
|
||||
@@ -11,18 +11,13 @@ import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlighte
|
||||
import { IMatch } from 'vs/base/common/filters';
|
||||
import uri from 'vs/base/common/uri';
|
||||
import paths = require('vs/base/common/paths');
|
||||
import { IWorkspaceFolderProvider, getPathLabel, IUserHomeProvider } from 'vs/base/common/labels';
|
||||
import { IWorkspaceFolderProvider, getPathLabel, IUserHomeProvider, getBaseLabel } from 'vs/base/common/labels';
|
||||
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IIconLabelCreationOptions {
|
||||
supportHighlights?: boolean;
|
||||
}
|
||||
|
||||
export interface ILabelBadgeOptions {
|
||||
title: string;
|
||||
className: string;
|
||||
}
|
||||
|
||||
export interface IIconLabelOptions {
|
||||
title?: string;
|
||||
extraClasses?: string[];
|
||||
@@ -168,6 +163,6 @@ export class FileLabel extends IconLabel {
|
||||
public setFile(file: uri, provider: IWorkspaceFolderProvider, userHome: IUserHomeProvider): void {
|
||||
const parent = paths.dirname(file.fsPath);
|
||||
|
||||
this.setValue(paths.basename(file.fsPath), parent && parent !== '.' ? getPathLabel(parent, provider, userHome) : '', { title: file.fsPath });
|
||||
this.setValue(getBaseLabel(file), parent && parent !== '.' ? getPathLabel(parent, provider, userHome) : '', { title: file.fsPath });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,6 @@ export interface IMessage {
|
||||
|
||||
export interface IInputValidationOptions {
|
||||
validation: IInputValidator;
|
||||
showMessage?: boolean;
|
||||
}
|
||||
|
||||
export enum MessageType {
|
||||
@@ -94,11 +93,11 @@ export class InputBox extends Widget {
|
||||
private placeholder: string;
|
||||
private ariaLabel: string;
|
||||
private validation: IInputValidator;
|
||||
private showValidationMessage: boolean;
|
||||
private state = 'idle';
|
||||
private cachedHeight: number;
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected showValidationMessage: boolean;
|
||||
protected inputBackground: Color;
|
||||
protected inputForeground: Color;
|
||||
protected inputBorder: Color;
|
||||
@@ -141,7 +140,7 @@ export class InputBox extends Widget {
|
||||
if (this.options.validationOptions) {
|
||||
this.validation = this.options.validationOptions.validation;
|
||||
// {{SQL CARBON EDIT}} Canidate for addition to vscode
|
||||
this.showValidationMessage = this.options.validationOptions.showMessage || true;
|
||||
this.showValidationMessage = true;
|
||||
}
|
||||
|
||||
this.element = dom.append(container, $('.monaco-inputbox.idle'));
|
||||
@@ -235,10 +234,6 @@ export class InputBox extends Widget {
|
||||
}
|
||||
}
|
||||
|
||||
public setContextViewProvider(contextViewProvider: IContextViewProvider): void {
|
||||
this.contextViewProvider = contextViewProvider;
|
||||
}
|
||||
|
||||
public get inputElement(): HTMLInputElement {
|
||||
return this.input;
|
||||
}
|
||||
@@ -405,9 +400,9 @@ export class InputBox extends Widget {
|
||||
className: 'monaco-inputbox-message'
|
||||
};
|
||||
|
||||
let spanElement: HTMLElement = (this.message.formatContent
|
||||
const spanElement = (this.message.formatContent
|
||||
? renderFormattedText(this.message.content, renderOptions)
|
||||
: renderText(this.message.content, renderOptions)) as any;
|
||||
: renderText(this.message.content, renderOptions));
|
||||
dom.addClass(spanElement, this.classForType(this.message.type));
|
||||
|
||||
const styles = this.stylesForType(this.message.type);
|
||||
@@ -511,7 +506,6 @@ export class InputBox extends Widget {
|
||||
this.placeholder = null;
|
||||
this.ariaLabel = null;
|
||||
this.validation = null;
|
||||
this.showValidationMessage = null;
|
||||
this.state = null;
|
||||
this.actionbar = null;
|
||||
|
||||
|
||||
@@ -43,4 +43,6 @@
|
||||
}
|
||||
|
||||
/* Focus */
|
||||
.monaco-list.element-focused { outline: 0 !important; }
|
||||
.monaco-list.element-focused, .monaco-list.selection-single, .monaco-list.selection-multiple {
|
||||
outline: 0 !important;
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { GestureEvent } from 'vs/base/browser/touch';
|
||||
|
||||
export interface IDelegate<T> {
|
||||
getHeight(element: T): number;
|
||||
getTemplateId(element: T): string;
|
||||
@@ -15,19 +17,26 @@ export interface IRenderer<TElement, TTemplateData> {
|
||||
disposeTemplate(templateData: TTemplateData): void;
|
||||
}
|
||||
|
||||
export interface IListElementEvent<T, E> {
|
||||
element: T;
|
||||
index: number;
|
||||
event: E;
|
||||
}
|
||||
|
||||
export interface IListEvent<T> {
|
||||
elements: T[];
|
||||
indexes: number[];
|
||||
}
|
||||
|
||||
export interface IListMouseEvent<T> extends MouseEvent {
|
||||
element: T;
|
||||
export interface IListMouseEvent<T> {
|
||||
browserEvent: MouseEvent;
|
||||
element: T | undefined;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface IListTouchEvent<T> {
|
||||
browserEvent: TouchEvent;
|
||||
element: T | undefined;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface IListGestureEvent<T> {
|
||||
browserEvent: GestureEvent;
|
||||
element: T | undefined;
|
||||
index: number;
|
||||
}
|
||||
|
||||
@@ -35,4 +44,4 @@ export interface IListContextMenuEvent<T> {
|
||||
element: T;
|
||||
index: number;
|
||||
anchor: HTMLElement | { x: number; y: number; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'vs/css!./list';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { range } from 'vs/base/common/arrays';
|
||||
import { IDelegate, IRenderer, IListEvent } from './list';
|
||||
import { List, IListOptions } from './listWidget';
|
||||
import { List, IListOptions, IListStyles } from './listWidget';
|
||||
import { IPagedModel } from 'vs/base/common/paging';
|
||||
import Event, { mapEvent } from 'vs/base/common/event';
|
||||
|
||||
@@ -73,6 +73,22 @@ export class PagedList<T> {
|
||||
this.list = new List(container, delegate, pagedRenderers, options);
|
||||
}
|
||||
|
||||
getHTMLElement(): HTMLElement {
|
||||
return this.list.getHTMLElement();
|
||||
}
|
||||
|
||||
isDOMFocused(): boolean {
|
||||
return this.list.getHTMLElement() === document.activeElement;
|
||||
}
|
||||
|
||||
get onDidFocus(): Event<void> {
|
||||
return this.list.onDidFocus;
|
||||
}
|
||||
|
||||
get onDidBlur(): Event<void> {
|
||||
return this.list.onDidBlur;
|
||||
}
|
||||
|
||||
get widget(): List<number> {
|
||||
return this.list;
|
||||
}
|
||||
@@ -110,6 +126,14 @@ export class PagedList<T> {
|
||||
this.list.scrollTop = scrollTop;
|
||||
}
|
||||
|
||||
open(indexes: number[]): void {
|
||||
this.list.open(indexes);
|
||||
}
|
||||
|
||||
setFocus(indexes: number[]): void {
|
||||
this.list.setFocus(indexes);
|
||||
}
|
||||
|
||||
focusNext(n?: number, loop?: boolean): void {
|
||||
this.list.focusNext(n, loop);
|
||||
}
|
||||
@@ -149,4 +173,8 @@ export class PagedList<T> {
|
||||
reveal(index: number, relativeTop?: number): void {
|
||||
this.list.reveal(index, relativeTop);
|
||||
}
|
||||
|
||||
style(styles: IListStyles): void {
|
||||
this.list.style(styles);
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,22 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { toObject, assign, getOrDefault } from 'vs/base/common/objects';
|
||||
import { getOrDefault } from 'vs/base/common/objects';
|
||||
import { IDisposable, dispose } 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 } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollEvent, ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { RangeMap, IRange, relativeComplement, each } from './rangeMap';
|
||||
import { IDelegate, IRenderer } from './list';
|
||||
import { RangeMap, IRange, relativeComplement, intersect, shift } from './rangeMap';
|
||||
import { IDelegate, IRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent } 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';
|
||||
|
||||
function canUseTranslate3d(): boolean {
|
||||
if (browser.isFirefox) {
|
||||
@@ -46,18 +50,6 @@ interface IItem<T> {
|
||||
row: IRow;
|
||||
}
|
||||
|
||||
const MouseEventTypes = [
|
||||
'click',
|
||||
'dblclick',
|
||||
'mouseup',
|
||||
'mousedown',
|
||||
'mouseover',
|
||||
'mousemove',
|
||||
'mouseout',
|
||||
'contextmenu',
|
||||
'touchstart'
|
||||
];
|
||||
|
||||
export interface IListViewOptions {
|
||||
useShadows?: boolean;
|
||||
}
|
||||
@@ -66,19 +58,23 @@ const DefaultOptions: IListViewOptions = {
|
||||
useShadows: true
|
||||
};
|
||||
|
||||
export class ListView<T> implements IDisposable {
|
||||
export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
private items: IItem<T>[];
|
||||
private itemId: number;
|
||||
private rangeMap: RangeMap;
|
||||
private cache: RowCache<T>;
|
||||
private renderers: { [templateId: string]: IRenderer<T, any>; };
|
||||
private renderers = new Map<string, IRenderer<T, any>>();
|
||||
private lastRenderTop: number;
|
||||
private lastRenderHeight: number;
|
||||
private _domNode: HTMLElement;
|
||||
private gesture: Gesture;
|
||||
private rowsContainer: HTMLElement;
|
||||
private scrollableElement: ScrollableElement;
|
||||
private splicing = false;
|
||||
private dragAndDropScrollInterval: number;
|
||||
private dragAndDropScrollTimeout: number;
|
||||
private dragAndDropMouseY: number;
|
||||
private disposables: IDisposable[];
|
||||
|
||||
constructor(
|
||||
@@ -90,7 +86,11 @@ export class ListView<T> implements IDisposable {
|
||||
this.items = [];
|
||||
this.itemId = 0;
|
||||
this.rangeMap = new RangeMap();
|
||||
this.renderers = toObject<IRenderer<T, any>>(renderers, r => r.templateId);
|
||||
|
||||
for (const renderer of renderers) {
|
||||
this.renderers.set(renderer.templateId, renderer);
|
||||
}
|
||||
|
||||
this.cache = new RowCache(this.renderers);
|
||||
|
||||
this.lastRenderTop = 0;
|
||||
@@ -101,7 +101,7 @@ export class ListView<T> implements IDisposable {
|
||||
|
||||
this.rowsContainer = document.createElement('div');
|
||||
this.rowsContainer.className = 'monaco-list-rows';
|
||||
this.gesture = new Gesture(this.rowsContainer);
|
||||
Gesture.addTarget(this.rowsContainer);
|
||||
|
||||
this.scrollableElement = new ScrollableElement(this.rowsContainer, {
|
||||
alwaysConsumeMouseWheel: true,
|
||||
@@ -118,6 +118,9 @@ export class ListView<T> implements IDisposable {
|
||||
this.scrollableElement.onScroll(this.onScroll, this, this.disposables);
|
||||
domEvent(this.rowsContainer, TouchEventType.Change)(this.onTouchChange, this, this.disposables);
|
||||
|
||||
const onDragOver = mapEvent(domEvent(this.rowsContainer, 'dragover'), e => new DragMouseEvent(e));
|
||||
onDragOver(this.onDragOver, this, this.disposables);
|
||||
|
||||
this.layout();
|
||||
}
|
||||
|
||||
@@ -126,8 +129,31 @@ export class ListView<T> implements IDisposable {
|
||||
}
|
||||
|
||||
splice(start: number, deleteCount: number, elements: T[] = []): T[] {
|
||||
if (this.splicing) {
|
||||
throw new Error('Can\'t run recursive splices.');
|
||||
}
|
||||
|
||||
this.splicing = true;
|
||||
|
||||
try {
|
||||
return this._splice(start, deleteCount, elements);
|
||||
} finally {
|
||||
this.splicing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _splice(start: number, deleteCount: number, elements: T[] = []): T[] {
|
||||
const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
|
||||
each(previousRenderRange, i => this.removeItemFromDOM(this.items[i]));
|
||||
const deleteRange = { start, end: start + deleteCount };
|
||||
const removeRange = intersect(previousRenderRange, deleteRange);
|
||||
|
||||
for (let i = removeRange.start; i < removeRange.end; i++) {
|
||||
this.removeItemFromDOM(this.items[i]);
|
||||
}
|
||||
|
||||
const previousRestRange: IRange = { start: start + deleteCount, end: this.items.length };
|
||||
const previousRenderedRestRange = intersect(previousRestRange, previousRenderRange);
|
||||
const previousUnrenderedRestRanges = relativeComplement(previousRestRange, previousRenderRange);
|
||||
|
||||
const inserted = elements.map<IItem<T>>(element => ({
|
||||
id: String(this.itemId++),
|
||||
@@ -138,11 +164,38 @@ export class ListView<T> implements IDisposable {
|
||||
}));
|
||||
|
||||
this.rangeMap.splice(start, deleteCount, ...inserted);
|
||||
|
||||
const deleted = this.items.splice(start, deleteCount, ...inserted);
|
||||
|
||||
const delta = elements.length - deleteCount;
|
||||
const renderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
|
||||
each(renderRange, i => this.insertItemInDOM(this.items[i], i));
|
||||
const renderedRestRange = shift(previousRenderedRestRange, delta);
|
||||
const updateRange = intersect(renderRange, renderedRestRange);
|
||||
|
||||
for (let i = updateRange.start; i < updateRange.end; i++) {
|
||||
this.updateItemInDOM(this.items[i], i);
|
||||
}
|
||||
|
||||
const removeRanges = relativeComplement(renderedRestRange, renderRange);
|
||||
|
||||
for (let r = 0; r < removeRanges.length; r++) {
|
||||
const removeRange = removeRanges[r];
|
||||
|
||||
for (let i = removeRange.start; i < removeRange.end; i++) {
|
||||
this.removeItemFromDOM(this.items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
const unrenderedRestRanges = previousUnrenderedRestRanges.map(r => shift(r, delta));
|
||||
const elementsRange = { start, end: start + elements.length };
|
||||
const insertRanges = [elementsRange, ...unrenderedRestRanges].map(r => intersect(renderRange, r));
|
||||
|
||||
for (let r = 0; r < insertRanges.length; r++) {
|
||||
const insertRange = insertRanges[r];
|
||||
|
||||
for (let i = insertRange.start; i < insertRange.end; i++) {
|
||||
this.insertItemInDOM(this.items[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
const scrollHeight = this.getContentHeight();
|
||||
this.rowsContainer.style.height = `${scrollHeight}px`;
|
||||
@@ -200,8 +253,17 @@ export class ListView<T> implements IDisposable {
|
||||
const rangesToInsert = relativeComplement(renderRange, previousRenderRange);
|
||||
const rangesToRemove = relativeComplement(previousRenderRange, renderRange);
|
||||
|
||||
rangesToInsert.forEach(range => each(range, i => this.insertItemInDOM(this.items[i], i)));
|
||||
rangesToRemove.forEach(range => each(range, i => this.removeItemFromDOM(this.items[i])));
|
||||
for (const range of rangesToInsert) {
|
||||
for (let i = range.start; i < range.end; i++) {
|
||||
this.insertItemInDOM(this.items[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
for (const range of rangesToRemove) {
|
||||
for (let i = range.start; i < range.end; i++) {
|
||||
this.removeItemFromDOM(this.items[i], );
|
||||
}
|
||||
}
|
||||
|
||||
if (canUseTranslate3d() && !isWindows /* Windows: translate3d breaks subpixel-antialias (ClearType) unless a background is defined */) {
|
||||
const transform = `translate3d(0px, -${renderTop}px, 0px)`;
|
||||
@@ -226,13 +288,18 @@ export class ListView<T> implements IDisposable {
|
||||
this.rowsContainer.appendChild(item.row.domNode);
|
||||
}
|
||||
|
||||
const renderer = this.renderers[item.templateId];
|
||||
const renderer = this.renderers.get(item.templateId);
|
||||
item.row.domNode.style.top = `${this.elementTop(index)}px`;
|
||||
item.row.domNode.style.height = `${item.size}px`;
|
||||
item.row.domNode.setAttribute('data-index', `${index}`);
|
||||
renderer.renderElement(item.element, index, item.row.templateData);
|
||||
}
|
||||
|
||||
private updateItemInDOM(item: IItem<T>, index: number): void {
|
||||
item.row.domNode.style.top = `${this.elementTop(index)}px`;
|
||||
item.row.domNode.setAttribute('data-index', `${index}`);
|
||||
}
|
||||
|
||||
private removeItemFromDOM(item: IItem<T>): void {
|
||||
this.cache.release(item.row);
|
||||
item.row = null;
|
||||
@@ -261,31 +328,33 @@ export class ListView<T> implements IDisposable {
|
||||
|
||||
// Events
|
||||
|
||||
addListener(type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable {
|
||||
const userHandler = handler;
|
||||
let domNode = this.domNode;
|
||||
@memoize get onMouseClick(): Event<IListMouseEvent<T>> { return filterEvent(mapEvent(domEvent(this.domNode, 'click'), e => this.toMouseEvent(e)), e => e.index >= 0); }
|
||||
@memoize get onMouseDblClick(): Event<IListMouseEvent<T>> { return filterEvent(mapEvent(domEvent(this.domNode, 'dblclick'), e => this.toMouseEvent(e)), e => e.index >= 0); }
|
||||
@memoize get onMouseUp(): Event<IListMouseEvent<T>> { return filterEvent(mapEvent(domEvent(this.domNode, 'mouseup'), e => this.toMouseEvent(e)), e => e.index >= 0); }
|
||||
@memoize get onMouseDown(): Event<IListMouseEvent<T>> { return filterEvent(mapEvent(domEvent(this.domNode, 'mousedown'), e => this.toMouseEvent(e)), e => e.index >= 0); }
|
||||
@memoize get onMouseOver(): Event<IListMouseEvent<T>> { return filterEvent(mapEvent(domEvent(this.domNode, 'mouseover'), e => this.toMouseEvent(e)), e => e.index >= 0); }
|
||||
@memoize get onMouseMove(): Event<IListMouseEvent<T>> { return filterEvent(mapEvent(domEvent(this.domNode, 'mousemove'), e => this.toMouseEvent(e)), e => e.index >= 0); }
|
||||
@memoize get onMouseOut(): Event<IListMouseEvent<T>> { return filterEvent(mapEvent(domEvent(this.domNode, 'mouseout'), e => this.toMouseEvent(e)), e => e.index >= 0); }
|
||||
@memoize get onContextMenu(): Event<IListMouseEvent<T>> { return filterEvent(mapEvent(domEvent(this.domNode, 'contextmenu'), e => this.toMouseEvent(e)), e => e.index >= 0); }
|
||||
@memoize get onTouchStart(): Event<IListTouchEvent<T>> { return filterEvent(mapEvent(domEvent(this.domNode, 'touchstart'), e => this.toTouchEvent(e)), e => e.index >= 0); }
|
||||
@memoize get onTap(): Event<IListGestureEvent<T>> { return filterEvent(mapEvent(domEvent(this.rowsContainer, TouchEventType.Tap), e => this.toGestureEvent(e)), e => e.index >= 0); }
|
||||
|
||||
if (MouseEventTypes.indexOf(type) > -1) {
|
||||
handler = e => this.fireScopedEvent(e, userHandler, this.getItemIndexFromMouseEvent(e));
|
||||
} else if (type === TouchEventType.Tap) {
|
||||
domNode = this.rowsContainer;
|
||||
handler = e => this.fireScopedEvent(e, userHandler, this.getItemIndexFromGestureEvent(e));
|
||||
}
|
||||
|
||||
return DOM.addDisposableListener(domNode, type, handler, useCapture);
|
||||
private toMouseEvent(browserEvent: MouseEvent): IListMouseEvent<T> {
|
||||
const index = this.getItemIndexFromEventTarget(browserEvent.target);
|
||||
const element = index < 0 ? undefined : this.items[index].element;
|
||||
return { browserEvent, index, element };
|
||||
}
|
||||
|
||||
private fireScopedEvent(
|
||||
event: any,
|
||||
handler: (event: any) => void,
|
||||
index: number
|
||||
) {
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
private toTouchEvent(browserEvent: TouchEvent): IListTouchEvent<T> {
|
||||
const index = this.getItemIndexFromEventTarget(browserEvent.target);
|
||||
const element = index < 0 ? undefined : this.items[index].element;
|
||||
return { browserEvent, index, element };
|
||||
}
|
||||
|
||||
const element = this.items[index].element;
|
||||
handler(assign(event, { element, index }));
|
||||
private toGestureEvent(browserEvent: GestureEvent): IListGestureEvent<T> {
|
||||
const index = this.getItemIndexFromEventTarget(browserEvent.initialTarget);
|
||||
const element = index < 0 ? undefined : this.items[index].element;
|
||||
return { browserEvent, index, element };
|
||||
}
|
||||
|
||||
private onScroll(e: ScrollEvent): void {
|
||||
@@ -299,16 +368,60 @@ export class ListView<T> implements IDisposable {
|
||||
this.scrollTop -= event.translationY;
|
||||
}
|
||||
|
||||
private onDragOver(event: DragMouseEvent): void {
|
||||
this.setupDragAndDropScrollInterval();
|
||||
this.dragAndDropMouseY = event.posy;
|
||||
}
|
||||
|
||||
private setupDragAndDropScrollInterval(): void {
|
||||
var viewTop = DOM.getTopLeftOffset(this._domNode).top;
|
||||
|
||||
if (!this.dragAndDropScrollInterval) {
|
||||
this.dragAndDropScrollInterval = window.setInterval(() => {
|
||||
if (this.dragAndDropMouseY === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var diff = this.dragAndDropMouseY - viewTop;
|
||||
var scrollDiff = 0;
|
||||
var upperLimit = this.renderHeight - 35;
|
||||
|
||||
if (diff < 35) {
|
||||
scrollDiff = Math.max(-14, 0.2 * (diff - 35));
|
||||
} else if (diff > upperLimit) {
|
||||
scrollDiff = Math.min(14, 0.2 * (diff - upperLimit));
|
||||
}
|
||||
|
||||
this.scrollTop += scrollDiff;
|
||||
}, 10);
|
||||
|
||||
this.cancelDragAndDropScrollTimeout();
|
||||
|
||||
this.dragAndDropScrollTimeout = window.setTimeout(() => {
|
||||
this.cancelDragAndDropScrollInterval();
|
||||
this.dragAndDropScrollTimeout = null;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private cancelDragAndDropScrollInterval(): void {
|
||||
if (this.dragAndDropScrollInterval) {
|
||||
window.clearInterval(this.dragAndDropScrollInterval);
|
||||
this.dragAndDropScrollInterval = null;
|
||||
}
|
||||
|
||||
this.cancelDragAndDropScrollTimeout();
|
||||
}
|
||||
|
||||
private cancelDragAndDropScrollTimeout(): void {
|
||||
if (this.dragAndDropScrollTimeout) {
|
||||
window.clearTimeout(this.dragAndDropScrollTimeout);
|
||||
this.dragAndDropScrollTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Util
|
||||
|
||||
private getItemIndexFromMouseEvent(event: MouseEvent): number {
|
||||
return this.getItemIndexFromEventTarget(event.target);
|
||||
}
|
||||
|
||||
private getItemIndexFromGestureEvent(event: GestureEvent): number {
|
||||
return this.getItemIndexFromEventTarget(event.initialTarget);
|
||||
}
|
||||
|
||||
private getItemIndexFromEventTarget(target: EventTarget): number {
|
||||
while (target instanceof HTMLElement && target !== this.rowsContainer) {
|
||||
const element = target as HTMLElement;
|
||||
|
||||
@@ -4,37 +4,35 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./list';
|
||||
import { IDisposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { isNumber } from 'vs/base/common/types';
|
||||
import { range } from 'vs/base/common/arrays';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { range, firstIndex } from 'vs/base/common/arrays';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { EventType as TouchEventType } from 'vs/base/browser/touch';
|
||||
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, fromCallback, anyEvent } from 'vs/base/common/event';
|
||||
import Event, { Emitter, EventBufferer, chain, mapEvent, anyEvent } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { IDelegate, IRenderer, IListEvent, IListMouseEvent, IListContextMenuEvent } from './list';
|
||||
import { IDelegate, IRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent } from './list';
|
||||
import { ListView, IListViewOptions } from './listView';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
|
||||
export interface IIdentityProvider<T> {
|
||||
(element: T): string;
|
||||
}
|
||||
|
||||
export interface ISpliceable<T> {
|
||||
splice(start: number, deleteCount: number, elements: T[]): void;
|
||||
}
|
||||
|
||||
class CombinedSpliceable<T> implements ISpliceable<T> {
|
||||
|
||||
constructor(private spliceables: ISpliceable<T>[]) { }
|
||||
|
||||
splice(start: number, deleteCount: number, elements: T[]): void {
|
||||
this.spliceables.forEach(s => s.splice(start, deleteCount, elements));
|
||||
for (const spliceable of this.spliceables) {
|
||||
spliceable.splice(start, deleteCount, elements);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,19 +40,16 @@ interface ITraitChangeEvent {
|
||||
indexes: number[];
|
||||
}
|
||||
|
||||
interface ITraitTemplateData {
|
||||
container: HTMLElement;
|
||||
elementDisposable: IDisposable;
|
||||
}
|
||||
type ITraitTemplateData = HTMLElement;
|
||||
|
||||
interface IRenderedElement {
|
||||
interface IRenderedContainer {
|
||||
templateData: ITraitTemplateData;
|
||||
index: number;
|
||||
}
|
||||
|
||||
class TraitRenderer<T, D> implements IRenderer<T, ITraitTemplateData>
|
||||
class TraitRenderer<T> implements IRenderer<T, ITraitTemplateData>
|
||||
{
|
||||
private rendered: IRenderedElement[] = [];
|
||||
private renderedElements: IRenderedContainer[] = [];
|
||||
|
||||
constructor(private trait: Trait<T>) { }
|
||||
|
||||
@@ -63,34 +58,59 @@ class TraitRenderer<T, D> implements IRenderer<T, ITraitTemplateData>
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): ITraitTemplateData {
|
||||
const elementDisposable = EmptyDisposable;
|
||||
return { container, elementDisposable };
|
||||
return container;
|
||||
}
|
||||
|
||||
renderElement(element: T, index: number, templateData: ITraitTemplateData): void {
|
||||
templateData.elementDisposable.dispose();
|
||||
const renderedElementIndex = firstIndex(this.renderedElements, el => el.templateData === templateData);
|
||||
|
||||
const rendered = { index, templateData };
|
||||
this.rendered.push(rendered);
|
||||
templateData.elementDisposable = toDisposable(once(() => this.rendered.splice(this.rendered.indexOf(rendered), 1)));
|
||||
if (renderedElementIndex >= 0) {
|
||||
const rendered = this.renderedElements[renderedElementIndex];
|
||||
this.trait.unrender(templateData);
|
||||
rendered.index = index;
|
||||
} else {
|
||||
const rendered = { index, templateData };
|
||||
this.renderedElements.push(rendered);
|
||||
}
|
||||
|
||||
this.trait.renderIndex(index, templateData.container);
|
||||
this.trait.renderIndex(index, templateData);
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
if (renderedElement.index < start) {
|
||||
rendered.push(renderedElement);
|
||||
} else if (renderedElement.index >= start + deleteCount) {
|
||||
rendered.push({
|
||||
index: renderedElement.index + insertCount - deleteCount,
|
||||
templateData: renderedElement.templateData
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.renderedElements = rendered;
|
||||
}
|
||||
|
||||
renderIndexes(indexes: number[]): void {
|
||||
this.rendered
|
||||
.filter(({ index }) => indexes.indexOf(index) > -1)
|
||||
.forEach(({ index, templateData }) => this.trait.renderIndex(index, templateData.container));
|
||||
}
|
||||
|
||||
splice(start: number, deleteCount: number): void {
|
||||
this.rendered
|
||||
.filter(({ index }) => index >= start && index < start + deleteCount)
|
||||
.forEach(({ templateData }) => templateData.elementDisposable.dispose());
|
||||
for (const { index, templateData } of this.renderedElements) {
|
||||
if (indexes.indexOf(index) > -1) {
|
||||
this.trait.renderIndex(index, templateData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ITraitTemplateData): void {
|
||||
templateData.elementDisposable.dispose();
|
||||
const index = firstIndex(this.renderedElements, el => el.templateData === templateData);
|
||||
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.renderedElements.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,8 +127,8 @@ class Trait<T> implements ISpliceable<boolean>, IDisposable {
|
||||
get trait(): string { return this._trait; }
|
||||
|
||||
@memoize
|
||||
get renderer(): TraitRenderer<T, any> {
|
||||
return new TraitRenderer<T, any>(this);
|
||||
get renderer(): TraitRenderer<T> {
|
||||
return new TraitRenderer<T>(this);
|
||||
}
|
||||
|
||||
constructor(private _trait: string) {
|
||||
@@ -124,7 +144,7 @@ class Trait<T> implements ISpliceable<boolean>, IDisposable {
|
||||
...this.indexes.filter(i => i >= end).map(i => i + diff)
|
||||
];
|
||||
|
||||
this.renderer.splice(start, deleteCount);
|
||||
this.renderer.splice(start, deleteCount, elements.length);
|
||||
this.set(indexes);
|
||||
}
|
||||
|
||||
@@ -132,6 +152,10 @@ class Trait<T> implements ISpliceable<boolean>, IDisposable {
|
||||
DOM.toggleClass(container, this._trait, this.contains(index));
|
||||
}
|
||||
|
||||
unrender(container: HTMLElement): void {
|
||||
DOM.removeClass(container, this._trait);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the indexes which should have this trait.
|
||||
*
|
||||
@@ -229,17 +253,24 @@ class TraitSpliceable<T> implements ISpliceable<T> {
|
||||
}
|
||||
}
|
||||
|
||||
function isInputElement(e: HTMLElement): boolean {
|
||||
return e.tagName === 'INPUT' || e.tagName === 'TEXTAREA';
|
||||
}
|
||||
|
||||
class KeyboardController<T> implements IDisposable {
|
||||
|
||||
private disposables: IDisposable[];
|
||||
|
||||
constructor(
|
||||
private list: List<T>,
|
||||
private view: ListView<T>
|
||||
private view: ListView<T>,
|
||||
options: IListOptions<T>
|
||||
) {
|
||||
const multipleSelectionSupport = !(options.multipleSelectionSupport === false);
|
||||
this.disposables = [];
|
||||
|
||||
const onKeyDown = chain(domEvent(view.domNode, 'keydown'))
|
||||
.filter(e => !isInputElement(e.target as HTMLElement))
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
|
||||
onKeyDown.filter(e => e.keyCode === KeyCode.Enter).on(this.onEnter, this, this.disposables);
|
||||
@@ -247,8 +278,11 @@ class KeyboardController<T> implements IDisposable {
|
||||
onKeyDown.filter(e => e.keyCode === KeyCode.DownArrow).on(this.onDownArrow, this, this.disposables);
|
||||
onKeyDown.filter(e => e.keyCode === KeyCode.PageUp).on(this.onPageUpArrow, this, this.disposables);
|
||||
onKeyDown.filter(e => e.keyCode === KeyCode.PageDown).on(this.onPageDownArrow, this, this.disposables);
|
||||
onKeyDown.filter(e => (platform.isMacintosh ? e.metaKey : e.ctrlKey) && e.keyCode === KeyCode.KEY_A).on(this.onCtrlA, this, this.disposables);
|
||||
onKeyDown.filter(e => e.keyCode === KeyCode.Escape).on(this.onEscape, this, this.disposables);
|
||||
|
||||
if (multipleSelectionSupport) {
|
||||
onKeyDown.filter(e => (platform.isMacintosh ? e.metaKey : e.ctrlKey) && e.keyCode === KeyCode.KEY_A).on(this.onCtrlA, this, this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
private onEnter(e: StandardKeyboardEvent): void {
|
||||
@@ -309,32 +343,39 @@ class KeyboardController<T> implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
function isSelectionSingleChangeEvent(event: IListMouseEvent<any>): boolean {
|
||||
return platform.isMacintosh ? event.metaKey : event.ctrlKey;
|
||||
function isSelectionSingleChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
return platform.isMacintosh ? event.browserEvent.metaKey : event.browserEvent.ctrlKey;
|
||||
}
|
||||
|
||||
function isSelectionRangeChangeEvent(event: IListMouseEvent<any>): boolean {
|
||||
return event.shiftKey;
|
||||
function isSelectionRangeChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
return event.browserEvent.shiftKey;
|
||||
}
|
||||
|
||||
function isSelectionChangeEvent(event: IListMouseEvent<any>): boolean {
|
||||
function isSelectionChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
return isSelectionSingleChangeEvent(event) || isSelectionRangeChangeEvent(event);
|
||||
}
|
||||
|
||||
export interface IMouseControllerOptions {
|
||||
selectOnMouseDown?: boolean;
|
||||
}
|
||||
|
||||
class MouseController<T> implements IDisposable {
|
||||
|
||||
private disposables: IDisposable[];
|
||||
private multipleSelectionSupport: boolean;
|
||||
private didJustPressContextMenuKey: boolean = false;
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
@memoize get onContextMenu(): Event<IListContextMenuEvent<T>> {
|
||||
const fromKeyboard = chain(domEvent(this.view.domNode, 'keydown'))
|
||||
const fromKeydown = chain(domEvent(this.view.domNode, 'keydown'))
|
||||
.map(e => new StandardKeyboardEvent(e))
|
||||
.filter(e => this.list.getFocus().length > 0)
|
||||
.filter(e => e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10))
|
||||
.map(e => {
|
||||
.filter(e => this.didJustPressContextMenuKey = e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10))
|
||||
.filter(e => { e.preventDefault(); e.stopPropagation(); return false; })
|
||||
.event as Event<any>;
|
||||
|
||||
const fromKeyup = chain(domEvent(this.view.domNode, 'keyup'))
|
||||
.filter(() => {
|
||||
const didJustPressContextMenuKey = this.didJustPressContextMenuKey;
|
||||
this.didJustPressContextMenuKey = false;
|
||||
return didJustPressContextMenuKey;
|
||||
})
|
||||
.filter(() => this.list.getFocus().length > 0)
|
||||
.map(() => {
|
||||
const index = this.list.getFocus()[0];
|
||||
const element = this.view.element(index);
|
||||
const anchor = this.view.domElement(index);
|
||||
@@ -343,40 +384,48 @@ class MouseController<T> implements IDisposable {
|
||||
.filter(({ anchor }) => !!anchor)
|
||||
.event;
|
||||
|
||||
const fromMouse = chain(fromCallback(handler => this.view.addListener('contextmenu', handler)))
|
||||
.map(({ element, index, clientX, clientY }) => ({ element, index, anchor: { x: clientX + 1, y: clientY } }))
|
||||
const fromMouse = chain(this.view.onContextMenu)
|
||||
.filter(() => !this.didJustPressContextMenuKey)
|
||||
.map(({ element, index, browserEvent }) => ({ element, index, anchor: { x: browserEvent.clientX + 1, y: browserEvent.clientY } }))
|
||||
.event;
|
||||
|
||||
return anyEvent<IListContextMenuEvent<T>>(fromKeyboard, fromMouse);
|
||||
return anyEvent<IListContextMenuEvent<T>>(fromKeydown, fromKeyup, fromMouse);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private list: List<T>,
|
||||
private view: ListView<T>,
|
||||
private options: IMouseControllerOptions = {}
|
||||
private options: IListOptions<T> = {}
|
||||
) {
|
||||
this.disposables = [];
|
||||
this.disposables.push(view.addListener('mousedown', e => this.onMouseDown(e)));
|
||||
this.disposables.push(view.addListener('click', e => this.onPointer(e)));
|
||||
this.disposables.push(view.addListener('dblclick', e => this.onDoubleClick(e)));
|
||||
this.disposables.push(view.addListener('touchstart', e => this.onMouseDown(e)));
|
||||
this.disposables.push(view.addListener(TouchEventType.Tap, e => this.onPointer(e)));
|
||||
this.multipleSelectionSupport = options.multipleSelectionSupport !== false;
|
||||
|
||||
view.onMouseDown(this.onMouseDown, this, this.disposables);
|
||||
view.onMouseClick(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);
|
||||
Gesture.addTarget(view.domNode);
|
||||
}
|
||||
|
||||
private onMouseDown(e: IListMouseEvent<T>): void {
|
||||
this.view.domNode.focus();
|
||||
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) {
|
||||
this.view.domNode.focus();
|
||||
}
|
||||
|
||||
let reference = this.list.getFocus()[0];
|
||||
reference = reference === undefined ? this.list.getSelection()[0] : reference;
|
||||
|
||||
if (isSelectionRangeChangeEvent(e)) {
|
||||
if (this.multipleSelectionSupport && isSelectionRangeChangeEvent(e)) {
|
||||
return this.changeSelection(e, reference);
|
||||
}
|
||||
|
||||
const focus = e.index;
|
||||
this.list.setFocus([focus]);
|
||||
|
||||
if (isSelectionChangeEvent(e)) {
|
||||
if (this.multipleSelectionSupport && isSelectionChangeEvent(e)) {
|
||||
return this.changeSelection(e, reference);
|
||||
}
|
||||
|
||||
@@ -387,7 +436,7 @@ class MouseController<T> implements IDisposable {
|
||||
}
|
||||
|
||||
private onPointer(e: IListMouseEvent<T>): void {
|
||||
if (isSelectionChangeEvent(e)) {
|
||||
if (this.multipleSelectionSupport && isSelectionChangeEvent(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -399,7 +448,7 @@ class MouseController<T> implements IDisposable {
|
||||
}
|
||||
|
||||
private onDoubleClick(e: IListMouseEvent<T>): void {
|
||||
if (isSelectionChangeEvent(e)) {
|
||||
if (this.multipleSelectionSupport && isSelectionChangeEvent(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -408,7 +457,7 @@ class MouseController<T> implements IDisposable {
|
||||
this.list.pin(focus);
|
||||
}
|
||||
|
||||
private changeSelection(e: IListMouseEvent<T>, reference: number | undefined): void {
|
||||
private changeSelection(e: IListMouseEvent<T> | IListTouchEvent<T>, reference: number | undefined): void {
|
||||
const focus = e.index;
|
||||
|
||||
if (isSelectionRangeChangeEvent(e) && reference !== undefined) {
|
||||
@@ -442,11 +491,14 @@ class MouseController<T> implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IListOptions<T> extends IListViewOptions, IMouseControllerOptions, IListStyles {
|
||||
export interface IListOptions<T> extends IListViewOptions, IListStyles {
|
||||
identityProvider?: IIdentityProvider<T>;
|
||||
ariaLabel?: string;
|
||||
mouseSupport?: boolean;
|
||||
selectOnMouseDown?: boolean;
|
||||
focusOnMouseDown?: boolean;
|
||||
keyboardSupport?: boolean;
|
||||
multipleSelectionSupport?: boolean;
|
||||
}
|
||||
|
||||
export interface IListStyles {
|
||||
@@ -481,7 +533,8 @@ const defaultStyles: IListStyles = {
|
||||
|
||||
const DefaultOptions: IListOptions<any> = {
|
||||
keyboardSupport: true,
|
||||
mouseSupport: true
|
||||
mouseSupport: true,
|
||||
multipleSelectionSupport: true
|
||||
};
|
||||
|
||||
// TODO@Joao: move these utils into a SortedArray class
|
||||
@@ -581,11 +634,19 @@ class PipelineRenderer<T> implements IRenderer<T, any> {
|
||||
}
|
||||
|
||||
renderElement(element: T, index: number, templateData: any[]): void {
|
||||
this.renderers.forEach((r, i) => r.renderElement(element, index, templateData[i]));
|
||||
let i = 0;
|
||||
|
||||
for (const renderer of this.renderers) {
|
||||
renderer.renderElement(element, index, templateData[i++]);
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: any[]): void {
|
||||
this.renderers.forEach((r, i) => r.disposeTemplate(templateData[i]));
|
||||
let i = 0;
|
||||
|
||||
for (const renderer of this.renderers) {
|
||||
renderer.disposeTemplate(templateData[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,7 +657,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
private focus: Trait<T>;
|
||||
private selection: Trait<T>;
|
||||
private eventBufferer: EventBufferer;
|
||||
private eventBufferer = new EventBufferer();
|
||||
private view: ListView<T>;
|
||||
private spliceable: ISpliceable<T>;
|
||||
private disposables: IDisposable[];
|
||||
@@ -610,10 +671,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
return mapEvent(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e));
|
||||
}
|
||||
|
||||
private _onContextMenu: Event<IListContextMenuEvent<T>> = Event.None;
|
||||
get onContextMenu(): Event<IListContextMenuEvent<T>> {
|
||||
return this._onContextMenu;
|
||||
}
|
||||
readonly onContextMenu: Event<IListContextMenuEvent<T>> = Event.None;
|
||||
|
||||
private _onOpen = new Emitter<number[]>();
|
||||
@memoize get onOpen(): Event<IListEvent<T>> {
|
||||
@@ -625,11 +683,25 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
return mapEvent(this._onPin.event, indexes => this.toListEvent({ indexes }));
|
||||
}
|
||||
|
||||
readonly onDOMFocus: Event<void>;
|
||||
readonly onDOMBlur: Event<void>;
|
||||
get onMouseClick(): Event<IListMouseEvent<T>> { return this.view.onMouseClick; }
|
||||
get onMouseDblClick(): Event<IListMouseEvent<T>> { return this.view.onMouseDblClick; }
|
||||
get onMouseUp(): Event<IListMouseEvent<T>> { return this.view.onMouseUp; }
|
||||
get onMouseDown(): Event<IListMouseEvent<T>> { return this.view.onMouseDown; }
|
||||
get onMouseOver(): Event<IListMouseEvent<T>> { return this.view.onMouseOver; }
|
||||
get onMouseMove(): Event<IListMouseEvent<T>> { return this.view.onMouseMove; }
|
||||
get onMouseOut(): Event<IListMouseEvent<T>> { return this.view.onMouseOut; }
|
||||
get onTouchStart(): Event<IListTouchEvent<T>> { return this.view.onTouchStart; }
|
||||
get onTap(): Event<IListGestureEvent<T>> { return this.view.onTap; }
|
||||
|
||||
private _onDispose = new Emitter<void>();
|
||||
get onDispose(): Event<void> { return this._onDispose.event; }
|
||||
get onKeyDown(): Event<KeyboardEvent> { return domEvent(this.view.domNode, 'keydown'); }
|
||||
get onKeyUp(): Event<KeyboardEvent> { return domEvent(this.view.domNode, 'keyup'); }
|
||||
get onKeyPress(): Event<KeyboardEvent> { return domEvent(this.view.domNode, 'keypress'); }
|
||||
|
||||
readonly onDidFocus: Event<void>;
|
||||
readonly onDidBlur: Event<void>;
|
||||
|
||||
private _onDidDispose = new Emitter<void>();
|
||||
get onDidDispose(): Event<void> { return this._onDidDispose.event; }
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
@@ -641,7 +713,6 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
this.focus = new FocusTrait(i => this.getElementDomId(i));
|
||||
this.selection = new Trait('selected');
|
||||
|
||||
this.eventBufferer = new EventBufferer();
|
||||
mixin(options, defaultStyles, false);
|
||||
|
||||
renderers = renderers.map(r => new PipelineRenderer(r.templateId, [aria, this.focus.renderer, this.selection.renderer, r]));
|
||||
@@ -660,20 +731,20 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
this.view
|
||||
]);
|
||||
|
||||
this.disposables = [this.focus, this.selection, this.view, this._onDispose];
|
||||
this.disposables = [this.focus, this.selection, this.view, this._onDidDispose];
|
||||
|
||||
this.onDOMFocus = mapEvent(domEvent(this.view.domNode, 'focus', true), () => null);
|
||||
this.onDOMBlur = mapEvent(domEvent(this.view.domNode, 'blur', true), () => null);
|
||||
this.onDidFocus = mapEvent(domEvent(this.view.domNode, 'focus', true), () => null);
|
||||
this.onDidBlur = mapEvent(domEvent(this.view.domNode, 'blur', true), () => null);
|
||||
|
||||
if (typeof options.keyboardSupport !== 'boolean' || options.keyboardSupport) {
|
||||
const controller = new KeyboardController(this, this.view);
|
||||
const controller = new KeyboardController(this, this.view, options);
|
||||
this.disposables.push(controller);
|
||||
}
|
||||
|
||||
if (typeof options.mouseSupport !== 'boolean' || options.mouseSupport) {
|
||||
const controller = new MouseController(this, this.view, options);
|
||||
this.disposables.push(controller);
|
||||
this._onContextMenu = controller.onContextMenu;
|
||||
this.onContextMenu = controller.onContextMenu;
|
||||
}
|
||||
|
||||
this.onFocusChange(this._onFocusChange, this, this.disposables);
|
||||
@@ -687,6 +758,10 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
|
||||
splice(start: number, deleteCount: number, elements: T[] = []): void {
|
||||
if (deleteCount === 0 && elements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.eventBufferer.bufferEvents(() => this.spliceable.splice(start, deleteCount, elements));
|
||||
}
|
||||
|
||||
@@ -966,7 +1041,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._onDispose.fire();
|
||||
this._onDidDispose.fire();
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,18 +19,18 @@ export interface IRangedGroup {
|
||||
|
||||
/**
|
||||
* Returns the intersection between two ranges as a range itself.
|
||||
* Returns `null` if the intersection is empty.
|
||||
* Returns `{ start: 0, end: 0 }` if the intersection is empty.
|
||||
*/
|
||||
export function intersect(one: IRange, other: IRange): IRange {
|
||||
if (one.start >= other.end || other.start >= one.end) {
|
||||
return null;
|
||||
return { start: 0, end: 0 };
|
||||
}
|
||||
|
||||
const start = Math.max(one.start, other.start);
|
||||
const end = Math.min(one.end, other.end);
|
||||
|
||||
if (end - start <= 0) {
|
||||
return null;
|
||||
return { start: 0, end: 0 };
|
||||
}
|
||||
|
||||
return { start, end };
|
||||
@@ -56,12 +56,6 @@ export function relativeComplement(one: IRange, other: IRange): IRange[] {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function each(range: IRange, fn: (index: number) => void): void {
|
||||
for (let i = range.start; i < range.end; i++) {
|
||||
fn(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the intersection between a ranged group and a range.
|
||||
* Returns `[]` if the intersection is empty.
|
||||
@@ -80,7 +74,7 @@ export function groupIntersect(range: IRange, groups: IRangedGroup[]): IRangedGr
|
||||
|
||||
const intersection = intersect(range, r.range);
|
||||
|
||||
if (!intersection) {
|
||||
if (isEmpty(intersection)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -96,7 +90,7 @@ export function groupIntersect(range: IRange, groups: IRangedGroup[]): IRangedGr
|
||||
/**
|
||||
* Shifts a range by that `much`.
|
||||
*/
|
||||
function shift({ start, end }: IRange, much: number): IRange {
|
||||
export function shift({ start, end }: IRange, much: number): IRange {
|
||||
return { start: start + much, end: end + much };
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,9 @@ function removeFromParent(element: HTMLElement): void {
|
||||
|
||||
export class RowCache<T> implements IDisposable {
|
||||
|
||||
private cache: { [templateId: string]: IRow[]; };
|
||||
private cache = new Map<string, IRow[]>();
|
||||
|
||||
constructor(private renderers: { [templateId: string]: IRenderer<T, any>; }) {
|
||||
this.cache = Object.create(null);
|
||||
}
|
||||
constructor(private renderers: Map<string, IRenderer<T, any>>) { }
|
||||
|
||||
/**
|
||||
* Returns a row either by creating a new one or reusing
|
||||
@@ -38,7 +36,7 @@ export class RowCache<T> implements IDisposable {
|
||||
|
||||
if (!result) {
|
||||
const domNode = $('.monaco-list-row');
|
||||
const renderer = this.renderers[templateId];
|
||||
const renderer = this.renderers.get(templateId);
|
||||
const templateData = renderer.renderTemplate(domNode);
|
||||
result = { domNode, templateId, templateData };
|
||||
}
|
||||
@@ -67,27 +65,36 @@ export class RowCache<T> implements IDisposable {
|
||||
}
|
||||
|
||||
private getTemplateCache(templateId: string): IRow[] {
|
||||
return this.cache[templateId] || (this.cache[templateId] = []);
|
||||
let result = this.cache.get(templateId);
|
||||
|
||||
if (!result) {
|
||||
result = [];
|
||||
this.cache.set(templateId, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private garbageCollect(): void {
|
||||
if (this.cache) {
|
||||
Object.keys(this.cache).forEach(templateId => {
|
||||
this.cache[templateId].forEach(cachedRow => {
|
||||
const renderer = this.renderers[templateId];
|
||||
renderer.disposeTemplate(cachedRow.templateData);
|
||||
cachedRow.domNode = null;
|
||||
cachedRow.templateData = null;
|
||||
});
|
||||
|
||||
delete this.cache[templateId];
|
||||
});
|
||||
if (!this.renderers) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cache.forEach((cachedRows, templateId) => {
|
||||
for (const cachedRow of cachedRows) {
|
||||
const renderer = this.renderers[templateId];
|
||||
renderer.disposeTemplate(cachedRow.templateData);
|
||||
cachedRow.domNode = null;
|
||||
cachedRow.templateData = null;
|
||||
}
|
||||
});
|
||||
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.garbageCollect();
|
||||
this.cache = null;
|
||||
this.cache.clear();
|
||||
this.renderers = null;
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { $ } from 'vs/base/browser/builder';
|
||||
import { IActionRunner, IAction } from 'vs/base/common/actions';
|
||||
import { ActionBar, IActionItemProvider, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { EventEmitter } from 'vs/base/common/eventEmitter';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import Event from 'vs/base/common/event';
|
||||
|
||||
export interface IMenuOptions {
|
||||
context?: any;
|
||||
@@ -20,14 +20,12 @@ export interface IMenuOptions {
|
||||
getKeyBinding?: (action: IAction) => ResolvedKeybinding;
|
||||
}
|
||||
|
||||
export class Menu extends EventEmitter {
|
||||
export class Menu {
|
||||
|
||||
private actionBar: ActionBar;
|
||||
private listener: IDisposable;
|
||||
|
||||
constructor(container: HTMLElement, actions: IAction[], options: IMenuOptions = {}) {
|
||||
super();
|
||||
|
||||
$(container).addClass('monaco-menu-container');
|
||||
|
||||
let $menu = $('.monaco-menu').appendTo(container);
|
||||
@@ -40,18 +38,22 @@ export class Menu extends EventEmitter {
|
||||
isMenu: true
|
||||
});
|
||||
|
||||
this.listener = this.addEmitter(this.actionBar);
|
||||
|
||||
this.actionBar.push(actions, { icon: true, label: true });
|
||||
}
|
||||
|
||||
public get onDidCancel(): Event<void> {
|
||||
return this.actionBar.onDidCancel;
|
||||
}
|
||||
|
||||
public get onDidBlur(): Event<void> {
|
||||
return this.actionBar.onDidBlur;
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.actionBar.focus(true);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose();
|
||||
|
||||
if (this.actionBar) {
|
||||
this.actionBar.dispose();
|
||||
this.actionBar = null;
|
||||
|
||||
@@ -40,7 +40,6 @@ export class ProgressBar {
|
||||
private toUnbind: IDisposable[];
|
||||
private workedVal: number;
|
||||
private element: Builder;
|
||||
private animationRunning: boolean;
|
||||
private bit: HTMLElement;
|
||||
private totalWork: number;
|
||||
private animationStopToken: ValueCallback;
|
||||
@@ -64,11 +63,6 @@ export class ProgressBar {
|
||||
|
||||
builder.div({ 'class': css_progress_bit }).on([DOM.EventType.ANIMATION_START, DOM.EventType.ANIMATION_END, DOM.EventType.ANIMATION_ITERATION], (e: Event) => {
|
||||
switch (e.type) {
|
||||
case DOM.EventType.ANIMATION_START:
|
||||
case DOM.EventType.ANIMATION_END:
|
||||
this.animationRunning = e.type === DOM.EventType.ANIMATION_START;
|
||||
break;
|
||||
|
||||
case DOM.EventType.ANIMATION_ITERATION:
|
||||
if (this.animationStopToken) {
|
||||
this.animationStopToken(null);
|
||||
|
||||
@@ -13,8 +13,8 @@ import paths = require('vs/base/common/paths');
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { BoundedMap } from 'vs/base/common/map';
|
||||
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
interface MapExtToMediaMimes {
|
||||
[index: string]: string;
|
||||
@@ -75,14 +75,19 @@ export interface IResourceDescriptor {
|
||||
name: string;
|
||||
size: number;
|
||||
etag: string;
|
||||
mime: string;
|
||||
}
|
||||
|
||||
// Chrome is caching images very aggressively and so we use the ETag information to find out if
|
||||
// we need to bypass the cache or not. We could always bypass the cache everytime we show the image
|
||||
// however that has very bad impact on memory consumption because each time the image gets shown,
|
||||
// memory grows (see also https://github.com/electron/electron/issues/6275)
|
||||
const IMAGE_RESOURCE_ETAG_CACHE = new BoundedMap<{ etag: string, src: string }>(100);
|
||||
const IMAGE_RESOURCE_ETAG_CACHE = new LRUCache<string, { etag: string, src: string }>(100);
|
||||
function imageSrc(descriptor: IResourceDescriptor): string {
|
||||
if (descriptor.resource.scheme === Schemas.data) {
|
||||
return descriptor.resource.toString(true /* skip encoding */);
|
||||
}
|
||||
|
||||
const src = descriptor.resource.toString();
|
||||
|
||||
let cached = IMAGE_RESOURCE_ETAG_CACHE.get(src);
|
||||
@@ -105,12 +110,12 @@ function imageSrc(descriptor: IResourceDescriptor): string {
|
||||
*/
|
||||
export class ResourceViewer {
|
||||
|
||||
private static KB = 1024;
|
||||
private static MB = ResourceViewer.KB * ResourceViewer.KB;
|
||||
private static GB = ResourceViewer.MB * ResourceViewer.KB;
|
||||
private static TB = ResourceViewer.GB * ResourceViewer.KB;
|
||||
private static readonly KB = 1024;
|
||||
private static readonly MB = ResourceViewer.KB * ResourceViewer.KB;
|
||||
private static readonly GB = ResourceViewer.MB * ResourceViewer.KB;
|
||||
private static readonly TB = ResourceViewer.GB * ResourceViewer.KB;
|
||||
|
||||
private static MAX_IMAGE_SIZE = ResourceViewer.MB; // showing images inline is memory intense, so we have a limit
|
||||
private static readonly MAX_IMAGE_SIZE = ResourceViewer.MB; // showing images inline is memory intense, so we have a limit
|
||||
|
||||
public static show(
|
||||
descriptor: IResourceDescriptor,
|
||||
@@ -119,23 +124,26 @@ export class ResourceViewer {
|
||||
openExternal: (uri: URI) => void,
|
||||
metadataClb?: (meta: string) => void
|
||||
): void {
|
||||
|
||||
// Ensure CSS class
|
||||
$(container).setClass('monaco-resource-viewer');
|
||||
|
||||
// Lookup media mime if any
|
||||
let mime: string;
|
||||
const ext = paths.extname(descriptor.resource.toString());
|
||||
if (ext) {
|
||||
mime = mapExtToMediaMimes[ext.toLowerCase()];
|
||||
let mime = descriptor.mime;
|
||||
if (!mime && descriptor.resource.scheme === Schemas.file) {
|
||||
const ext = paths.extname(descriptor.resource.toString());
|
||||
if (ext) {
|
||||
mime = mapExtToMediaMimes[ext.toLowerCase()];
|
||||
}
|
||||
}
|
||||
|
||||
if (!mime) {
|
||||
mime = mimes.MIME_BINARY;
|
||||
}
|
||||
|
||||
// Show Image inline
|
||||
// Show Image inline unless they are large
|
||||
if (mime.indexOf('image/') >= 0) {
|
||||
if (descriptor.size <= ResourceViewer.MAX_IMAGE_SIZE) {
|
||||
if (ResourceViewer.inlineImage(descriptor)) {
|
||||
$(container)
|
||||
.empty()
|
||||
.addClass('image')
|
||||
@@ -159,18 +167,21 @@ export class ResourceViewer {
|
||||
scrollbar.scanDomNode();
|
||||
});
|
||||
} else {
|
||||
$(container)
|
||||
const imageContainer = $(container)
|
||||
.empty()
|
||||
.p({
|
||||
text: nls.localize('largeImageError', "The image is too large to display in the editor. ")
|
||||
})
|
||||
.append($('a', {
|
||||
});
|
||||
|
||||
if (descriptor.resource.scheme !== Schemas.data) {
|
||||
imageContainer.append($('a', {
|
||||
role: 'button',
|
||||
class: 'open-external',
|
||||
text: nls.localize('resourceOpenExternalButton', "Open image using external program?")
|
||||
}).on(DOM.EventType.CLICK, (e) => {
|
||||
openExternal(descriptor.resource);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +201,26 @@ export class ResourceViewer {
|
||||
}
|
||||
}
|
||||
|
||||
private static inlineImage(descriptor: IResourceDescriptor): boolean {
|
||||
let skipInlineImage: boolean;
|
||||
|
||||
// Data URI
|
||||
if (descriptor.resource.scheme === Schemas.data) {
|
||||
const BASE64_MARKER = 'base64,';
|
||||
const base64MarkerIndex = descriptor.resource.path.indexOf(BASE64_MARKER);
|
||||
const hasData = base64MarkerIndex >= 0 && descriptor.resource.path.substring(base64MarkerIndex + BASE64_MARKER.length).length > 0;
|
||||
|
||||
skipInlineImage = !hasData || descriptor.size > ResourceViewer.MAX_IMAGE_SIZE || descriptor.resource.path.length > ResourceViewer.MAX_IMAGE_SIZE;
|
||||
}
|
||||
|
||||
// File URI
|
||||
else {
|
||||
skipInlineImage = typeof descriptor.size !== 'number' || descriptor.size > ResourceViewer.MAX_IMAGE_SIZE;
|
||||
}
|
||||
|
||||
return !skipInlineImage;
|
||||
}
|
||||
|
||||
private static formatSize(size: number): string {
|
||||
if (size < ResourceViewer.KB) {
|
||||
return nls.localize('sizeB', "{0}B", size);
|
||||
|
||||
@@ -12,8 +12,7 @@ import { isIPad } from 'vs/base/browser/browser';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import types = require('vs/base/common/types');
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { Gesture, EventType, GestureEvent } from 'vs/base/browser/touch';
|
||||
import { EventEmitter } from 'vs/base/common/eventEmitter';
|
||||
import { EventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
@@ -48,18 +47,21 @@ export enum Orientation {
|
||||
HORIZONTAL
|
||||
}
|
||||
|
||||
export class Sash extends EventEmitter {
|
||||
export class Sash {
|
||||
|
||||
private $e: Builder;
|
||||
private gesture: Gesture;
|
||||
private layoutProvider: ISashLayoutProvider;
|
||||
private isDisabled: boolean;
|
||||
private hidden: boolean;
|
||||
private orientation: Orientation;
|
||||
private size: number;
|
||||
|
||||
private _onDidStart = new Emitter<ISashEvent>();
|
||||
private _onDidChange = new Emitter<ISashEvent>();
|
||||
private _onDidReset = new Emitter<void>();
|
||||
private _onDidEnd = new Emitter<void>();
|
||||
|
||||
constructor(container: HTMLElement, layoutProvider: ISashLayoutProvider, options: ISashOptions = {}) {
|
||||
super();
|
||||
|
||||
this.$e = $('.monaco-sash').appendTo(container);
|
||||
|
||||
@@ -67,11 +69,10 @@ export class Sash extends EventEmitter {
|
||||
this.$e.addClass('mac');
|
||||
}
|
||||
|
||||
this.gesture = new Gesture(this.$e.getHTMLElement());
|
||||
|
||||
this.$e.on(DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => { this.onMouseDown(e); });
|
||||
this.$e.on(DOM.EventType.DBLCLICK, (e: MouseEvent) => { this.emit('reset', e); });
|
||||
this.$e.on(EventType.Start, (e: GestureEvent) => { this.onTouchStart(e); });
|
||||
this.$e.on(DOM.EventType.MOUSE_DOWN, (e) => { this.onMouseDown(e as MouseEvent); });
|
||||
this.$e.on(DOM.EventType.DBLCLICK, (e) => this._onDidReset.fire());
|
||||
Gesture.addTarget(this.$e.getHTMLElement());
|
||||
this.$e.on(EventType.Start, (e) => { this.onTouchStart(e as GestureEvent); });
|
||||
|
||||
this.size = options.baseSize || 5;
|
||||
|
||||
@@ -87,8 +88,20 @@ export class Sash extends EventEmitter {
|
||||
this.layoutProvider = layoutProvider;
|
||||
}
|
||||
|
||||
public getHTMLElement(): HTMLElement {
|
||||
return this.$e.getHTMLElement();
|
||||
public get onDidStart(): Event<ISashEvent> {
|
||||
return this._onDidStart.event;
|
||||
}
|
||||
|
||||
public get onDidChange(): Event<ISashEvent> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
public get onDidReset(): Event<void> {
|
||||
return this._onDidReset.event;
|
||||
}
|
||||
|
||||
public get onDidEnd(): Event<void> {
|
||||
return this._onDidEnd.event;
|
||||
}
|
||||
|
||||
public setOrientation(orientation: Orientation): void {
|
||||
@@ -136,17 +149,14 @@ export class Sash extends EventEmitter {
|
||||
};
|
||||
|
||||
this.$e.addClass('active');
|
||||
this.emit('start', startEvent);
|
||||
this._onDidStart.fire(startEvent);
|
||||
|
||||
let $window = $(window);
|
||||
let containerCSSClass = `${this.getOrientation()}-cursor-container${isMacintosh ? '-mac' : ''}`;
|
||||
|
||||
let lastCurrentX = startX;
|
||||
let lastCurrentY = startY;
|
||||
|
||||
$window.on('mousemove', (e: MouseEvent) => {
|
||||
$window.on('mousemove', (e) => {
|
||||
DOM.EventHelper.stop(e, false);
|
||||
let mouseMoveEvent = new StandardMouseEvent(e);
|
||||
let mouseMoveEvent = new StandardMouseEvent(e as MouseEvent);
|
||||
|
||||
let event: ISashEvent = {
|
||||
startX: startX,
|
||||
@@ -155,14 +165,11 @@ export class Sash extends EventEmitter {
|
||||
currentY: mouseMoveEvent.posy
|
||||
};
|
||||
|
||||
lastCurrentX = mouseMoveEvent.posx;
|
||||
lastCurrentY = mouseMoveEvent.posy;
|
||||
|
||||
this.emit('change', event);
|
||||
}).once('mouseup', (e: MouseEvent) => {
|
||||
this._onDidChange.fire(event);
|
||||
}).once('mouseup', (e) => {
|
||||
DOM.EventHelper.stop(e, false);
|
||||
this.$e.removeClass('active');
|
||||
this.emit('end');
|
||||
this._onDidEnd.fire();
|
||||
|
||||
$window.off('mousemove');
|
||||
document.body.classList.remove(containerCSSClass);
|
||||
@@ -184,32 +191,26 @@ export class Sash extends EventEmitter {
|
||||
let startX = event.pageX;
|
||||
let startY = event.pageY;
|
||||
|
||||
this.emit('start', {
|
||||
this._onDidStart.fire({
|
||||
startX: startX,
|
||||
currentX: startX,
|
||||
startY: startY,
|
||||
currentY: startY
|
||||
});
|
||||
|
||||
let lastCurrentX = startX;
|
||||
let lastCurrentY = startY;
|
||||
|
||||
listeners.push(DOM.addDisposableListener(this.$e.getHTMLElement(), EventType.Change, (event: GestureEvent) => {
|
||||
if (types.isNumber(event.pageX) && types.isNumber(event.pageY)) {
|
||||
this.emit('change', {
|
||||
this._onDidChange.fire({
|
||||
startX: startX,
|
||||
currentX: event.pageX,
|
||||
startY: startY,
|
||||
currentY: event.pageY
|
||||
});
|
||||
|
||||
lastCurrentX = event.pageX;
|
||||
lastCurrentY = event.pageY;
|
||||
}
|
||||
}));
|
||||
|
||||
listeners.push(DOM.addDisposableListener(this.$e.getHTMLElement(), EventType.End, (event: GestureEvent) => {
|
||||
this.emit('end');
|
||||
this._onDidEnd.fire();
|
||||
dispose(listeners);
|
||||
}));
|
||||
}
|
||||
@@ -277,8 +278,6 @@ export class Sash extends EventEmitter {
|
||||
this.$e.destroy();
|
||||
this.$e = null;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,10 +301,10 @@ export class VSash extends Disposable implements IVerticalSashLayoutProvider {
|
||||
this.ratio = 0.5;
|
||||
this.sash = new Sash(container, this);
|
||||
|
||||
this._register(this.sash.addListener('start', () => this.onSashDragStart()));
|
||||
this._register(this.sash.addListener('change', (e: ISashEvent) => this.onSashDrag(e)));
|
||||
this._register(this.sash.addListener('end', () => this.onSashDragEnd()));
|
||||
this._register(this.sash.addListener('reset', () => this.onSashReset()));
|
||||
this._register(this.sash.onDidStart(() => this.onSashDragStart()));
|
||||
this._register(this.sash.onDidChange((e: ISashEvent) => this.onSashDrag(e)));
|
||||
this._register(this.sash.onDidEnd(() => this.onSashDragEnd()));
|
||||
this._register(this.sash.onDidReset(() => this.onSashReset()));
|
||||
}
|
||||
|
||||
public getVerticalSashTop(): number {
|
||||
@@ -344,7 +343,7 @@ export class VSash extends Disposable implements IVerticalSashLayoutProvider {
|
||||
}
|
||||
|
||||
private onSashReset(): void {
|
||||
this.ratio = 0.5;
|
||||
this.compute(0.5);
|
||||
this._onPositionChange.fire(this.position);
|
||||
this.sash.layout();
|
||||
}
|
||||
|
||||
@@ -187,10 +187,6 @@ export abstract class AbstractScrollbar extends Widget {
|
||||
}
|
||||
}
|
||||
|
||||
public delegateSliderMouseDown(e: ISimplifiedMouseEvent, onDragFinished: () => void): void {
|
||||
this._sliderMouseDown(e, onDragFinished);
|
||||
}
|
||||
|
||||
private _onMouseDown(e: IMouseEvent): void {
|
||||
let offsetX: number;
|
||||
let offsetY: number;
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Scrollable, ScrollEvent, ScrollbarVisibility, INewScrollDimensions, ISc
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { TimeoutTimer } from 'vs/base/common/async';
|
||||
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
|
||||
import { ScrollbarHost, ISimplifiedMouseEvent } from 'vs/base/browser/ui/scrollbar/abstractScrollbar';
|
||||
import { ScrollbarHost } from 'vs/base/browser/ui/scrollbar/abstractScrollbar';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
const HIDE_TIMEOUT = 500;
|
||||
@@ -45,7 +45,7 @@ class MouseWheelClassifierItem {
|
||||
|
||||
export class MouseWheelClassifier {
|
||||
|
||||
public static INSTANCE = new MouseWheelClassifier();
|
||||
public static readonly INSTANCE = new MouseWheelClassifier();
|
||||
|
||||
private readonly _capacity: number;
|
||||
private _memory: MouseWheelClassifierItem[];
|
||||
@@ -250,14 +250,6 @@ export abstract class AbstractScrollableElement extends Widget {
|
||||
this._verticalScrollbar.delegateMouseDown(browserEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate a mouse down event to the vertical scrollbar (directly to the slider!).
|
||||
* This is to help with clicking somewhere else and having the scrollbar react.
|
||||
*/
|
||||
public delegateSliderMouseDown(e: ISimplifiedMouseEvent, onDragFinished: () => void): void {
|
||||
this._verticalScrollbar.delegateSliderMouseDown(e, onDragFinished);
|
||||
}
|
||||
|
||||
public getScrollDimensions(): IScrollDimensions {
|
||||
return this._scrollable.getScrollDimensions();
|
||||
}
|
||||
|
||||
@@ -189,10 +189,6 @@ export class ScrollbarState {
|
||||
return this._computedSliderPosition;
|
||||
}
|
||||
|
||||
public getSliderCenter(): number {
|
||||
return (this._computedSliderPosition + this._computedSliderSize / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a desired `scrollPosition` such that `offset` ends up in the center of the slider.
|
||||
* `offset` is based on the same coordinate system as the `sliderPosition`.
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { clone } from 'vs/base/common/objects';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
|
||||
export interface ISelectBoxStyles {
|
||||
selectBackground?: Color;
|
||||
@@ -36,18 +36,15 @@ export class SelectBox extends Widget {
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected selectElement: HTMLSelectElement;
|
||||
protected options: string[];
|
||||
|
||||
private selected: number;
|
||||
private container: HTMLElement;
|
||||
private _onDidSelect: Emitter<ISelectData>;
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected selectBackground: Color;
|
||||
protected selectForeground: Color;
|
||||
protected selectBorder: Color;
|
||||
|
||||
constructor(options: string[], selected: number, styles: ISelectBoxStyles = clone(defaultStyles)) {
|
||||
constructor(options: string[], selected: number, styles: ISelectBoxStyles = deepClone(defaultStyles)) {
|
||||
super();
|
||||
|
||||
this.selectElement = document.createElement('select');
|
||||
@@ -117,7 +114,6 @@ export class SelectBox extends Widget {
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
this.container = container;
|
||||
dom.addClass(container, 'select-container');
|
||||
container.appendChild(this.selectElement);
|
||||
this.setOptions(this.options, this.selected);
|
||||
@@ -159,4 +155,4 @@ export class SelectBox extends Widget {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export interface IPanelStyles {
|
||||
|
||||
export abstract class Panel implements IView {
|
||||
|
||||
private static HEADER_SIZE = 22;
|
||||
private static readonly HEADER_SIZE = 22;
|
||||
|
||||
protected _expanded: boolean;
|
||||
private expandedSize: number | undefined = undefined;
|
||||
@@ -146,8 +146,8 @@ export abstract class Panel implements IView {
|
||||
this.renderHeader(this.header);
|
||||
|
||||
const focusTracker = trackFocus(this.header);
|
||||
focusTracker.addFocusListener(() => addClass(this.header, 'focused'));
|
||||
focusTracker.addBlurListener(() => removeClass(this.header, 'focused'));
|
||||
focusTracker.onDidFocus(() => addClass(this.header, 'focused'));
|
||||
focusTracker.onDidBlur(() => removeClass(this.header, 'focused'));
|
||||
|
||||
this.updateHeader();
|
||||
|
||||
@@ -226,7 +226,7 @@ interface IDndContext {
|
||||
|
||||
class PanelDraggable implements IDisposable {
|
||||
|
||||
private static DefaultDragOverBackgroundColor = new Color(new RGBA(128, 128, 128, 0.5));
|
||||
private static readonly DefaultDragOverBackgroundColor = new Color(new RGBA(128, 128, 128, 0.5));
|
||||
|
||||
// see https://github.com/Microsoft/vscode/issues/14470
|
||||
private dragOverCounter = 0;
|
||||
@@ -338,7 +338,7 @@ export class PanelView implements IDisposable {
|
||||
|
||||
readonly onDidSashChange: Event<void>;
|
||||
|
||||
constructor(private container: HTMLElement, options: IPanelViewOptions = {}) {
|
||||
constructor(container: HTMLElement, options: IPanelViewOptions = {}) {
|
||||
this.dnd = !!options.dnd;
|
||||
this.el = append(container, $('.monaco-panel-view'));
|
||||
this.splitview = new SplitView(this.el);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import 'vs/css!./splitview';
|
||||
import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import Event, { fromEventEmitter, mapEvent, Emitter } from 'vs/base/common/event';
|
||||
import Event, { mapEvent, Emitter } from 'vs/base/common/event';
|
||||
import types = require('vs/base/common/types');
|
||||
import dom = require('vs/base/browser/dom');
|
||||
import { clamp } from 'vs/base/common/numbers';
|
||||
@@ -59,6 +59,25 @@ enum State {
|
||||
Busy
|
||||
}
|
||||
|
||||
function pushToEnd<T>(arr: T[], value: T): T[] {
|
||||
let didFindValue = false;
|
||||
|
||||
const result = arr.filter(v => {
|
||||
if (v === value) {
|
||||
didFindValue = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (didFindValue) {
|
||||
result.push(value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export class SplitView implements IDisposable {
|
||||
|
||||
private orientation: Orientation;
|
||||
@@ -76,8 +95,7 @@ export class SplitView implements IDisposable {
|
||||
get length(): number {
|
||||
return this.viewItems.length;
|
||||
}
|
||||
|
||||
constructor(private container: HTMLElement, options: ISplitViewOptions = {}) {
|
||||
constructor(container: HTMLElement, options: ISplitViewOptions = {}) {
|
||||
this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
|
||||
|
||||
this.el = document.createElement('div');
|
||||
@@ -128,11 +146,11 @@ export class SplitView implements IDisposable {
|
||||
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY })
|
||||
: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX });
|
||||
|
||||
const onStart = mapEvent(fromEventEmitter<IBaseSashEvent>(sash, 'start'), sashEventMapper);
|
||||
const onStart = mapEvent(sash.onDidStart, sashEventMapper);
|
||||
const onStartDisposable = onStart(this.onSashStart, this);
|
||||
const onChange = mapEvent(fromEventEmitter<IBaseSashEvent>(sash, 'change'), sashEventMapper);
|
||||
const onChange = mapEvent(sash.onDidChange, sashEventMapper);
|
||||
const onSashChangeDisposable = onChange(this.onSashChange, this);
|
||||
const onEnd = mapEvent<IBaseSashEvent, void>(fromEventEmitter<IBaseSashEvent>(sash, 'end'), () => null);
|
||||
const onEnd = mapEvent<void, void>(sash.onDidEnd, () => null);
|
||||
const onEndDisposable = onEnd(() => this._onDidSashChange.fire());
|
||||
|
||||
const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, onEndDisposable, sash]);
|
||||
@@ -204,9 +222,9 @@ export class SplitView implements IDisposable {
|
||||
this.state = State.Idle;
|
||||
}
|
||||
|
||||
private relayout(): void {
|
||||
private relayout(lowPriorityIndex?: number): void {
|
||||
const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
this.resize(this.viewItems.length - 1, this.contentSize - contentSize);
|
||||
this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex);
|
||||
}
|
||||
|
||||
layout(size: number): void {
|
||||
@@ -250,7 +268,7 @@ export class SplitView implements IDisposable {
|
||||
size = typeof size === 'number' ? size : item.size;
|
||||
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
||||
item.size = size;
|
||||
this.relayout();
|
||||
this.relayout(index);
|
||||
}
|
||||
|
||||
resizeView(index: number, size: number): void {
|
||||
@@ -299,21 +317,28 @@ export class SplitView implements IDisposable {
|
||||
return this.viewItems[index].size;
|
||||
}
|
||||
|
||||
private resize(index: number, delta: number, sizes = this.viewItems.map(i => i.size)): void {
|
||||
private resize(index: number, delta: number, sizes = this.viewItems.map(i => i.size), lowPriorityIndex?: number): void {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (delta !== 0) {
|
||||
const upIndexes = range(index, -1);
|
||||
const up = upIndexes.map(i => this.viewItems[i]);
|
||||
let upIndexes = range(index, -1);
|
||||
let downIndexes = range(index + 1, this.viewItems.length);
|
||||
|
||||
if (typeof lowPriorityIndex === 'number') {
|
||||
upIndexes = pushToEnd(upIndexes, lowPriorityIndex);
|
||||
downIndexes = pushToEnd(downIndexes, lowPriorityIndex);
|
||||
}
|
||||
|
||||
const upItems = upIndexes.map(i => this.viewItems[i]);
|
||||
const upSizes = upIndexes.map(i => sizes[i]);
|
||||
const downIndexes = range(index + 1, this.viewItems.length);
|
||||
const down = downIndexes.map(i => this.viewItems[i]);
|
||||
|
||||
const downItems = downIndexes.map(i => this.viewItems[i]);
|
||||
const downSizes = downIndexes.map(i => sizes[i]);
|
||||
|
||||
for (let i = 0, deltaUp = delta; deltaUp !== 0 && i < up.length; i++) {
|
||||
const item = up[i];
|
||||
for (let i = 0, deltaUp = delta; deltaUp !== 0 && i < upItems.length; i++) {
|
||||
const item = upItems[i];
|
||||
const size = clamp(upSizes[i] + deltaUp, item.view.minimumSize, item.view.maximumSize);
|
||||
const viewDelta = size - upSizes[i];
|
||||
|
||||
@@ -321,8 +346,8 @@ export class SplitView implements IDisposable {
|
||||
item.size = size;
|
||||
}
|
||||
|
||||
for (let i = 0, deltaDown = delta; deltaDown !== 0 && i < down.length; i++) {
|
||||
const item = down[i];
|
||||
for (let i = 0, deltaDown = delta; deltaDown !== 0 && i < downItems.length; i++) {
|
||||
const item = downItems[i];
|
||||
const size = clamp(downSizes[i] - deltaDown, item.view.minimumSize, item.view.maximumSize);
|
||||
const viewDelta = size - downSizes[i];
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ export class ToolBar {
|
||||
|
||||
class ToggleMenuAction extends Action {
|
||||
|
||||
public static ID = 'toolbar.toggle.more';
|
||||
public static readonly ID = 'toolbar.toggle.more';
|
||||
|
||||
private _menuActions: IAction[];
|
||||
private toggleDropdownMenu: () => void;
|
||||
@@ -186,7 +186,6 @@ class ToggleMenuAction extends Action {
|
||||
export class DropdownMenuActionItem extends BaseActionItem {
|
||||
private menuActionsOrProvider: any;
|
||||
private dropdownMenu: DropdownMenu;
|
||||
private toUnbind: IDisposable;
|
||||
private contextMenuProvider: IContextMenuProvider;
|
||||
private actionItemProvider: IActionItemProvider;
|
||||
private keybindings: (action: IAction) => ResolvedKeybinding;
|
||||
@@ -240,9 +239,6 @@ export class DropdownMenuActionItem extends BaseActionItem {
|
||||
getKeyBinding: this.keybindings,
|
||||
context: this._context
|
||||
};
|
||||
|
||||
// Reemit events for running actions
|
||||
this.toUnbind = this.addEmitter(this.dropdownMenu);
|
||||
}
|
||||
|
||||
public setActionContext(newContext: any): void {
|
||||
@@ -260,7 +256,6 @@ export class DropdownMenuActionItem extends BaseActionItem {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.toUnbind.dispose();
|
||||
this.dropdownMenu.dispose();
|
||||
|
||||
super.dispose();
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IEventEmitter, EventEmitter } from 'vs/base/common/eventEmitter';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as Events from 'vs/base/common/events';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface ITelemetryData {
|
||||
@@ -27,11 +25,13 @@ export interface IAction extends IDisposable {
|
||||
run(event?: any): TPromise<any>;
|
||||
}
|
||||
|
||||
export interface IActionRunner extends IEventEmitter {
|
||||
export interface IActionRunner extends IDisposable {
|
||||
run(action: IAction, context?: any): TPromise<any>;
|
||||
onDidRun: Event<IRunEvent>;
|
||||
onDidBeforeRun: Event<IRunEvent>;
|
||||
}
|
||||
|
||||
export interface IActionItem extends IEventEmitter {
|
||||
export interface IActionItem {
|
||||
actionRunner: IActionRunner;
|
||||
setActionContext(context: any): void;
|
||||
render(element: any /* HTMLElement */): void;
|
||||
@@ -41,33 +41,6 @@ export interface IActionItem extends IEventEmitter {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided object is compatible
|
||||
* with the IAction interface.
|
||||
* @param thing an object
|
||||
*/
|
||||
export function isAction(thing: any): thing is IAction {
|
||||
if (!thing) {
|
||||
return false;
|
||||
} else if (thing instanceof Action) {
|
||||
return true;
|
||||
} else if (typeof thing.id !== 'string') {
|
||||
return false;
|
||||
} else if (typeof thing.label !== 'string') {
|
||||
return false;
|
||||
} else if (typeof thing.class !== 'string') {
|
||||
return false;
|
||||
} else if (typeof thing.enabled !== 'boolean') {
|
||||
return false;
|
||||
} else if (typeof thing.checked !== 'boolean') {
|
||||
return false;
|
||||
} else if (typeof thing.run !== 'function') {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IActionChangeEvent {
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
@@ -222,23 +195,44 @@ export interface IRunEvent {
|
||||
error?: any;
|
||||
}
|
||||
|
||||
export class ActionRunner extends EventEmitter implements IActionRunner {
|
||||
export class ActionRunner implements IActionRunner {
|
||||
|
||||
private _onDidBeforeRun = new Emitter<IRunEvent>();
|
||||
private _onDidRun = new Emitter<IRunEvent>();
|
||||
|
||||
public get onDidRun(): Event<IRunEvent> {
|
||||
return this._onDidRun.event;
|
||||
}
|
||||
|
||||
public get onDidBeforeRun(): Event<IRunEvent> {
|
||||
return this._onDidBeforeRun.event;
|
||||
}
|
||||
|
||||
public run(action: IAction, context?: any): TPromise<any> {
|
||||
if (!action.enabled) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
this.emit(Events.EventType.BEFORE_RUN, { action: action });
|
||||
this._onDidBeforeRun.fire({ action: action });
|
||||
|
||||
return this.runAction(action, context).then((result: any) => {
|
||||
this.emit(Events.EventType.RUN, <IRunEvent>{ action: action, result: result });
|
||||
this._onDidRun.fire({ action: action, result: result });
|
||||
}, (error: any) => {
|
||||
this.emit(Events.EventType.RUN, <IRunEvent>{ action: action, error: error });
|
||||
this._onDidRun.fire({ action: action, error: error });
|
||||
});
|
||||
}
|
||||
|
||||
protected runAction(action: IAction, context?: any): TPromise<any> {
|
||||
return TPromise.as(context ? action.run(context) : action.run());
|
||||
const res = context ? action.run(context) : action.run();
|
||||
|
||||
if (TPromise.is(res)) {
|
||||
return res;
|
||||
}
|
||||
|
||||
return TPromise.wrap(res);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ISplice } from 'vs/base/common/sequence';
|
||||
|
||||
/**
|
||||
* Returns the last element of an array.
|
||||
@@ -124,20 +125,18 @@ export function groupBy<T>(data: T[], compare: (a: T, b: T) => number): T[][] {
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface Splice<T> {
|
||||
start: number;
|
||||
interface IMutableSplice<T> extends ISplice<T> {
|
||||
deleteCount: number;
|
||||
inserted: 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): Splice<T>[] {
|
||||
const result: Splice<T>[] = [];
|
||||
export function sortedDiff<T>(before: T[], after: T[], compare: (a: T, b: T) => number): ISplice<T>[] {
|
||||
const result: IMutableSplice<T>[] = [];
|
||||
|
||||
function pushSplice(start: number, deleteCount: number, inserted: T[]): void {
|
||||
if (deleteCount === 0 && inserted.length === 0) {
|
||||
function pushSplice(start: number, deleteCount: number, toInsert: T[]): void {
|
||||
if (deleteCount === 0 && toInsert.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -145,9 +144,9 @@ export function sortedDiff<T>(before: T[], after: T[], compare: (a: T, b: T) =>
|
||||
|
||||
if (latest && latest.start + latest.deleteCount === start) {
|
||||
latest.deleteCount += deleteCount;
|
||||
latest.inserted.push(...inserted);
|
||||
latest.toInsert.push(...toInsert);
|
||||
} else {
|
||||
result.push({ start, deleteCount, inserted });
|
||||
result.push({ start, deleteCount, toInsert });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +198,7 @@ export function delta<T>(before: T[], after: T[], compare: (a: T, b: T) => numbe
|
||||
|
||||
for (const splice of splices) {
|
||||
removed.push(...before.slice(splice.start, splice.start + splice.deleteCount));
|
||||
added.push(...splice.inserted);
|
||||
added.push(...splice.toInsert);
|
||||
}
|
||||
|
||||
return { removed, added };
|
||||
@@ -397,21 +396,6 @@ export function range(arg: number, to?: number): number[] {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function weave<T>(a: T[], b: T[]): T[] {
|
||||
const result: T[] = [];
|
||||
let ai = 0, bi = 0;
|
||||
|
||||
for (let i = 0, length = a.length + b.length; i < length; i++) {
|
||||
if ((i % 2 === 0 && ai < a.length) || bi >= b.length) {
|
||||
result.push(a[ai++]);
|
||||
} else {
|
||||
result.push(b[bi++]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function fill<T>(num: number, valueFn: () => T, arr: T[] = []): T[] {
|
||||
for (let i = 0; i < num; i++) {
|
||||
arr[i] = valueFn();
|
||||
|
||||
+63
-50
@@ -146,7 +146,7 @@ export class Throttler {
|
||||
// TODO@Joao: can the previous throttler be replaced with this?
|
||||
export class SimpleThrottler {
|
||||
|
||||
private current = TPromise.as<any>(null);
|
||||
private current = TPromise.wrap<any>(null);
|
||||
|
||||
queue<T>(promiseTask: ITask<TPromise<T>>): TPromise<T> {
|
||||
return this.current = this.current.then(() => promiseTask());
|
||||
@@ -261,56 +261,34 @@ export class ThrottledDelayer<T> extends Delayer<TPromise<T>> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to the ThrottledDelayer, except it also guarantees that the promise
|
||||
* factory doesn't get called more often than every `minimumPeriod` milliseconds.
|
||||
* A barrier that is initially closed and then becomes opened permanently.
|
||||
*/
|
||||
export class PeriodThrottledDelayer<T> extends ThrottledDelayer<T> {
|
||||
export class Barrier {
|
||||
|
||||
private minimumPeriod: number;
|
||||
private periodThrottler: Throttler;
|
||||
|
||||
constructor(defaultDelay: number, minimumPeriod: number = 0) {
|
||||
super(defaultDelay);
|
||||
|
||||
this.minimumPeriod = minimumPeriod;
|
||||
this.periodThrottler = new Throttler();
|
||||
}
|
||||
|
||||
trigger(promiseFactory: ITask<TPromise<T>>, delay?: number): Promise {
|
||||
return super.trigger(() => {
|
||||
return this.periodThrottler.queue(() => {
|
||||
return Promise.join([
|
||||
TPromise.timeout(this.minimumPeriod),
|
||||
promiseFactory()
|
||||
]).then(r => r[1]);
|
||||
});
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
|
||||
export class PromiseSource<T> {
|
||||
|
||||
private _value: TPromise<T>;
|
||||
private _completeCallback: Function;
|
||||
private _errorCallback: Function;
|
||||
private _isOpen: boolean;
|
||||
private _promise: TPromise<boolean>;
|
||||
private _completePromise: (v: boolean) => void;
|
||||
|
||||
constructor() {
|
||||
this._value = new TPromise<T>((c, e) => {
|
||||
this._completeCallback = c;
|
||||
this._errorCallback = e;
|
||||
this._isOpen = false;
|
||||
this._promise = new TPromise<boolean>((c, e, p) => {
|
||||
this._completePromise = c;
|
||||
}, () => {
|
||||
console.warn('You should really not try to cancel this ready promise!');
|
||||
});
|
||||
}
|
||||
|
||||
get value(): TPromise<T> {
|
||||
return this._value;
|
||||
isOpen(): boolean {
|
||||
return this._isOpen;
|
||||
}
|
||||
|
||||
complete(value?: T): void {
|
||||
this._completeCallback(value);
|
||||
open(): void {
|
||||
this._isOpen = true;
|
||||
this._completePromise(true);
|
||||
}
|
||||
|
||||
error(err?: any): void {
|
||||
this._errorCallback(err);
|
||||
wait(): TPromise<boolean> {
|
||||
return this._promise;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,7 +488,7 @@ export class Queue<T> extends Limiter<T> {
|
||||
* A helper to organize queues per resource. The ResourceQueue makes sure to manage queues per resource
|
||||
* by disposing them once the queue is empty.
|
||||
*/
|
||||
export class ResourceQueue<T> {
|
||||
export class ResourceQueue {
|
||||
private queues: { [path: string]: Queue<void> };
|
||||
|
||||
constructor() {
|
||||
@@ -639,13 +617,6 @@ export class RunOnceScheduler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace runner. If there is a runner already scheduled, the new runner will be called.
|
||||
*/
|
||||
setRunner(runner: () => void): void {
|
||||
this.runner = runner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel previous runner (if any) & schedule a new runner.
|
||||
*/
|
||||
@@ -672,11 +643,53 @@ export class RunOnceScheduler {
|
||||
export function nfcall(fn: Function, ...args: any[]): Promise;
|
||||
export function nfcall<T>(fn: Function, ...args: any[]): TPromise<T>;
|
||||
export function nfcall(fn: Function, ...args: any[]): any {
|
||||
return new TPromise((c, e) => fn(...args, (err, result) => err ? e(err) : c(result)), () => null);
|
||||
return new TPromise((c, e) => fn(...args, (err: any, result: any) => err ? e(err) : c(result)), () => null);
|
||||
}
|
||||
|
||||
export function ninvoke(thisArg: any, fn: Function, ...args: any[]): Promise;
|
||||
export function ninvoke<T>(thisArg: any, fn: Function, ...args: any[]): TPromise<T>;
|
||||
export function ninvoke(thisArg: any, fn: Function, ...args: any[]): any {
|
||||
return new TPromise((c, e) => fn.call(thisArg, ...args, (err, result) => err ? e(err) : c(result)), () => null);
|
||||
return new TPromise((c, e) => fn.call(thisArg, ...args, (err: any, result: any) => err ? e(err) : c(result)), () => null);
|
||||
}
|
||||
|
||||
/**
|
||||
* An emitter that will ignore any events that occur during a specific code
|
||||
* execution triggered via throttle() until the promise has finished (either
|
||||
* successfully or with an error). Only after the promise has finished, the
|
||||
* last event that was fired during the operation will get emitted.
|
||||
*
|
||||
*/
|
||||
export class ThrottledEmitter<T> extends Emitter<T> {
|
||||
private suspended: boolean;
|
||||
|
||||
private lastEvent: T;
|
||||
private hasLastEvent: boolean;
|
||||
|
||||
public throttle<C>(promise: TPromise<C>): TPromise<C> {
|
||||
this.suspended = true;
|
||||
|
||||
return always(promise, () => this.resume());
|
||||
}
|
||||
|
||||
public fire(event?: T): any {
|
||||
if (this.suspended) {
|
||||
this.lastEvent = event;
|
||||
this.hasLastEvent = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return super.fire(event);
|
||||
}
|
||||
|
||||
private resume(): void {
|
||||
this.suspended = false;
|
||||
|
||||
if (this.hasLastEvent) {
|
||||
this.fire(this.lastEvent);
|
||||
}
|
||||
|
||||
this.hasLastEvent = false;
|
||||
this.lastEvent = void 0;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
|
||||
export default class CallbackList {
|
||||
|
||||
private _callbacks: LinkedList<[Function, any]>;
|
||||
|
||||
public add(callback: Function, context: any = null, bucket?: IDisposable[]): () => void {
|
||||
if (!this._callbacks) {
|
||||
this._callbacks = new LinkedList<[Function, any]>();
|
||||
}
|
||||
const remove = this._callbacks.push([callback, context]);
|
||||
if (Array.isArray(bucket)) {
|
||||
bucket.push({ dispose: remove });
|
||||
}
|
||||
return remove;
|
||||
}
|
||||
|
||||
public invoke(...args: any[]): any[] {
|
||||
if (!this._callbacks) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const ret: any[] = [];
|
||||
const elements = this._callbacks.toArray();
|
||||
|
||||
for (const [callback, context] of elements) {
|
||||
try {
|
||||
ret.push(callback.apply(context, args));
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public entries(): [Function, any][] {
|
||||
if (!this._callbacks) {
|
||||
return [];
|
||||
}
|
||||
return this._callbacks
|
||||
? this._callbacks.toArray()
|
||||
: [];
|
||||
}
|
||||
|
||||
public isEmpty(): boolean {
|
||||
return !this._callbacks || this._callbacks.isEmpty();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._callbacks = undefined;
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export function values<T>(from: IStringDictionary<T> | INumberDictionary<T>): T[
|
||||
const result: T[] = [];
|
||||
for (let key in from) {
|
||||
if (hasOwnProperty.call(from, key)) {
|
||||
result.push(from[key]);
|
||||
result.push((from as any)[key]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -54,8 +54,8 @@ export function size<T>(from: IStringDictionary<T> | INumberDictionary<T>): numb
|
||||
export function forEach<T>(from: IStringDictionary<T> | INumberDictionary<T>, callback: (entry: { key: any; value: T; }, remove: Function) => any): void {
|
||||
for (let key in from) {
|
||||
if (hasOwnProperty.call(from, key)) {
|
||||
const result = callback({ key: key, value: from[key] }, function () {
|
||||
delete from[key];
|
||||
const result = callback({ key: key, value: (from as any)[key] }, function () {
|
||||
delete (from as any)[key];
|
||||
});
|
||||
if (result === false) {
|
||||
return;
|
||||
@@ -72,7 +72,7 @@ export function remove<T>(from: IStringDictionary<T> | INumberDictionary<T>, key
|
||||
if (!hasOwnProperty.call(from, key)) {
|
||||
return false;
|
||||
}
|
||||
delete from[key];
|
||||
delete (from as any)[key];
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -214,7 +214,7 @@ export class HSVA {
|
||||
m = ((r - g) / delta) + 4;
|
||||
}
|
||||
|
||||
return new HSVA(m * 60, s, cmax, rgba.a);
|
||||
return new HSVA(Math.round(m * 60), s, cmax, rgba.a);
|
||||
}
|
||||
|
||||
// from http://www.rapidtables.com/convert/color/hsv-to-rgb.htm
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
function pad(number: number): string {
|
||||
if (number < 10) {
|
||||
return '0' + number;
|
||||
}
|
||||
|
||||
return String(number);
|
||||
}
|
||||
|
||||
export function toLocalISOString(date: Date): string {
|
||||
return date.getFullYear() +
|
||||
'-' + pad(date.getMonth() + 1) +
|
||||
'-' + pad(date.getDate()) +
|
||||
'T' + pad(date.getHours()) +
|
||||
':' + pad(date.getMinutes()) +
|
||||
':' + pad(date.getSeconds()) +
|
||||
'.' + (date.getMilliseconds() / 1000).toFixed(3).slice(2, 5) +
|
||||
'Z';
|
||||
}
|
||||
@@ -62,7 +62,7 @@ export function debounce(delay: number): Function {
|
||||
return createDecorator((fn, key) => {
|
||||
const timerKey = `$debounce$${key}`;
|
||||
|
||||
return function (...args: any[]) {
|
||||
return function (this: any, ...args: any[]) {
|
||||
clearTimeout(this[timerKey]);
|
||||
this[timerKey] = setTimeout(() => fn.apply(this, args), delay);
|
||||
};
|
||||
|
||||
@@ -95,7 +95,7 @@ export class MyArray {
|
||||
// LcsDiff.cs
|
||||
//
|
||||
// An implementation of the difference algorithm described in
|
||||
// "An O(ND) Difference Algorithm and its letiations" by Eugene W. Myers
|
||||
// "An O(ND) Difference Algorithm and its variations" by Eugene W. Myers
|
||||
//
|
||||
// Copyright (C) 2008 Microsoft Corporation @minifier_do_not_preserve
|
||||
//*****************************************************************************
|
||||
@@ -215,7 +215,7 @@ const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
/**
|
||||
* An implementation of the difference algorithm described in
|
||||
* "An O(ND) Difference Algorithm and its letiations" by Eugene W. Myers
|
||||
* "An O(ND) Difference Algorithm and its variations" by Eugene W. Myers
|
||||
*/
|
||||
export class LcsDiff {
|
||||
|
||||
|
||||
@@ -57,8 +57,6 @@ export class LcsDiff2 {
|
||||
private ids_for_x: number[];
|
||||
private ids_for_y: number[];
|
||||
|
||||
private hashFunc: IHashFunction;
|
||||
|
||||
private resultX: boolean[];
|
||||
private resultY: boolean[];
|
||||
private forwardPrev: number[];
|
||||
@@ -72,14 +70,6 @@ export class LcsDiff2 {
|
||||
this.ids_for_x = [];
|
||||
this.ids_for_y = [];
|
||||
|
||||
if (hashFunc) {
|
||||
this.hashFunc = hashFunc;
|
||||
} else {
|
||||
this.hashFunc = function (sequence, index) {
|
||||
return sequence[index];
|
||||
};
|
||||
}
|
||||
|
||||
this.resultX = [];
|
||||
this.resultY = [];
|
||||
this.forwardPrev = [];
|
||||
|
||||
@@ -4,12 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export const DifferenceType = {
|
||||
Add: 0,
|
||||
Remove: 1,
|
||||
Change: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents information about a specific difference between two sequences.
|
||||
*/
|
||||
@@ -51,19 +45,6 @@ export class DiffChange {
|
||||
this.modifiedLength = modifiedLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of difference.
|
||||
*/
|
||||
public getChangeType() {
|
||||
if (this.originalLength === 0) {
|
||||
return DifferenceType.Add;
|
||||
} else if (this.modifiedLength === 0) {
|
||||
return DifferenceType.Remove;
|
||||
} else {
|
||||
return DifferenceType.Change;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The end point (exclusive) of the change in the original sequence.
|
||||
*/
|
||||
|
||||
@@ -5,156 +5,8 @@
|
||||
'use strict';
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import objects = require('vs/base/common/objects');
|
||||
import types = require('vs/base/common/types');
|
||||
import arrays = require('vs/base/common/arrays');
|
||||
import strings = require('vs/base/common/strings');
|
||||
|
||||
export interface IXHRResponse {
|
||||
responseText: string;
|
||||
status: number;
|
||||
|
||||
readyState: number;
|
||||
getResponseHeader: (header: string) => string;
|
||||
}
|
||||
|
||||
export interface IConnectionErrorData {
|
||||
status: number;
|
||||
statusText?: string;
|
||||
responseText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base class for all connection errors originating from XHR requests.
|
||||
*/
|
||||
export class ConnectionError implements Error {
|
||||
public status: number;
|
||||
public statusText: string;
|
||||
public responseText: string;
|
||||
public errorMessage: string;
|
||||
public errorCode: string;
|
||||
public errorObject: any;
|
||||
public name: string;
|
||||
|
||||
constructor(mixin: IConnectionErrorData);
|
||||
constructor(request: IXHRResponse);
|
||||
constructor(arg: any) {
|
||||
this.status = arg.status;
|
||||
this.statusText = arg.statusText;
|
||||
this.name = 'ConnectionError';
|
||||
|
||||
try {
|
||||
this.responseText = arg.responseText;
|
||||
} catch (e) {
|
||||
this.responseText = '';
|
||||
}
|
||||
|
||||
this.errorMessage = null;
|
||||
this.errorCode = null;
|
||||
this.errorObject = null;
|
||||
|
||||
if (this.responseText) {
|
||||
try {
|
||||
let errorObj = JSON.parse(this.responseText);
|
||||
this.errorMessage = errorObj.message;
|
||||
this.errorCode = errorObj.code;
|
||||
this.errorObject = errorObj;
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get message(): string {
|
||||
return this.connectionErrorToMessage(this, false);
|
||||
}
|
||||
|
||||
public get verboseMessage(): string {
|
||||
return this.connectionErrorToMessage(this, true);
|
||||
}
|
||||
|
||||
private connectionErrorDetailsToMessage(error: ConnectionError, verbose: boolean): string {
|
||||
let errorCode = error.errorCode;
|
||||
let errorMessage = error.errorMessage;
|
||||
|
||||
if (errorCode !== null && errorMessage !== null) {
|
||||
return nls.localize(
|
||||
{
|
||||
key: 'message',
|
||||
comment: [
|
||||
'{0} represents the error message',
|
||||
'{1} represents the error code'
|
||||
]
|
||||
},
|
||||
"{0}. Error code: {1}",
|
||||
strings.rtrim(errorMessage, '.'), errorCode);
|
||||
}
|
||||
|
||||
if (errorMessage !== null) {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
if (verbose && error.responseText !== null) {
|
||||
return error.responseText;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private connectionErrorToMessage(error: ConnectionError, verbose: boolean): string {
|
||||
let details = this.connectionErrorDetailsToMessage(error, verbose);
|
||||
|
||||
// Status Code based Error
|
||||
if (error.status === 401) {
|
||||
if (details !== null) {
|
||||
return nls.localize(
|
||||
{
|
||||
key: 'error.permission.verbose',
|
||||
comment: [
|
||||
'{0} represents detailed information why the permission got denied'
|
||||
]
|
||||
},
|
||||
"Permission Denied (HTTP {0})",
|
||||
details);
|
||||
}
|
||||
|
||||
return nls.localize('error.permission', "Permission Denied");
|
||||
}
|
||||
|
||||
// Return error details if present
|
||||
if (details) {
|
||||
return details;
|
||||
}
|
||||
|
||||
// Fallback to HTTP Status and Code
|
||||
if (error.status > 0 && error.statusText !== null) {
|
||||
if (verbose && error.responseText !== null && error.responseText.length > 0) {
|
||||
return nls.localize('error.http.verbose', "{0} (HTTP {1}: {2})", error.statusText, error.status, error.responseText);
|
||||
}
|
||||
|
||||
return nls.localize('error.http', "{0} (HTTP {1})", error.statusText, error.status);
|
||||
}
|
||||
|
||||
// Finally its an Unknown Connection Error
|
||||
if (verbose && error.responseText !== null && error.responseText.length > 0) {
|
||||
return nls.localize('error.connection.unknown.verbose', "Unknown Connection Error ({0})", error.responseText);
|
||||
}
|
||||
|
||||
return nls.localize('error.connection.unknown', "An unknown connection error occurred. Either you are no longer connected to the internet or the server you are connected to is offline.");
|
||||
}
|
||||
}
|
||||
|
||||
// Bug: Can not subclass a JS Type. Do it manually (as done in WinJS.Class.derive)
|
||||
objects.derive(Error, ConnectionError);
|
||||
|
||||
function xhrToErrorMessage(xhr: IConnectionErrorData, verbose: boolean): string {
|
||||
let ce = new ConnectionError(xhr);
|
||||
if (verbose) {
|
||||
return ce.verboseMessage;
|
||||
} else {
|
||||
return ce.message;
|
||||
}
|
||||
}
|
||||
|
||||
function exceptionToErrorMessage(exception: any, verbose: boolean): string {
|
||||
if (exception.message) {
|
||||
@@ -181,6 +33,7 @@ function detectSystemErrorMessage(exception: any): string {
|
||||
/**
|
||||
* Tries to generate a human readable error message out of the error. If the verbose parameter
|
||||
* is set to true, the error message will include stacktrace details if provided.
|
||||
*
|
||||
* @returns A string containing the error message.
|
||||
*/
|
||||
export function toErrorMessage(error: any = null, verbose: boolean = false): string {
|
||||
@@ -189,8 +42,8 @@ export function toErrorMessage(error: any = null, verbose: boolean = false): str
|
||||
}
|
||||
|
||||
if (Array.isArray(error)) {
|
||||
let errors: any[] = arrays.coalesce(error);
|
||||
let msg = toErrorMessage(errors[0], verbose);
|
||||
const errors: any[] = arrays.coalesce(error);
|
||||
const msg = toErrorMessage(errors[0], verbose);
|
||||
|
||||
if (errors.length > 1) {
|
||||
return nls.localize('error.moreErrors', "{0} ({1} errors in total)", msg, errors.length);
|
||||
@@ -203,36 +56,14 @@ export function toErrorMessage(error: any = null, verbose: boolean = false): str
|
||||
return error;
|
||||
}
|
||||
|
||||
if (!types.isUndefinedOrNull(error.status)) {
|
||||
return xhrToErrorMessage(error, verbose);
|
||||
}
|
||||
|
||||
if (error.detail) {
|
||||
let detail = error.detail;
|
||||
const detail = error.detail;
|
||||
|
||||
if (detail.error) {
|
||||
if (detail.error && !types.isUndefinedOrNull(detail.error.status)) {
|
||||
return xhrToErrorMessage(detail.error, verbose);
|
||||
}
|
||||
|
||||
if (types.isArray(detail.error)) {
|
||||
for (let i = 0; i < detail.error.length; i++) {
|
||||
if (detail.error[i] && !types.isUndefinedOrNull(detail.error[i].status)) {
|
||||
return xhrToErrorMessage(detail.error[i], verbose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
return exceptionToErrorMessage(detail.error, verbose);
|
||||
}
|
||||
return exceptionToErrorMessage(detail.error, verbose);
|
||||
}
|
||||
|
||||
if (detail.exception) {
|
||||
if (!types.isUndefinedOrNull(detail.exception.status)) {
|
||||
return xhrToErrorMessage(detail.exception, verbose);
|
||||
}
|
||||
|
||||
return exceptionToErrorMessage(detail.exception, verbose);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,10 +148,6 @@ export function onUnexpectedExternalError(e: any): undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function onUnexpectedPromiseError<T>(promise: TPromise<T>): TPromise<T | void> {
|
||||
return promise.then(null, onUnexpectedError);
|
||||
}
|
||||
|
||||
export interface SerializedError {
|
||||
readonly $isError: true;
|
||||
readonly name: string;
|
||||
@@ -213,13 +209,6 @@ export function canceled(): Error {
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an error that signals something is not implemented.
|
||||
*/
|
||||
export function notImplemented(): Error {
|
||||
return new Error('Not Implemented');
|
||||
}
|
||||
|
||||
export function illegalArgument(name?: string): Error {
|
||||
if (name) {
|
||||
return new Error(`Illegal argument: ${name}`);
|
||||
|
||||
+69
-79
@@ -5,10 +5,10 @@
|
||||
'use strict';
|
||||
|
||||
import { IDisposable, toDisposable, combinedDisposable, empty as EmptyDisposable } from 'vs/base/common/lifecycle';
|
||||
import CallbackList from 'vs/base/common/callbackList';
|
||||
import { EventEmitter } from 'vs/base/common/eventEmitter';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { once as onceFn } from 'vs/base/common/functional';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
|
||||
/**
|
||||
* To an event a function with one or zero parameters
|
||||
@@ -25,6 +25,8 @@ namespace Event {
|
||||
|
||||
export default Event;
|
||||
|
||||
type Listener = [Function, any] | Function;
|
||||
|
||||
export interface EmitterOptions {
|
||||
onFirstListenerAdd?: Function;
|
||||
onFirstListenerDidAdd?: Function;
|
||||
@@ -55,10 +57,11 @@ export interface EmitterOptions {
|
||||
*/
|
||||
export class Emitter<T> {
|
||||
|
||||
private static _noop = function () { };
|
||||
private static readonly _noop = function () { };
|
||||
|
||||
private _event: Event<T>;
|
||||
private _callbacks: CallbackList;
|
||||
private _listeners: LinkedList<Listener>;
|
||||
private _deliveryQueue: [Listener, T][];
|
||||
private _disposed: boolean;
|
||||
|
||||
constructor(private _options?: EmitterOptions) {
|
||||
@@ -72,17 +75,17 @@ export class Emitter<T> {
|
||||
get event(): Event<T> {
|
||||
if (!this._event) {
|
||||
this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]) => {
|
||||
if (!this._callbacks) {
|
||||
this._callbacks = new CallbackList();
|
||||
if (!this._listeners) {
|
||||
this._listeners = new LinkedList();
|
||||
}
|
||||
|
||||
const firstListener = this._callbacks.isEmpty();
|
||||
const firstListener = this._listeners.isEmpty();
|
||||
|
||||
if (firstListener && this._options && this._options.onFirstListenerAdd) {
|
||||
this._options.onFirstListenerAdd(this);
|
||||
}
|
||||
|
||||
const remove = this._callbacks.add(listener, thisArgs);
|
||||
const remove = this._listeners.push(!thisArgs ? listener : [listener, thisArgs]);
|
||||
|
||||
if (firstListener && this._options && this._options.onFirstListenerDidAdd) {
|
||||
this._options.onFirstListenerDidAdd(this);
|
||||
@@ -98,7 +101,7 @@ export class Emitter<T> {
|
||||
result.dispose = Emitter._noop;
|
||||
if (!this._disposed) {
|
||||
remove();
|
||||
if (this._options && this._options.onLastListenerRemove && this._callbacks.isEmpty()) {
|
||||
if (this._options && this._options.onLastListenerRemove && this._listeners.isEmpty()) {
|
||||
this._options.onLastListenerRemove(this);
|
||||
}
|
||||
}
|
||||
@@ -119,17 +122,42 @@ export class Emitter<T> {
|
||||
* subscribers
|
||||
*/
|
||||
fire(event?: T): any {
|
||||
if (this._callbacks) {
|
||||
this._callbacks.invoke.call(this._callbacks, event);
|
||||
if (this._listeners) {
|
||||
// put all [listener,event]-pairs into delivery queue
|
||||
// then emit all event. an inner/nested event might be
|
||||
// the driver of this
|
||||
|
||||
if (!this._deliveryQueue) {
|
||||
this._deliveryQueue = [];
|
||||
}
|
||||
|
||||
for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) {
|
||||
this._deliveryQueue.push([e.value, event]);
|
||||
}
|
||||
|
||||
while (this._deliveryQueue.length > 0) {
|
||||
const [listener, event] = this._deliveryQueue.shift();
|
||||
try {
|
||||
if (typeof listener === 'function') {
|
||||
listener.call(undefined, event);
|
||||
} else {
|
||||
listener[0].call(listener[1], event);
|
||||
}
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._callbacks) {
|
||||
this._callbacks.dispose();
|
||||
this._callbacks = undefined;
|
||||
this._disposed = true;
|
||||
if (this._listeners) {
|
||||
this._listeners = undefined;
|
||||
}
|
||||
if (this._deliveryQueue) {
|
||||
this._deliveryQueue.length = 0;
|
||||
}
|
||||
this._disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,40 +222,6 @@ export class EventMultiplexer<T> implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Event which is backed-up by the event emitter. This allows
|
||||
* to use the existing eventing pattern and is likely using less memory.
|
||||
* Sample:
|
||||
*
|
||||
* class Document {
|
||||
*
|
||||
* private _eventbus = new EventEmitter();
|
||||
*
|
||||
* public onDidChange = fromEventEmitter(this._eventbus, 'changed');
|
||||
*
|
||||
* // getter-style
|
||||
* // get onDidChange(): Event<(value:string)=>any> {
|
||||
* // cache fromEventEmitter result and return
|
||||
* // }
|
||||
*
|
||||
* private _doIt() {
|
||||
* // ...
|
||||
* this._eventbus.emit('changed', value)
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export function fromEventEmitter<T>(emitter: EventEmitter, eventType: string): Event<T> {
|
||||
return function (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable {
|
||||
const result = emitter.addListener(eventType, function () {
|
||||
listener.apply(thisArgs, arguments);
|
||||
});
|
||||
if (Array.isArray(disposables)) {
|
||||
disposables.push(result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
export function fromCallback<T>(fn: (handler: (e: T) => void) => IDisposable): Event<T> {
|
||||
let listener: IDisposable;
|
||||
|
||||
@@ -239,8 +233,8 @@ export function fromCallback<T>(fn: (handler: (e: T) => void) => IDisposable): E
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
export function fromPromise(promise: TPromise<any>): Event<void> {
|
||||
const emitter = new Emitter<void>();
|
||||
export function fromPromise<T =any>(promise: TPromise<T>): Event<T> {
|
||||
const emitter = new Emitter<T>();
|
||||
let shouldEmit = false;
|
||||
|
||||
promise
|
||||
@@ -266,33 +260,6 @@ export function toPromise<T>(event: Event<T>): TPromise<T> {
|
||||
});
|
||||
}
|
||||
|
||||
export function delayed<T>(promise: TPromise<Event<T>>): Event<T> {
|
||||
let toCancel: TPromise<any> = null;
|
||||
let listener: IDisposable = null;
|
||||
|
||||
const emitter = new Emitter<T>({
|
||||
onFirstListenerAdd() {
|
||||
toCancel = promise.then(
|
||||
event => listener = event(e => emitter.fire(e)),
|
||||
() => null
|
||||
);
|
||||
},
|
||||
onLastListenerRemove() {
|
||||
if (toCancel) {
|
||||
toCancel.cancel();
|
||||
toCancel = null;
|
||||
}
|
||||
|
||||
if (listener) {
|
||||
listener.dispose();
|
||||
listener = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
export function once<T>(event: Event<T>): Event<T> {
|
||||
return (listener, thisArgs = null, disposables?) => {
|
||||
const result = event(e => {
|
||||
@@ -398,6 +365,7 @@ 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>;
|
||||
on(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable;
|
||||
}
|
||||
@@ -406,6 +374,10 @@ 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> {
|
||||
return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables);
|
||||
}
|
||||
@@ -420,6 +392,10 @@ class ChainableEvent<T> implements IChainableEvent<T> {
|
||||
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));
|
||||
}
|
||||
@@ -532,7 +508,7 @@ export function echo<T>(event: Event<T>, nextTick = false, buffer: T[] = []): Ev
|
||||
export class Relay<T> implements IDisposable {
|
||||
|
||||
private emitter = new Emitter<T>();
|
||||
readonly output: Event<T> = this.emitter.event;
|
||||
readonly event: Event<T> = this.emitter.event;
|
||||
|
||||
private disposable: IDisposable = EmptyDisposable;
|
||||
|
||||
@@ -546,3 +522,17 @@ 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;
|
||||
}
|
||||
|
||||
@@ -1,299 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Errors = require('vs/base/common/errors');
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class EmitterEvent {
|
||||
|
||||
public readonly type: string;
|
||||
public readonly data: any;
|
||||
|
||||
constructor(eventType: string = null, data: any = null) {
|
||||
this.type = eventType;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ListenerCallback {
|
||||
(value: any): void;
|
||||
}
|
||||
|
||||
export interface BulkListenerCallback {
|
||||
(value: EmitterEvent[]): void;
|
||||
}
|
||||
|
||||
export interface IBaseEventEmitter {
|
||||
addBulkListener(listener: BulkListenerCallback): IDisposable;
|
||||
}
|
||||
|
||||
export interface IEventEmitter extends IBaseEventEmitter, IDisposable {
|
||||
addListener(eventType: string, listener: ListenerCallback): IDisposable;
|
||||
addOneTimeListener(eventType: string, listener: ListenerCallback): IDisposable;
|
||||
addEmitter(eventEmitter: IEventEmitter): IDisposable;
|
||||
}
|
||||
|
||||
export interface IListenersMap {
|
||||
[key: string]: ListenerCallback[];
|
||||
}
|
||||
|
||||
export class EventEmitter implements IEventEmitter {
|
||||
|
||||
protected _listeners: IListenersMap;
|
||||
protected _bulkListeners: ListenerCallback[];
|
||||
private _collectedEvents: EmitterEvent[];
|
||||
private _deferredCnt: number;
|
||||
private _allowedEventTypes: { [eventType: string]: boolean; };
|
||||
|
||||
constructor(allowedEventTypes: string[] = null) {
|
||||
this._listeners = {};
|
||||
this._bulkListeners = [];
|
||||
this._collectedEvents = [];
|
||||
this._deferredCnt = 0;
|
||||
if (allowedEventTypes) {
|
||||
this._allowedEventTypes = {};
|
||||
for (let i = 0; i < allowedEventTypes.length; i++) {
|
||||
this._allowedEventTypes[allowedEventTypes[i]] = true;
|
||||
}
|
||||
} else {
|
||||
this._allowedEventTypes = null;
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._listeners = {};
|
||||
this._bulkListeners = [];
|
||||
this._collectedEvents = [];
|
||||
this._deferredCnt = 0;
|
||||
this._allowedEventTypes = null;
|
||||
}
|
||||
|
||||
public addListener(eventType: string, listener: ListenerCallback): IDisposable {
|
||||
if (eventType === '*') {
|
||||
throw new Error('Use addBulkListener(listener) to register your listener!');
|
||||
}
|
||||
|
||||
if (this._allowedEventTypes && !this._allowedEventTypes.hasOwnProperty(eventType)) {
|
||||
throw new Error('This object will never emit this event type!');
|
||||
}
|
||||
|
||||
if (this._listeners.hasOwnProperty(eventType)) {
|
||||
this._listeners[eventType].push(listener);
|
||||
} else {
|
||||
this._listeners[eventType] = [listener];
|
||||
}
|
||||
|
||||
let bound = this;
|
||||
return {
|
||||
dispose: () => {
|
||||
if (!bound) {
|
||||
// Already called
|
||||
return;
|
||||
}
|
||||
|
||||
bound._removeListener(eventType, listener);
|
||||
|
||||
// Prevent leakers from holding on to the event emitter
|
||||
bound = null;
|
||||
listener = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public addOneTimeListener(eventType: string, listener: ListenerCallback): IDisposable {
|
||||
const disposable = this.addListener(eventType, value => {
|
||||
disposable.dispose();
|
||||
listener(value);
|
||||
});
|
||||
|
||||
return disposable;
|
||||
}
|
||||
|
||||
public addBulkListener(listener: BulkListenerCallback): IDisposable {
|
||||
|
||||
this._bulkListeners.push(listener);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
this._removeBulkListener(listener);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public addEmitter(eventEmitter: IBaseEventEmitter): IDisposable {
|
||||
return eventEmitter.addBulkListener((events: EmitterEvent[]): void => {
|
||||
if (this._deferredCnt === 0) {
|
||||
this._emitEvents(events);
|
||||
} else {
|
||||
// Collect for later
|
||||
this._collectedEvents.push.apply(this._collectedEvents, events);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _removeListener(eventType: string, listener: ListenerCallback): void {
|
||||
if (this._listeners.hasOwnProperty(eventType)) {
|
||||
let listeners = this._listeners[eventType];
|
||||
for (let i = 0, len = listeners.length; i < len; i++) {
|
||||
if (listeners[i] === listener) {
|
||||
listeners.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _removeBulkListener(listener: BulkListenerCallback): void {
|
||||
for (let i = 0, len = this._bulkListeners.length; i < len; i++) {
|
||||
if (this._bulkListeners[i] === listener) {
|
||||
this._bulkListeners.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected _emitToSpecificTypeListeners(eventType: string, data: any): void {
|
||||
if (this._listeners.hasOwnProperty(eventType)) {
|
||||
const listeners = this._listeners[eventType].slice(0);
|
||||
for (let i = 0, len = listeners.length; i < len; i++) {
|
||||
safeInvoke1Arg(listeners[i], data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected _emitToBulkListeners(events: EmitterEvent[]): void {
|
||||
const bulkListeners = this._bulkListeners.slice(0);
|
||||
for (let i = 0, len = bulkListeners.length; i < len; i++) {
|
||||
safeInvoke1Arg(bulkListeners[i], events);
|
||||
}
|
||||
}
|
||||
|
||||
protected _emitEvents(events: EmitterEvent[]): void {
|
||||
if (this._bulkListeners.length > 0) {
|
||||
this._emitToBulkListeners(events);
|
||||
}
|
||||
for (let i = 0, len = events.length; i < len; i++) {
|
||||
const e = events[i];
|
||||
|
||||
this._emitToSpecificTypeListeners(e.type, e.data);
|
||||
}
|
||||
}
|
||||
|
||||
public emit(eventType: string, data: any = {}): void {
|
||||
if (this._allowedEventTypes && !this._allowedEventTypes.hasOwnProperty(eventType)) {
|
||||
throw new Error('Cannot emit this event type because it wasn\'t listed!');
|
||||
}
|
||||
// Early return if no listeners would get this
|
||||
if (!this._listeners.hasOwnProperty(eventType) && this._bulkListeners.length === 0) {
|
||||
return;
|
||||
}
|
||||
const emitterEvent = new EmitterEvent(eventType, data);
|
||||
|
||||
if (this._deferredCnt === 0) {
|
||||
this._emitEvents([emitterEvent]);
|
||||
} else {
|
||||
// Collect for later
|
||||
this._collectedEvents.push(emitterEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public beginDeferredEmit(): void {
|
||||
this._deferredCnt = this._deferredCnt + 1;
|
||||
}
|
||||
|
||||
public endDeferredEmit(): void {
|
||||
this._deferredCnt = this._deferredCnt - 1;
|
||||
|
||||
if (this._deferredCnt === 0) {
|
||||
this._emitCollected();
|
||||
}
|
||||
}
|
||||
|
||||
public deferredEmit<T>(callback: () => T): T {
|
||||
this.beginDeferredEmit();
|
||||
|
||||
let result: T = safeInvokeNoArg<T>(callback);
|
||||
|
||||
this.endDeferredEmit();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private _emitCollected(): void {
|
||||
if (this._collectedEvents.length === 0) {
|
||||
return;
|
||||
}
|
||||
// Flush collected events
|
||||
const events = this._collectedEvents;
|
||||
this._collectedEvents = [];
|
||||
this._emitEvents(events);
|
||||
}
|
||||
}
|
||||
|
||||
class EmitQueueElement {
|
||||
public target: Function;
|
||||
public arg: any;
|
||||
|
||||
constructor(target: Function, arg: any) {
|
||||
this.target = target;
|
||||
this.arg = arg;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as EventEmitter, but guarantees events are delivered in order to each listener
|
||||
*/
|
||||
export class OrderGuaranteeEventEmitter extends EventEmitter {
|
||||
|
||||
private _emitQueue: EmitQueueElement[];
|
||||
|
||||
constructor() {
|
||||
super(null);
|
||||
this._emitQueue = [];
|
||||
}
|
||||
|
||||
protected _emitToSpecificTypeListeners(eventType: string, data: any): void {
|
||||
if (this._listeners.hasOwnProperty(eventType)) {
|
||||
let listeners = this._listeners[eventType];
|
||||
for (let i = 0, len = listeners.length; i < len; i++) {
|
||||
this._emitQueue.push(new EmitQueueElement(listeners[i], data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected _emitToBulkListeners(events: EmitterEvent[]): void {
|
||||
let bulkListeners = this._bulkListeners;
|
||||
for (let i = 0, len = bulkListeners.length; i < len; i++) {
|
||||
this._emitQueue.push(new EmitQueueElement(bulkListeners[i], events));
|
||||
}
|
||||
}
|
||||
|
||||
protected _emitEvents(events: EmitterEvent[]): void {
|
||||
super._emitEvents(events);
|
||||
|
||||
while (this._emitQueue.length > 0) {
|
||||
let queueElement = this._emitQueue.shift();
|
||||
safeInvoke1Arg(queueElement.target, queueElement.arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function safeInvokeNoArg<T>(func: Function): T {
|
||||
try {
|
||||
return func();
|
||||
} catch (e) {
|
||||
Errors.onUnexpectedError(e);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function safeInvoke1Arg(func: Function, arg1: any): any {
|
||||
try {
|
||||
return func(arg1);
|
||||
} catch (e) {
|
||||
Errors.onUnexpectedError(e);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export class Event {
|
||||
public time: number;
|
||||
public originalEvent: Event;
|
||||
public source: any;
|
||||
|
||||
constructor(originalEvent?: Event) {
|
||||
this.time = (new Date()).getTime();
|
||||
this.originalEvent = originalEvent;
|
||||
this.source = null;
|
||||
}
|
||||
}
|
||||
|
||||
export class PropertyChangeEvent extends Event {
|
||||
public key: string;
|
||||
public oldValue: any;
|
||||
public newValue: any;
|
||||
|
||||
constructor(key?: string, oldValue?: any, newValue?: any, originalEvent?: Event) {
|
||||
super(originalEvent);
|
||||
|
||||
this.key = key;
|
||||
this.oldValue = oldValue;
|
||||
this.newValue = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewerEvent extends Event {
|
||||
public element: any;
|
||||
|
||||
constructor(element: any, originalEvent?: Event) {
|
||||
super(originalEvent);
|
||||
|
||||
this.element = element;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ISelectionEvent {
|
||||
selection: any[];
|
||||
payload?: any;
|
||||
source: any;
|
||||
}
|
||||
|
||||
export interface IFocusEvent {
|
||||
focus: any;
|
||||
payload?: any;
|
||||
source: any;
|
||||
}
|
||||
|
||||
export interface IHighlightEvent {
|
||||
highlight: any;
|
||||
payload?: any;
|
||||
source: any;
|
||||
}
|
||||
|
||||
export const EventType = {
|
||||
PROPERTY_CHANGED: 'propertyChanged',
|
||||
SELECTION: 'selection',
|
||||
FOCUS: 'focus',
|
||||
BLUR: 'blur',
|
||||
HIGHLIGHT: 'highlight',
|
||||
EXPAND: 'expand',
|
||||
COLLAPSE: 'collapse',
|
||||
TOGGLE: 'toggle',
|
||||
BEFORE_RUN: 'beforeRun',
|
||||
RUN: 'run',
|
||||
EDIT: 'edit',
|
||||
SAVE: 'save',
|
||||
CANCEL: 'cancel',
|
||||
CHANGE: 'change',
|
||||
DISPOSE: 'dispose',
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'use strict';
|
||||
|
||||
import strings = require('vs/base/common/strings');
|
||||
import { BoundedMap } from 'vs/base/common/map';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
export interface IFilter {
|
||||
@@ -38,25 +38,6 @@ export function or(...filter: IFilter[]): IFilter {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns A filter which combines the provided set
|
||||
* of filters with an and. The combines matches are
|
||||
* returned if *all* filters match.
|
||||
*/
|
||||
export function and(...filter: IFilter[]): IFilter {
|
||||
return function (word: string, wordToMatchAgainst: string): IMatch[] {
|
||||
let result: IMatch[] = [];
|
||||
for (let i = 0, len = filter.length; i < len; i++) {
|
||||
let match = filter[i](word, wordToMatchAgainst);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
result = result.concat(match);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
// Prefix
|
||||
|
||||
export const matchesStrictPrefix: IFilter = _matchesPrefix.bind(undefined, false);
|
||||
@@ -334,14 +315,9 @@ function nextWord(word: string, start: number): number {
|
||||
|
||||
// Fuzzy
|
||||
|
||||
export enum SubstringMatching {
|
||||
Contiguous,
|
||||
Separate
|
||||
}
|
||||
|
||||
export const fuzzyContiguousFilter = or(matchesPrefix, matchesCamelCase, matchesContiguousSubString);
|
||||
const fuzzySeparateFilter = or(matchesPrefix, matchesCamelCase, matchesSubString);
|
||||
const fuzzyRegExpCache = new BoundedMap<RegExp>(10000); // bounded to 10000 elements
|
||||
const fuzzyRegExpCache = new LRUCache<string, RegExp>(10000); // bounded to 10000 elements
|
||||
|
||||
export function matchesFuzzy(word: string, wordToMatchAgainst: string, enableSeparateSubstringMatching = false): IMatch[] {
|
||||
if (typeof word !== 'string' || typeof wordToMatchAgainst !== 'string') {
|
||||
@@ -365,6 +341,8 @@ export function matchesFuzzy(word: string, wordToMatchAgainst: string, enableSep
|
||||
return enableSeparateSubstringMatching ? fuzzySeparateFilter(word, wordToMatchAgainst) : fuzzyContiguousFilter(word, wordToMatchAgainst);
|
||||
}
|
||||
|
||||
//#region --- fuzzyScore ---
|
||||
|
||||
export function createMatches(position: number[]): IMatch[] {
|
||||
let ret: IMatch[] = [];
|
||||
if (!position) {
|
||||
@@ -527,7 +505,7 @@ export function fuzzyScore(pattern: string, word: string, patternMaxWhitespaceIg
|
||||
} else {
|
||||
score = 5;
|
||||
}
|
||||
} else if (isSeparatorAtPos(lowWord, wordPos - 2)) {
|
||||
} else if (isSeparatorAtPos(lowWord, wordPos - 2) || isWhitespaceAtPos(lowWord, wordPos - 2)) {
|
||||
// post separator: `foo <-> bar_foo`
|
||||
score = 5;
|
||||
|
||||
@@ -697,8 +675,7 @@ class LazyArray {
|
||||
slice(): LazyArray {
|
||||
const ret = new LazyArray();
|
||||
ret._parent = this;
|
||||
ret._parentLen = this._data ? this._data.length : 0;
|
||||
return ret;
|
||||
ret._parentLen = this._data ? this._data.length : 0; return ret;
|
||||
}
|
||||
|
||||
toArray(): number[] {
|
||||
@@ -717,23 +694,69 @@ class LazyArray {
|
||||
}
|
||||
}
|
||||
|
||||
export function nextTypoPermutation(pattern: string, patternPos: number) {
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region --- graceful ---
|
||||
|
||||
export function fuzzyScoreGracefulAggressive(pattern: string, word: string, patternMaxWhitespaceIgnore?: number): [number, number[]] {
|
||||
return fuzzyScoreWithPermutations(pattern, word, true, patternMaxWhitespaceIgnore);
|
||||
}
|
||||
|
||||
export function fuzzyScoreGraceful(pattern: string, word: string, patternMaxWhitespaceIgnore?: number): [number, number[]] {
|
||||
return fuzzyScoreWithPermutations(pattern, word, false, patternMaxWhitespaceIgnore);
|
||||
}
|
||||
|
||||
function fuzzyScoreWithPermutations(pattern: string, word: string, aggressive?: boolean, patternMaxWhitespaceIgnore?: number): [number, number[]] {
|
||||
let top: [number, number[]] = fuzzyScore(pattern, word, patternMaxWhitespaceIgnore);
|
||||
|
||||
if (top && !aggressive) {
|
||||
// when using the original pattern yield a result we`
|
||||
// return it unless we are aggressive and try to find
|
||||
// a better alignment, e.g. `cno` -> `^co^ns^ole` or `^c^o^nsole`.
|
||||
return top;
|
||||
}
|
||||
|
||||
if (pattern.length >= 3) {
|
||||
// When the pattern is long enough then try a few (max 7)
|
||||
// permutations of the pattern to find a better match. The
|
||||
// permutations only swap neighbouring characters, e.g
|
||||
// `cnoso` becomes `conso`, `cnsoo`, `cnoos`.
|
||||
let tries = Math.min(7, pattern.length - 1);
|
||||
for (let patternPos = 1; patternPos < tries; patternPos++) {
|
||||
let newPattern = nextTypoPermutation(pattern, patternPos);
|
||||
if (newPattern) {
|
||||
let candidate = fuzzyScore(newPattern, word, patternMaxWhitespaceIgnore);
|
||||
if (candidate) {
|
||||
candidate[0] -= 3; // permutation penalty
|
||||
if (!top || candidate[0] > top[0]) {
|
||||
top = candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return top;
|
||||
}
|
||||
|
||||
function nextTypoPermutation(pattern: string, patternPos: number): string {
|
||||
|
||||
if (patternPos + 1 >= pattern.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let swap1 = pattern[patternPos];
|
||||
let swap2 = pattern[patternPos + 1];
|
||||
|
||||
if (swap1 === swap2) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return pattern.slice(0, patternPos)
|
||||
+ pattern[patternPos + 1]
|
||||
+ pattern[patternPos]
|
||||
+ swap2
|
||||
+ swap1
|
||||
+ pattern.slice(patternPos + 2);
|
||||
}
|
||||
|
||||
export function fuzzyScoreGraceful(pattern: string, word: string): [number, number[]] {
|
||||
let ret = fuzzyScore(pattern, word);
|
||||
for (let patternPos = 1; patternPos < pattern.length - 1 && !ret; patternPos++) {
|
||||
let pattern2 = nextTypoPermutation(pattern, patternPos);
|
||||
ret = fuzzyScore(pattern2, word);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
@@ -5,12 +5,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
export function not<A>(fn: (a: A) => boolean): (a: A) => boolean;
|
||||
export function not(fn: Function): Function {
|
||||
return (...args) => !fn(...args);
|
||||
}
|
||||
|
||||
export function once<T extends Function>(fn: T): T {
|
||||
export function once<T extends Function>(this: any, fn: T): T {
|
||||
const _this = this;
|
||||
let didCall = false;
|
||||
let result: any;
|
||||
|
||||
+28
-21
@@ -5,10 +5,9 @@
|
||||
'use strict';
|
||||
|
||||
import arrays = require('vs/base/common/arrays');
|
||||
import objects = require('vs/base/common/objects');
|
||||
import strings = require('vs/base/common/strings');
|
||||
import paths = require('vs/base/common/paths');
|
||||
import { BoundedMap } from 'vs/base/common/map';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
@@ -19,16 +18,13 @@ export interface IExpression {
|
||||
export interface IRelativePattern {
|
||||
base: string;
|
||||
pattern: string;
|
||||
pathToRelative(from: string, to: string): string;
|
||||
}
|
||||
|
||||
export function getEmptyExpression(): IExpression {
|
||||
return Object.create(null);
|
||||
}
|
||||
|
||||
export function mergeExpressions(...expressions: IExpression[]): IExpression {
|
||||
return objects.assign(getEmptyExpression(), ...expressions.filter(expr => !!expr));
|
||||
}
|
||||
|
||||
export interface SiblingClause {
|
||||
when: string;
|
||||
}
|
||||
@@ -152,17 +148,28 @@ function parseRegExp(pattern: string): string {
|
||||
}
|
||||
|
||||
// Support brackets
|
||||
if (char !== ']' && inBrackets) {
|
||||
if (inBrackets && (char !== ']' || !bracketVal) /* ] is literally only allowed as first character in brackets to match it */) {
|
||||
let res: string;
|
||||
switch (char) {
|
||||
case '-': // allow the range operator
|
||||
res = char;
|
||||
break;
|
||||
case '^': // allow the negate operator
|
||||
res = char;
|
||||
break;
|
||||
default:
|
||||
res = strings.escapeRegExpCharacters(char);
|
||||
|
||||
// range operator
|
||||
if (char === '-') {
|
||||
res = char;
|
||||
}
|
||||
|
||||
// negation operator (only valid on first index in bracket)
|
||||
else if ((char === '^' || char === '!') && !bracketVal) {
|
||||
res = '^';
|
||||
}
|
||||
|
||||
// glob split matching is not allowed within character ranges
|
||||
// see http://man7.org/linux/man-pages/man7/glob.7.html
|
||||
else if (char === GLOB_SPLIT) {
|
||||
res = '';
|
||||
}
|
||||
|
||||
// anything else gets escaped
|
||||
else {
|
||||
res = strings.escapeRegExpCharacters(char);
|
||||
}
|
||||
|
||||
bracketVal += res;
|
||||
@@ -261,7 +268,7 @@ interface ParsedExpressionPattern {
|
||||
allPaths?: string[];
|
||||
}
|
||||
|
||||
const CACHE = new BoundedMap<ParsedStringPattern>(10000); // bounded to 10000 elements
|
||||
const CACHE = new LRUCache<string, ParsedStringPattern>(10000); // bounded to 10000 elements
|
||||
|
||||
const FALSE = function () {
|
||||
return false;
|
||||
@@ -332,7 +339,7 @@ function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string |
|
||||
return null;
|
||||
}
|
||||
|
||||
return parsedPattern(paths.relative(arg2.base, path), basename);
|
||||
return parsedPattern(paths.normalize(arg2.pathToRelative(arg2.base, path)), basename);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -471,10 +478,10 @@ export function parse(arg1: string | IExpression | IRelativePattern, options: IG
|
||||
return parsedExpression(<IExpression>arg1, options);
|
||||
}
|
||||
|
||||
function isRelativePattern(obj: any): obj is IRelativePattern {
|
||||
export function isRelativePattern(obj: any): obj is IRelativePattern {
|
||||
const rp = obj as IRelativePattern;
|
||||
|
||||
return typeof rp.base === 'string' && typeof rp.pattern === 'string';
|
||||
return rp && typeof rp.base === 'string' && typeof rp.pattern === 'string' && typeof rp.pathToRelative === 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -482,7 +489,7 @@ function isRelativePattern(obj: any): obj is IRelativePattern {
|
||||
*/
|
||||
export function parseToAsync(expression: IExpression, options?: IGlobOptions): ParsedExpression {
|
||||
const parsedExpression = parse(expression, options);
|
||||
return (path: string, basename?: string, siblingsFn?: () => TPromise<string[]>): TPromise<string> => {
|
||||
return (path: string, basename?: string, siblingsFn?: () => string[] | TPromise<string[]>): string | TPromise<string> => {
|
||||
const result = parsedExpression(path, basename, siblingsFn);
|
||||
return result instanceof TPromise ? result : TPromise.as(result);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
'use strict';
|
||||
|
||||
export interface IIterator<E> {
|
||||
next(): { done: boolean, value: E };
|
||||
next(): { readonly done: boolean, readonly value: E };
|
||||
}
|
||||
|
||||
export interface INextIterator<T> {
|
||||
|
||||
+3
-174
@@ -585,39 +585,6 @@ const enum CharacterCodes {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Takes JSON with JavaScript-style comments and remove
|
||||
* them. Optionally replaces every none-newline character
|
||||
* of comments with a replaceCharacter
|
||||
*/
|
||||
export function stripComments(text: string, replaceCh?: string): string {
|
||||
|
||||
let _scanner = createScanner(text),
|
||||
parts: string[] = [],
|
||||
kind: SyntaxKind,
|
||||
offset = 0,
|
||||
pos: number;
|
||||
|
||||
do {
|
||||
pos = _scanner.getPosition();
|
||||
kind = _scanner.scan();
|
||||
switch (kind) {
|
||||
case SyntaxKind.LineCommentTrivia:
|
||||
case SyntaxKind.BlockCommentTrivia:
|
||||
case SyntaxKind.EOF:
|
||||
if (offset !== pos) {
|
||||
parts.push(text.substring(offset, pos));
|
||||
}
|
||||
if (replaceCh !== void 0) {
|
||||
parts.push(_scanner.getTokenValue().replace(/[^\r\n]/g, replaceCh));
|
||||
}
|
||||
offset = _scanner.getPosition();
|
||||
break;
|
||||
}
|
||||
} while (kind !== SyntaxKind.EOF);
|
||||
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
export interface ParseError {
|
||||
error: ParseErrorCode;
|
||||
@@ -659,147 +626,6 @@ export interface Node {
|
||||
export type Segment = string | number;
|
||||
export type JSONPath = Segment[];
|
||||
|
||||
export interface Location {
|
||||
/**
|
||||
* The previous property key or literal value (string, number, boolean or null) or undefined.
|
||||
*/
|
||||
previousNode?: Node;
|
||||
/**
|
||||
* The path describing the location in the JSON document. The path consists of a sequence strings
|
||||
* representing an object property or numbers for array indices.
|
||||
*/
|
||||
path: JSONPath;
|
||||
/**
|
||||
* Matches the locations path against a pattern consisting of strings (for properties) and numbers (for array indices).
|
||||
* '*' will match a single segment, of any property name or index.
|
||||
* '**' will match a sequece of segments or no segment, of any property name or index.
|
||||
*/
|
||||
matches: (patterns: JSONPath) => boolean;
|
||||
/**
|
||||
* If set, the location's offset is at a property key.
|
||||
*/
|
||||
isAtPropertyKey: boolean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* For a given offset, evaluate the location in the JSON document. Each segment in the location path is either a property name or an array index.
|
||||
*/
|
||||
export function getLocation(text: string, position: number): Location {
|
||||
let segments: any[] = []; // strings or numbers
|
||||
let earlyReturnException = new Object();
|
||||
let previousNode: Node = void 0;
|
||||
const previousNodeInst: Node = {
|
||||
value: void 0,
|
||||
offset: void 0,
|
||||
length: void 0,
|
||||
type: void 0
|
||||
};
|
||||
let isAtPropertyKey = false;
|
||||
function setPreviousNode(value: string, offset: number, length: number, type: NodeType) {
|
||||
previousNodeInst.value = value;
|
||||
previousNodeInst.offset = offset;
|
||||
previousNodeInst.length = length;
|
||||
previousNodeInst.type = type;
|
||||
previousNodeInst.columnOffset = void 0;
|
||||
previousNode = previousNodeInst;
|
||||
}
|
||||
try {
|
||||
|
||||
visit(text, {
|
||||
onObjectBegin: (offset: number, length: number) => {
|
||||
if (position <= offset) {
|
||||
throw earlyReturnException;
|
||||
}
|
||||
previousNode = void 0;
|
||||
isAtPropertyKey = position > offset;
|
||||
segments.push(''); // push a placeholder (will be replaced)
|
||||
},
|
||||
onObjectProperty: (name: string, offset: number, length: number) => {
|
||||
if (position < offset) {
|
||||
throw earlyReturnException;
|
||||
}
|
||||
setPreviousNode(name, offset, length, 'property');
|
||||
segments[segments.length - 1] = name;
|
||||
if (position <= offset + length) {
|
||||
throw earlyReturnException;
|
||||
}
|
||||
},
|
||||
onObjectEnd: (offset: number, length: number) => {
|
||||
if (position <= offset) {
|
||||
throw earlyReturnException;
|
||||
}
|
||||
previousNode = void 0;
|
||||
segments.pop();
|
||||
},
|
||||
onArrayBegin: (offset: number, length: number) => {
|
||||
if (position <= offset) {
|
||||
throw earlyReturnException;
|
||||
}
|
||||
previousNode = void 0;
|
||||
segments.push(0);
|
||||
},
|
||||
onArrayEnd: (offset: number, length: number) => {
|
||||
if (position <= offset) {
|
||||
throw earlyReturnException;
|
||||
}
|
||||
previousNode = void 0;
|
||||
segments.pop();
|
||||
},
|
||||
onLiteralValue: (value: any, offset: number, length: number) => {
|
||||
if (position < offset) {
|
||||
throw earlyReturnException;
|
||||
}
|
||||
setPreviousNode(value, offset, length, getLiteralNodeType(value));
|
||||
|
||||
if (position <= offset + length) {
|
||||
throw earlyReturnException;
|
||||
}
|
||||
},
|
||||
onSeparator: (sep: string, offset: number, length: number) => {
|
||||
if (position <= offset) {
|
||||
throw earlyReturnException;
|
||||
}
|
||||
if (sep === ':' && previousNode.type === 'property') {
|
||||
previousNode.columnOffset = offset;
|
||||
isAtPropertyKey = false;
|
||||
previousNode = void 0;
|
||||
} else if (sep === ',') {
|
||||
let last = segments[segments.length - 1];
|
||||
if (typeof last === 'number') {
|
||||
segments[segments.length - 1] = last + 1;
|
||||
} else {
|
||||
isAtPropertyKey = true;
|
||||
segments[segments.length - 1] = '';
|
||||
}
|
||||
previousNode = void 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
if (e !== earlyReturnException) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
path: segments,
|
||||
previousNode,
|
||||
isAtPropertyKey,
|
||||
matches: (pattern: string[]) => {
|
||||
let k = 0;
|
||||
for (let i = 0; k < pattern.length && i < segments.length; i++) {
|
||||
if (pattern[k] === segments[i] || pattern[k] === '*') {
|
||||
k++;
|
||||
} else if (pattern[k] !== '**') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return k === pattern.length;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface ParseOptions {
|
||||
disallowComments?: boolean;
|
||||
allowTrailingComma?: boolean;
|
||||
@@ -1135,6 +961,9 @@ export function visit(text: string, visitor: JSONVisitor, options?: ParseOptions
|
||||
}
|
||||
onSeparator(',');
|
||||
scanNext(); // consume comma
|
||||
if (_scanner.getToken() === SyntaxKind.CloseBracketToken && allowTrailingComma) {
|
||||
break;
|
||||
}
|
||||
} else if (needsComma) {
|
||||
handleError(ParseErrorCode.CommaExpected, [], []);
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ export interface IJSONSchema {
|
||||
markdownEnumDescriptions?: string[]; // VSCode extension
|
||||
markdownDescription?: string; // VSCode extension
|
||||
doNotSuggest?: boolean; // VSCode extension
|
||||
allowComments?: boolean; // VSCode extension
|
||||
}
|
||||
|
||||
export interface IJSONSchemaMap {
|
||||
|
||||
@@ -476,6 +476,14 @@ export class SimpleKeybinding {
|
||||
);
|
||||
}
|
||||
|
||||
public getHashCode(): string {
|
||||
let ctrl = this.ctrlKey ? '1' : '0';
|
||||
let shift = this.shiftKey ? '1' : '0';
|
||||
let alt = this.altKey ? '1' : '0';
|
||||
let meta = this.metaKey ? '1' : '0';
|
||||
return `${ctrl}${shift}${alt}${meta}${this.keyCode}`;
|
||||
}
|
||||
|
||||
public isModifierKey(): boolean {
|
||||
return (
|
||||
this.keyCode === KeyCode.Unknown
|
||||
@@ -509,6 +517,10 @@ export class ChordKeybinding {
|
||||
this.firstPart = firstPart;
|
||||
this.chordPart = chordPart;
|
||||
}
|
||||
|
||||
public getHashCode(): string {
|
||||
return `${this.firstPart.getHashCode()};${this.chordPart.getHashCode()}`;
|
||||
}
|
||||
}
|
||||
|
||||
export type Keybinding = SimpleKeybinding | ChordKeybinding;
|
||||
|
||||
@@ -6,17 +6,9 @@
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import platform = require('vs/base/common/platform');
|
||||
import { nativeSep, normalize, isEqualOrParent, isEqual, basename, join } from 'vs/base/common/paths';
|
||||
import { nativeSep, normalize, isEqualOrParent, isEqual, basename as pathsBasename, join } from 'vs/base/common/paths';
|
||||
import { endsWith, ltrim } from 'vs/base/common/strings';
|
||||
|
||||
export interface ILabelProvider {
|
||||
|
||||
/**
|
||||
* Given an element returns a label for it to display in the UI.
|
||||
*/
|
||||
getLabel(element: any): string;
|
||||
}
|
||||
|
||||
export interface IWorkspaceFolderProvider {
|
||||
getWorkspaceFolder(resource: URI): { uri: URI };
|
||||
getWorkspace(): {
|
||||
@@ -54,7 +46,7 @@ export function getPathLabel(resource: URI | string, rootProvider?: IWorkspaceFo
|
||||
}
|
||||
|
||||
if (hasMultipleRoots) {
|
||||
const rootName = basename(baseResource.uri.fsPath);
|
||||
const rootName = pathsBasename(baseResource.uri.fsPath);
|
||||
pathLabel = pathLabel ? join(rootName, pathLabel) : rootName; // always show root basename if there are multiple
|
||||
}
|
||||
|
||||
@@ -75,6 +67,25 @@ export function getPathLabel(resource: URI | string, rootProvider?: IWorkspaceFo
|
||||
return res;
|
||||
}
|
||||
|
||||
export function getBaseLabel(resource: URI | string): string {
|
||||
if (!resource) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof resource === 'string') {
|
||||
resource = URI.file(resource);
|
||||
}
|
||||
|
||||
const base = pathsBasename(resource.fsPath) || resource.fsPath /* can be empty string if '/' is passed in */;
|
||||
|
||||
// convert c: => C:
|
||||
if (hasDriveLetter(base)) {
|
||||
return normalizeDriveLetter(base);
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
function hasDriveLetter(path: string): boolean {
|
||||
return platform.isWindows && path && path[1] === ':';
|
||||
}
|
||||
@@ -95,6 +106,10 @@ export function tildify(path: string, userHome: string): string {
|
||||
return path;
|
||||
}
|
||||
|
||||
export function untildify(path: string, userHome: string): string {
|
||||
return path.replace(/^~($|\/|\\)/, `${userHome}$1`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortens the paths but keeps them easy to distinguish.
|
||||
* Replaces not important parts with ellipsis.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { once } from 'vs/base/common/functional';
|
||||
|
||||
export const empty: IDisposable = Object.freeze({
|
||||
export const empty: IDisposable = Object.freeze<IDisposable>({
|
||||
dispose() { }
|
||||
});
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ export class LinkedList<E> {
|
||||
return !this._first;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this._first = undefined;
|
||||
this._last = undefined;
|
||||
}
|
||||
|
||||
unshift(element: E) {
|
||||
return this.insert(element, false);
|
||||
}
|
||||
@@ -90,21 +95,19 @@ export class LinkedList<E> {
|
||||
}
|
||||
|
||||
iterator(): IIterator<E> {
|
||||
let _done: boolean;
|
||||
let _value: E;
|
||||
let element = {
|
||||
get done() { return _done; },
|
||||
get value() { return _value; }
|
||||
done: undefined,
|
||||
value: undefined,
|
||||
};
|
||||
let node = this._first;
|
||||
return {
|
||||
next(): { done: boolean; value: E } {
|
||||
if (!node) {
|
||||
_done = true;
|
||||
_value = undefined;
|
||||
element.done = true;
|
||||
element.value = undefined;
|
||||
} else {
|
||||
_done = false;
|
||||
_value = node.element;
|
||||
element.done = false;
|
||||
element.value = node.element;
|
||||
node = node.next;
|
||||
}
|
||||
return element;
|
||||
|
||||
+144
-253
@@ -7,15 +7,6 @@
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export interface Key {
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
export interface Entry<K, T> {
|
||||
key: K;
|
||||
value: T;
|
||||
}
|
||||
|
||||
export function values<K, V>(map: Map<K, V>): V[] {
|
||||
const result: V[] = [];
|
||||
map.forEach(value => result.push(value));
|
||||
@@ -40,186 +31,6 @@ export function getOrSet<K, V>(map: Map<K, V>, key: K, value: V): V {
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface ISerializedBoundedLinkedMap<T> {
|
||||
entries: { key: string; value: T }[];
|
||||
}
|
||||
|
||||
interface LinkedEntry<K, T> extends Entry<K, T> {
|
||||
next?: LinkedEntry<K, T>;
|
||||
prev?: LinkedEntry<K, T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple Map<T> that optionally allows to set a limit of entries to store. Once the limit is hit,
|
||||
* the cache will remove the entry that was last recently added. Or, if a ratio is provided below 1,
|
||||
* all elements will be removed until the ratio is full filled (e.g. 0.75 to remove 25% of old elements).
|
||||
*/
|
||||
export class BoundedMap<T> {
|
||||
private map: Map<string, LinkedEntry<string, T>>;
|
||||
|
||||
private head: LinkedEntry<string, T>;
|
||||
private tail: LinkedEntry<string, T>;
|
||||
private ratio: number;
|
||||
|
||||
constructor(private limit = Number.MAX_VALUE, ratio = 1, value?: ISerializedBoundedLinkedMap<T>) {
|
||||
this.map = new Map<string, LinkedEntry<string, T>>();
|
||||
this.ratio = limit * ratio;
|
||||
|
||||
if (value) {
|
||||
value.entries.forEach(entry => {
|
||||
this.set(entry.key, entry.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public setLimit(limit: number): void {
|
||||
if (limit < 0) {
|
||||
return; // invalid limit
|
||||
}
|
||||
|
||||
this.limit = limit;
|
||||
while (this.map.size > this.limit) {
|
||||
this.trim();
|
||||
}
|
||||
}
|
||||
|
||||
public serialize(): ISerializedBoundedLinkedMap<T> {
|
||||
const serialized: ISerializedBoundedLinkedMap<T> = { entries: [] };
|
||||
|
||||
this.map.forEach(entry => {
|
||||
serialized.entries.push({ key: entry.key, value: entry.value });
|
||||
});
|
||||
|
||||
return serialized;
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
return this.map.size;
|
||||
}
|
||||
|
||||
public set(key: string, value: T): boolean {
|
||||
if (this.map.has(key)) {
|
||||
return false; // already present!
|
||||
}
|
||||
|
||||
const entry: LinkedEntry<string, T> = { key, value };
|
||||
this.push(entry);
|
||||
|
||||
if (this.size > this.limit) {
|
||||
this.trim();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public get(key: string): T {
|
||||
const entry = this.map.get(key);
|
||||
|
||||
return entry ? entry.value : null;
|
||||
}
|
||||
|
||||
public getOrSet(k: string, t: T): T {
|
||||
const res = this.get(k);
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
|
||||
this.set(k, t);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
public delete(key: string): T {
|
||||
const entry = this.map.get(key);
|
||||
|
||||
if (entry) {
|
||||
this.map.delete(key);
|
||||
|
||||
if (entry.next) {
|
||||
entry.next.prev = entry.prev; // [A]<-[x]<-[C] = [A]<-[C]
|
||||
} else {
|
||||
this.head = entry.prev; // [A]-[x] = [A]
|
||||
}
|
||||
|
||||
if (entry.prev) {
|
||||
entry.prev.next = entry.next; // [A]->[x]->[C] = [A]->[C]
|
||||
} else {
|
||||
this.tail = entry.next; // [x]-[A] = [A]
|
||||
}
|
||||
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public has(key: string): boolean {
|
||||
return this.map.has(key);
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this.map.clear();
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
}
|
||||
|
||||
private push(entry: LinkedEntry<string, T>): void {
|
||||
if (this.head) {
|
||||
// [A]-[B] = [A]-[B]->[X]
|
||||
entry.prev = this.head;
|
||||
this.head.next = entry;
|
||||
}
|
||||
|
||||
if (!this.tail) {
|
||||
this.tail = entry;
|
||||
}
|
||||
|
||||
this.head = entry;
|
||||
|
||||
this.map.set(entry.key, entry);
|
||||
}
|
||||
|
||||
private trim(): void {
|
||||
if (this.tail) {
|
||||
|
||||
// Remove all elements until ratio is reached
|
||||
if (this.ratio < this.limit) {
|
||||
let index = 0;
|
||||
let current = this.tail;
|
||||
while (current.next) {
|
||||
|
||||
// Remove the entry
|
||||
this.map.delete(current.key);
|
||||
|
||||
// if we reached the element that overflows our ratio condition
|
||||
// make its next element the new tail of the Map and adjust the size
|
||||
if (index === this.ratio) {
|
||||
this.tail = current.next;
|
||||
this.tail.prev = null;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Move on
|
||||
current = current.next;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Just remove the tail element
|
||||
else {
|
||||
this.map.delete(this.tail.key);
|
||||
|
||||
// [x]-[B] = [B]
|
||||
this.tail = this.tail.next;
|
||||
if (this.tail) {
|
||||
this.tail.prev = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface IKeyIterator {
|
||||
reset(key: string): this;
|
||||
next(): this;
|
||||
@@ -267,8 +78,8 @@ export class StringIterator implements IKeyIterator {
|
||||
|
||||
export class PathIterator implements IKeyIterator {
|
||||
|
||||
private static _fwd = '/'.charCodeAt(0);
|
||||
private static _bwd = '\\'.charCodeAt(0);
|
||||
private static readonly _fwd = '/'.charCodeAt(0);
|
||||
private static readonly _bwd = '\\'.charCodeAt(0);
|
||||
|
||||
private _value: string;
|
||||
private _from: number;
|
||||
@@ -370,7 +181,7 @@ export class TernarySearchTree<E> {
|
||||
this._root = undefined;
|
||||
}
|
||||
|
||||
set(key: string, element: E): void {
|
||||
set(key: string, element: E): E {
|
||||
let iter = this._iter.reset(key);
|
||||
let node: TernarySearchTreeNode<E>;
|
||||
|
||||
@@ -410,7 +221,9 @@ export class TernarySearchTree<E> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const oldElement = node.element;
|
||||
node.element = element;
|
||||
return oldElement;
|
||||
}
|
||||
|
||||
get(key: string): E {
|
||||
@@ -436,29 +249,44 @@ export class TernarySearchTree<E> {
|
||||
}
|
||||
|
||||
delete(key: string): void {
|
||||
this._delete(this._root, this._iter.reset(key));
|
||||
}
|
||||
|
||||
private _delete(node: TernarySearchTreeNode<E>, iter: IKeyIterator): TernarySearchTreeNode<E> {
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
const cmp = iter.cmp(node.str);
|
||||
if (cmp > 0) {
|
||||
// left
|
||||
node.left = this._delete(node.left, iter);
|
||||
} else if (cmp < 0) {
|
||||
// right
|
||||
node.right = this._delete(node.right, iter);
|
||||
} else if (iter.hasNext()) {
|
||||
// mid
|
||||
node.mid = this._delete(node.mid, iter.next());
|
||||
} else {
|
||||
// remove element
|
||||
node.element = undefined;
|
||||
}
|
||||
let iter = this._iter.reset(key);
|
||||
let stack: [-1 | 0 | 1, TernarySearchTreeNode<E>][] = [];
|
||||
let node = this._root;
|
||||
|
||||
return node.isEmpty() ? undefined : node;
|
||||
// find and unset node
|
||||
while (node) {
|
||||
let val = iter.cmp(node.str);
|
||||
if (val > 0) {
|
||||
// left
|
||||
stack.push([1, node]);
|
||||
node = node.left;
|
||||
} else if (val < 0) {
|
||||
// right
|
||||
stack.push([-1, node]);
|
||||
node = node.right;
|
||||
} else if (iter.hasNext()) {
|
||||
// mid
|
||||
iter.next();
|
||||
stack.push([0, node]);
|
||||
node = node.mid;
|
||||
} else {
|
||||
// remove element
|
||||
node.element = undefined;
|
||||
|
||||
// clean up empty nodes
|
||||
while (stack.length > 0 && node.isEmpty()) {
|
||||
let [dir, parent] = stack.pop();
|
||||
switch (dir) {
|
||||
case 1: parent.left = undefined; break;
|
||||
case 0: parent.mid = undefined; break;
|
||||
case -1: parent.right = undefined; break;
|
||||
}
|
||||
node = parent;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findSubstr(key: string): E {
|
||||
@@ -518,17 +346,22 @@ export class TernarySearchTree<E> {
|
||||
}
|
||||
|
||||
private _forEach(node: TernarySearchTreeNode<E>, parts: string[], callback: (value: E, index: string) => any) {
|
||||
if (!node) {
|
||||
return;
|
||||
if (node) {
|
||||
// left
|
||||
this._forEach(node.left, parts, callback);
|
||||
|
||||
// node
|
||||
parts.push(node.str);
|
||||
if (node.element) {
|
||||
callback(node.element, this._iter.join(parts));
|
||||
}
|
||||
// mid
|
||||
this._forEach(node.mid, parts, callback);
|
||||
parts.pop();
|
||||
|
||||
// right
|
||||
this._forEach(node.right, parts, callback);
|
||||
}
|
||||
this._forEach(node.left, parts, callback);
|
||||
this._forEach(node.right, parts, callback);
|
||||
let newParts = parts.slice();
|
||||
newParts.push(node.str);
|
||||
if (node.element) {
|
||||
callback(node.element, this._iter.join(newParts));
|
||||
}
|
||||
this._forEach(node.mid, newParts, callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,14 +436,12 @@ interface Item<K, V> {
|
||||
value: V;
|
||||
}
|
||||
|
||||
export namespace Touch {
|
||||
export const None: 0 = 0;
|
||||
export const First: 1 = 1;
|
||||
export const Last: 2 = 2;
|
||||
export enum Touch {
|
||||
None = 0,
|
||||
AsOld = 1,
|
||||
AsNew = 2
|
||||
}
|
||||
|
||||
export type Touch = 0 | 1 | 2;
|
||||
|
||||
export class LinkedMap<K, V> {
|
||||
|
||||
private _map: Map<K, Item<K, V>>;
|
||||
@@ -644,11 +475,14 @@ export class LinkedMap<K, V> {
|
||||
return this._map.has(key);
|
||||
}
|
||||
|
||||
public get(key: K): V | undefined {
|
||||
public get(key: K, touch: Touch = Touch.None): V | undefined {
|
||||
const item = this._map.get(key);
|
||||
if (!item) {
|
||||
return undefined;
|
||||
}
|
||||
if (touch !== Touch.None) {
|
||||
this.touch(item, touch);
|
||||
}
|
||||
return item.value;
|
||||
}
|
||||
|
||||
@@ -665,10 +499,10 @@ export class LinkedMap<K, V> {
|
||||
case Touch.None:
|
||||
this.addItemLast(item);
|
||||
break;
|
||||
case Touch.First:
|
||||
case Touch.AsOld:
|
||||
this.addItemFirst(item);
|
||||
break;
|
||||
case Touch.Last:
|
||||
case Touch.AsNew:
|
||||
this.addItemLast(item);
|
||||
break;
|
||||
default:
|
||||
@@ -721,18 +555,6 @@ export class LinkedMap<K, V> {
|
||||
}
|
||||
}
|
||||
|
||||
public forEachReverse(callbackfn: (value: V, key: K, map: LinkedMap<K, V>) => void, thisArg?: any): void {
|
||||
let current = this._tail;
|
||||
while (current) {
|
||||
if (thisArg) {
|
||||
callbackfn.bind(thisArg)(current.value, current.key, this);
|
||||
} else {
|
||||
callbackfn(current.value, current.key, this);
|
||||
}
|
||||
current = current.previous;
|
||||
}
|
||||
}
|
||||
|
||||
public values(): V[] {
|
||||
let result: V[] = [];
|
||||
let current = this._head;
|
||||
@@ -793,6 +615,26 @@ export class LinkedMap<K, V> {
|
||||
}
|
||||
*/
|
||||
|
||||
protected trimOld(newSize: number) {
|
||||
if (newSize >= this.size) {
|
||||
return;
|
||||
}
|
||||
if (newSize === 0) {
|
||||
this.clear();
|
||||
return;
|
||||
}
|
||||
let current = this._head;
|
||||
let currentSize = this.size;
|
||||
while (current && currentSize > newSize) {
|
||||
this._map.delete(current.key);
|
||||
current = current.next;
|
||||
currentSize--;
|
||||
}
|
||||
this._head = current;
|
||||
this._size = currentSize;
|
||||
current.previous = void 0;
|
||||
}
|
||||
|
||||
private addItemFirst(item: Item<K, V>): void {
|
||||
// First time Insert
|
||||
if (!this._head && !this._tail) {
|
||||
@@ -821,8 +663,8 @@ export class LinkedMap<K, V> {
|
||||
|
||||
private removeItem(item: Item<K, V>): void {
|
||||
if (item === this._head && item === this._tail) {
|
||||
this._head = undefined;
|
||||
this._tail = undefined;
|
||||
this._head = void 0;
|
||||
this._tail = void 0;
|
||||
}
|
||||
else if (item === this._head) {
|
||||
this._head = item.next;
|
||||
@@ -845,11 +687,11 @@ export class LinkedMap<K, V> {
|
||||
if (!this._head || !this._tail) {
|
||||
throw new Error('Invalid list');
|
||||
}
|
||||
if ((touch !== Touch.First && touch !== Touch.Last)) {
|
||||
if ((touch !== Touch.AsOld && touch !== Touch.AsNew)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (touch === Touch.First) {
|
||||
if (touch === Touch.AsOld) {
|
||||
if (item === this._head) {
|
||||
return;
|
||||
}
|
||||
@@ -861,7 +703,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 = undefined;
|
||||
previous!.next = void 0;
|
||||
this._tail = previous;
|
||||
}
|
||||
else {
|
||||
@@ -871,11 +713,11 @@ export class LinkedMap<K, V> {
|
||||
}
|
||||
|
||||
// Insert the node at head
|
||||
item.previous = undefined;
|
||||
item.previous = void 0;
|
||||
item.next = this._head;
|
||||
this._head.previous = item;
|
||||
this._head = item;
|
||||
} else if (touch === Touch.Last) {
|
||||
} else if (touch === Touch.AsNew) {
|
||||
if (item === this._tail) {
|
||||
return;
|
||||
}
|
||||
@@ -887,17 +729,66 @@ 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 = undefined;
|
||||
next!.previous = void 0;
|
||||
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 = undefined;
|
||||
item.next = void 0;
|
||||
item.previous = this._tail;
|
||||
this._tail.next = item;
|
||||
this._tail = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class LRUCache<K, V> extends LinkedMap<K, V> {
|
||||
|
||||
private _limit: number;
|
||||
private _ratio: number;
|
||||
|
||||
constructor(limit: number, ratio: number = 1) {
|
||||
super();
|
||||
this._limit = limit;
|
||||
this._ratio = Math.min(Math.max(0, ratio), 1);
|
||||
}
|
||||
|
||||
public get limit(): number {
|
||||
return this._limit;
|
||||
}
|
||||
|
||||
public set limit(limit: number) {
|
||||
this._limit = limit;
|
||||
this.checkTrim();
|
||||
}
|
||||
|
||||
public get ratio(): number {
|
||||
return this._ratio;
|
||||
}
|
||||
|
||||
public set ratio(ratio: number) {
|
||||
this._ratio = Math.min(Math.max(0, ratio), 1);
|
||||
this.checkTrim();
|
||||
}
|
||||
|
||||
public get(key: K): V | undefined {
|
||||
return super.get(key, Touch.AsNew);
|
||||
}
|
||||
|
||||
public peek(key: K): V | undefined {
|
||||
return super.get(key, Touch.None);
|
||||
}
|
||||
|
||||
public set(key: K, value: V): void {
|
||||
super.set(key, value, Touch.AsNew);
|
||||
this.checkTrim();
|
||||
}
|
||||
|
||||
private checkTrim() {
|
||||
if (this.size > this._limit) {
|
||||
this.trimOld(Math.round(this._limit * this._ratio));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ export function stringify(obj: any): string {
|
||||
}
|
||||
|
||||
export function parse(text: string): any {
|
||||
return JSON.parse(text, reviver);
|
||||
let data = JSON.parse(text);
|
||||
data = revive(data, 0);
|
||||
return data;
|
||||
}
|
||||
|
||||
interface MarshalledObject {
|
||||
@@ -30,15 +32,27 @@ function replacer(key: string, value: any): any {
|
||||
return value;
|
||||
}
|
||||
|
||||
function reviver(key: string, value: any): any {
|
||||
let marshallingConst: number;
|
||||
if (value !== void 0 && value !== null) {
|
||||
marshallingConst = (<MarshalledObject>value).$mid;
|
||||
function revive(obj: any, depth: number): any {
|
||||
|
||||
if (!obj || depth > 200) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
switch (marshallingConst) {
|
||||
case 1: return URI.revive(value);
|
||||
case 2: return new RegExp(value.source, value.flags);
|
||||
default: return value;
|
||||
if (typeof obj === 'object') {
|
||||
|
||||
switch ((<MarshalledObject>obj).$mid) {
|
||||
case 1: return URI.revive(obj);
|
||||
case 2: return new RegExp(obj.source, obj.flags);
|
||||
}
|
||||
|
||||
// walk object (or array)
|
||||
for (let key in obj) {
|
||||
if (Object.hasOwnProperty.call(obj, key)) {
|
||||
obj[key] = revive(obj[key], depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
+11
-29
@@ -5,7 +5,6 @@
|
||||
'use strict';
|
||||
|
||||
import paths = require('vs/base/common/paths');
|
||||
import types = require('vs/base/common/types');
|
||||
import strings = require('vs/base/common/strings');
|
||||
import { match } from 'vs/base/common/glob';
|
||||
|
||||
@@ -37,7 +36,7 @@ let userRegisteredAssociations: ITextMimeAssociationItem[] = [];
|
||||
/**
|
||||
* Associate a text mime to the registry.
|
||||
*/
|
||||
export function registerTextMime(association: ITextMimeAssociation): void {
|
||||
export function registerTextMime(association: ITextMimeAssociation, warnOnOverwrite = false): void {
|
||||
|
||||
// Register
|
||||
const associationItem = toTextMimeAssociationItem(association);
|
||||
@@ -49,7 +48,7 @@ export function registerTextMime(association: ITextMimeAssociation): void {
|
||||
}
|
||||
|
||||
// Check for conflicts unless this is a user configured association
|
||||
if (!associationItem.userConfigured) {
|
||||
if (warnOnOverwrite && !associationItem.userConfigured) {
|
||||
registeredAssociations.forEach(a => {
|
||||
if (a.mime === associationItem.mime || a.userConfigured) {
|
||||
return; // same mime or userConfigured is ok
|
||||
@@ -113,23 +112,23 @@ export function guessMimeTypes(path: string, firstLine?: string): string[] {
|
||||
}
|
||||
|
||||
path = path.toLowerCase();
|
||||
let filename = paths.basename(path);
|
||||
const filename = paths.basename(path);
|
||||
|
||||
// 1.) User configured mappings have highest priority
|
||||
let configuredMime = guessMimeTypeByPath(path, filename, userRegisteredAssociations);
|
||||
const configuredMime = guessMimeTypeByPath(path, filename, userRegisteredAssociations);
|
||||
if (configuredMime) {
|
||||
return [configuredMime, MIME_TEXT];
|
||||
}
|
||||
|
||||
// 2.) Registered mappings have middle priority
|
||||
let registeredMime = guessMimeTypeByPath(path, filename, nonUserRegisteredAssociations);
|
||||
const registeredMime = guessMimeTypeByPath(path, filename, nonUserRegisteredAssociations);
|
||||
if (registeredMime) {
|
||||
return [registeredMime, MIME_TEXT];
|
||||
}
|
||||
|
||||
// 3.) Firstline has lowest priority
|
||||
if (firstLine) {
|
||||
let firstlineMime = guessMimeTypeByFirstline(firstLine);
|
||||
const firstlineMime = guessMimeTypeByFirstline(firstLine);
|
||||
if (firstlineMime) {
|
||||
return [firstlineMime, MIME_TEXT];
|
||||
}
|
||||
@@ -146,7 +145,7 @@ function guessMimeTypeByPath(path: string, filename: string, associations: IText
|
||||
// We want to prioritize associations based on the order they are registered so that the last registered
|
||||
// association wins over all other. This is for https://github.com/Microsoft/vscode/issues/20074
|
||||
for (let i = associations.length - 1; i >= 0; i--) {
|
||||
let association = associations[i];
|
||||
const association = associations[i];
|
||||
|
||||
// First exact name match
|
||||
if (filename === association.filenameLowercase) {
|
||||
@@ -157,7 +156,7 @@ function guessMimeTypeByPath(path: string, filename: string, associations: IText
|
||||
// Longest pattern match
|
||||
if (association.filepattern) {
|
||||
if (!patternMatch || association.filepattern.length > patternMatch.filepattern.length) {
|
||||
let target = association.filepatternOnPath ? path : filename; // match on full path if pattern contains path separator
|
||||
const target = association.filepatternOnPath ? path : filename; // match on full path if pattern contains path separator
|
||||
if (match(association.filepatternLowercase, target)) {
|
||||
patternMatch = association;
|
||||
}
|
||||
@@ -199,12 +198,12 @@ function guessMimeTypeByFirstline(firstLine: string): string {
|
||||
|
||||
if (firstLine.length > 0) {
|
||||
for (let i = 0; i < registeredAssociations.length; ++i) {
|
||||
let association = registeredAssociations[i];
|
||||
const association = registeredAssociations[i];
|
||||
if (!association.firstline) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let matches = firstLine.match(association.firstline);
|
||||
const matches = firstLine.match(association.firstline);
|
||||
if (matches && matches.length > 0) {
|
||||
return association.mime;
|
||||
}
|
||||
@@ -214,23 +213,6 @@ function guessMimeTypeByFirstline(firstLine: string): string {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function isBinaryMime(mimes: string): boolean;
|
||||
export function isBinaryMime(mimes: string[]): boolean;
|
||||
export function isBinaryMime(mimes: any): boolean {
|
||||
if (!mimes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mimeVals: string[];
|
||||
if (types.isArray(mimes)) {
|
||||
mimeVals = (<string[]>mimes);
|
||||
} else {
|
||||
mimeVals = (<string>mimes).split(',').map((mime) => mime.trim());
|
||||
}
|
||||
|
||||
return mimeVals.indexOf(MIME_BINARY) >= 0;
|
||||
}
|
||||
|
||||
export function isUnspecific(mime: string[] | string): boolean {
|
||||
if (!mime) {
|
||||
return true;
|
||||
@@ -245,7 +227,7 @@ export function isUnspecific(mime: string[] | string): boolean {
|
||||
|
||||
export function suggestFilename(langId: string, prefix: string): string {
|
||||
for (let i = 0; i < registeredAssociations.length; i++) {
|
||||
let association = registeredAssociations[i];
|
||||
const association = registeredAssociations[i];
|
||||
if (association.userConfigured) {
|
||||
continue; // only support registered ones
|
||||
}
|
||||
|
||||
@@ -41,4 +41,6 @@ export namespace Schemas {
|
||||
export const mailto: string = 'mailto';
|
||||
|
||||
export const untitled: string = 'untitled';
|
||||
|
||||
export const data: string = 'data';
|
||||
}
|
||||
|
||||
@@ -7,6 +7,15 @@
|
||||
|
||||
import types = require('vs/base/common/types');
|
||||
|
||||
export function clamp(value: number, min: number, max: number): number {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
export function rot(index: number, modulo: number): number {
|
||||
return (modulo + (index % modulo)) % modulo;
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
export type NumberCallback = (index: number) => void;
|
||||
|
||||
export function count(to: number, callback: NumberCallback): void;
|
||||
@@ -46,12 +55,3 @@ export function countToArray(fromOrTo: number, to?: number): number[] {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
export function clamp(value: number, min: number, max: number): number {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
export function rot(index: number, modulo: number): number {
|
||||
return (modulo + (index % modulo)) % modulo;
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { isObject, isUndefinedOrNull, isArray } from 'vs/base/common/types';
|
||||
|
||||
export function clone<T>(obj: T): T {
|
||||
export function deepClone<T>(obj: T): T {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
@@ -15,23 +15,8 @@ export function clone<T>(obj: T): T {
|
||||
// See https://github.com/Microsoft/TypeScript/issues/10990
|
||||
return obj as any;
|
||||
}
|
||||
const result = (Array.isArray(obj)) ? <any>[] : <any>{};
|
||||
Object.keys(obj).forEach(key => {
|
||||
if (obj[key] && typeof obj[key] === 'object') {
|
||||
result[key] = clone(obj[key]);
|
||||
} else {
|
||||
result[key] = obj[key];
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function deepClone<T>(obj: T): T {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
const result = (Array.isArray(obj)) ? <any>[] : <any>{};
|
||||
Object.getOwnPropertyNames(obj).forEach(key => {
|
||||
const result: any = Array.isArray(obj) ? [] : {};
|
||||
Object.keys(obj).forEach((key: keyof T) => {
|
||||
if (obj[key] && typeof obj[key] === 'object') {
|
||||
result[key] = deepClone(obj[key]);
|
||||
} else {
|
||||
@@ -41,7 +26,27 @@ export function deepClone<T>(obj: T): T {
|
||||
return result;
|
||||
}
|
||||
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
export function deepFreeze<T>(obj: T): T {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
const stack: any[] = [obj];
|
||||
while (stack.length > 0) {
|
||||
let obj = stack.shift();
|
||||
Object.freeze(obj);
|
||||
for (const key in obj) {
|
||||
if (_hasOwnProperty.call(obj, key)) {
|
||||
let prop = obj[key];
|
||||
if (typeof prop === 'object' && !Object.isFrozen(prop)) {
|
||||
stack.push(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
const _hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
export function cloneAndChange(obj: any, changer: (orig: any) => any): any {
|
||||
return _cloneAndChange(obj, changer, []);
|
||||
@@ -72,8 +77,8 @@ function _cloneAndChange(obj: any, changer: (orig: any) => any, encounteredObjec
|
||||
encounteredObjects.push(obj);
|
||||
const r2 = {};
|
||||
for (let i2 in obj) {
|
||||
if (hasOwnProperty.call(obj, i2)) {
|
||||
r2[i2] = _cloneAndChange(obj[i2], changer, encounteredObjects);
|
||||
if (_hasOwnProperty.call(obj, i2)) {
|
||||
(r2 as any)[i2] = _cloneAndChange(obj[i2], changer, encounteredObjects);
|
||||
}
|
||||
}
|
||||
encounteredObjects.pop();
|
||||
@@ -115,10 +120,6 @@ export function assign(destination: any, ...sources: any[]): any {
|
||||
return destination;
|
||||
}
|
||||
|
||||
export function toObject<T>(arr: T[], keyMap: (t: T) => string): { [key: string]: T } {
|
||||
return arr.reduce((o, d) => assign(o, { [keyMap(d)]: d }), Object.create(null));
|
||||
}
|
||||
|
||||
export function equals(one: any, other: any): boolean {
|
||||
if (one === other) {
|
||||
return true;
|
||||
@@ -172,12 +173,6 @@ export function equals(one: any, other: any): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function ensureProperty(obj: any, property: string, defaultValue: any) {
|
||||
if (typeof obj[property] === 'undefined') {
|
||||
obj[property] = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
export function arrayToHash(array: any[]) {
|
||||
const result: any = {};
|
||||
for (let i = 0; i < array.length; ++i) {
|
||||
@@ -206,34 +201,6 @@ export function createKeywordMatcher(arr: string[], caseInsensitive: boolean = f
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Started from TypeScript's __extends function to make a type a subclass of a specific class.
|
||||
* Modified to work with properties already defined on the derivedClass, since we can't get TS
|
||||
* to call this method before the constructor definition.
|
||||
*/
|
||||
export function derive(baseClass: any, derivedClass: any): void {
|
||||
for (let prop in baseClass) {
|
||||
if (baseClass.hasOwnProperty(prop)) {
|
||||
derivedClass[prop] = baseClass[prop];
|
||||
}
|
||||
}
|
||||
|
||||
derivedClass = derivedClass || function () { };
|
||||
const basePrototype = baseClass.prototype;
|
||||
const derivedPrototype = derivedClass.prototype;
|
||||
derivedClass.prototype = Object.create(basePrototype);
|
||||
|
||||
for (let prop in derivedPrototype) {
|
||||
if (derivedPrototype.hasOwnProperty(prop)) {
|
||||
// handle getters and setters properly
|
||||
Object.defineProperty(derivedClass.prototype, prop, Object.getOwnPropertyDescriptor(derivedPrototype, prop));
|
||||
}
|
||||
}
|
||||
|
||||
// Cast to any due to Bug 16188:PropertyDescriptor set and get function should be optional.
|
||||
Object.defineProperty(derivedClass.prototype, 'constructor', <any>{ value: derivedClass, writable: true, configurable: true, enumerable: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls JSON.Stringify with a replacer to break apart any circular references.
|
||||
* This prevents JSON.stringify from throwing the exception
|
||||
@@ -287,4 +254,4 @@ export function distinct(base: obj, target: obj): obj {
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export class PagedModel<T> implements IPagedModel<T> {
|
||||
|
||||
get length(): number { return this.pager.total; }
|
||||
|
||||
constructor(private arg: IPager<T> | T[], private pageTimeout: number = 500) {
|
||||
constructor(arg: IPager<T> | T[], private pageTimeout: number = 500) {
|
||||
this.pager = isArray(arg) ? singlePagePager<T>(arg) : arg;
|
||||
|
||||
this.pages = [{ isResolved: true, promise: null, promiseIndexes: new Set<number>(), elements: this.pager.firstPage.slice() }];
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
'use strict';
|
||||
|
||||
import * as Types from 'vs/base/common/types';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
|
||||
export enum ValidationState {
|
||||
OK = 0,
|
||||
@@ -49,14 +48,6 @@ export interface IProblemReporter {
|
||||
status: ValidationStatus;
|
||||
}
|
||||
|
||||
export class NullProblemReporter implements IProblemReporter {
|
||||
info(message: string): void { };
|
||||
warn(message: string): void { };
|
||||
error(message: string): void { };
|
||||
fatal(message: string): void { };
|
||||
status: ValidationStatus = new ValidationStatus();
|
||||
}
|
||||
|
||||
export abstract class Parser {
|
||||
|
||||
private _problemReporter: IProblemReporter;
|
||||
@@ -89,30 +80,8 @@ export abstract class Parser {
|
||||
this._problemReporter.fatal(message);
|
||||
}
|
||||
|
||||
protected is(value: any, func: (value: any) => boolean, wrongTypeState?: ValidationState, wrongTypeMessage?: string, undefinedState?: ValidationState, undefinedMessage?: string): boolean {
|
||||
if (Types.isUndefined(value)) {
|
||||
if (undefinedState) {
|
||||
this._problemReporter.status.state = undefinedState;
|
||||
}
|
||||
if (undefinedMessage) {
|
||||
this._problemReporter.info(undefinedMessage);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!func(value)) {
|
||||
if (wrongTypeState) {
|
||||
this._problemReporter.status.state = wrongTypeState;
|
||||
}
|
||||
if (wrongTypeMessage) {
|
||||
this.info(wrongTypeMessage);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static merge<T>(destination: T, source: T, overwrite: boolean): void {
|
||||
Object.keys(source).forEach((key) => {
|
||||
Object.keys(source).forEach((key: keyof T) => {
|
||||
let destValue = destination[key];
|
||||
let sourceValue = source[key];
|
||||
if (Types.isUndefined(sourceValue)) {
|
||||
@@ -131,87 +100,4 @@ export abstract class Parser {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface ISystemVariables {
|
||||
resolve(value: string): string;
|
||||
resolve(value: string[]): string[];
|
||||
resolve(value: IStringDictionary<string>): IStringDictionary<string>;
|
||||
resolve(value: IStringDictionary<string[]>): IStringDictionary<string[]>;
|
||||
resolve(value: IStringDictionary<IStringDictionary<string>>): IStringDictionary<IStringDictionary<string>>;
|
||||
resolveAny<T>(value: T): T;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export abstract class AbstractSystemVariables implements ISystemVariables {
|
||||
|
||||
public resolve(value: string): string;
|
||||
public resolve(value: string[]): string[];
|
||||
public resolve(value: IStringDictionary<string>): IStringDictionary<string>;
|
||||
public resolve(value: IStringDictionary<string[]>): IStringDictionary<string[]>;
|
||||
public resolve(value: IStringDictionary<IStringDictionary<string>>): IStringDictionary<IStringDictionary<string>>;
|
||||
public resolve(value: any): any {
|
||||
if (Types.isString(value)) {
|
||||
return this.resolveString(value);
|
||||
} else if (Types.isArray(value)) {
|
||||
return this.__resolveArray(value);
|
||||
} else if (Types.isObject(value)) {
|
||||
return this.__resolveLiteral(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
resolveAny<T>(value: T): T;
|
||||
resolveAny<T>(value: any): any {
|
||||
if (Types.isString(value)) {
|
||||
return this.resolveString(value);
|
||||
} else if (Types.isArray(value)) {
|
||||
return this.__resolveAnyArray(value);
|
||||
} else if (Types.isObject(value)) {
|
||||
return this.__resolveAnyLiteral(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
protected resolveString(value: string): string {
|
||||
let regexp = /\$\{(.*?)\}/g;
|
||||
return value.replace(regexp, (match: string, name: string) => {
|
||||
let newValue = (<any>this)[name];
|
||||
if (Types.isString(newValue)) {
|
||||
return newValue;
|
||||
} else {
|
||||
return match && match.indexOf('env.') > 0 ? '' : match;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private __resolveLiteral(values: IStringDictionary<string | IStringDictionary<string> | string[]>): IStringDictionary<string | IStringDictionary<string> | string[]> {
|
||||
let result: IStringDictionary<string | IStringDictionary<string> | string[]> = Object.create(null);
|
||||
Object.keys(values).forEach(key => {
|
||||
let value = values[key];
|
||||
result[key] = <any>this.resolve(<any>value);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private __resolveAnyLiteral<T>(values: T): T;
|
||||
private __resolveAnyLiteral<T>(values: any): any {
|
||||
let result: IStringDictionary<string | IStringDictionary<string> | string[]> = Object.create(null);
|
||||
Object.keys(values).forEach(key => {
|
||||
let value = values[key];
|
||||
result[key] = <any>this.resolveAny(<any>value);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private __resolveArray(value: string[]): string[] {
|
||||
return value.map(s => this.resolveString(s));
|
||||
}
|
||||
|
||||
private __resolveAnyArray<T>(value: T[]): T[];
|
||||
private __resolveAnyArray(value: any[]): any[] {
|
||||
return value.map(s => this.resolveAny(s));
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { isLinux, isWindows } from 'vs/base/common/platform';
|
||||
import { fill } from 'vs/base/common/arrays';
|
||||
import { rtrim, beginsWithIgnoreCase, equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { beginsWithIgnoreCase, equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
/**
|
||||
@@ -19,35 +18,6 @@ export const sep = '/';
|
||||
*/
|
||||
export const nativeSep = isWindows ? '\\' : '/';
|
||||
|
||||
export function relative(from: string, to: string): string {
|
||||
// ignore trailing slashes
|
||||
const originalNormalizedFrom = rtrim(normalize(from), sep);
|
||||
const originalNormalizedTo = rtrim(normalize(to), sep);
|
||||
|
||||
// we're assuming here that any non=linux OS is case insensitive
|
||||
// so we must compare each part in its lowercase form
|
||||
const normalizedFrom = isLinux ? originalNormalizedFrom : originalNormalizedFrom.toLowerCase();
|
||||
const normalizedTo = isLinux ? originalNormalizedTo : originalNormalizedTo.toLowerCase();
|
||||
|
||||
const fromParts = normalizedFrom.split(sep);
|
||||
const toParts = normalizedTo.split(sep);
|
||||
|
||||
let i = 0, max = Math.min(fromParts.length, toParts.length);
|
||||
|
||||
for (; i < max; i++) {
|
||||
if (fromParts[i] !== toParts[i]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const result = [
|
||||
...fill(fromParts.length - i, () => '..'),
|
||||
...originalNormalizedTo.split(sep).slice(i)
|
||||
];
|
||||
|
||||
return result.join(sep);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the directory name of a path.
|
||||
*/
|
||||
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface PerformanceEntry {
|
||||
readonly type: 'mark' | 'measure';
|
||||
readonly name: string;
|
||||
readonly startTime: number;
|
||||
readonly duration: number;
|
||||
}
|
||||
|
||||
export function mark(name: string): void;
|
||||
export function measure(name: string, from?: string, to?: string): void;
|
||||
|
||||
/**
|
||||
* Time something, shorthant for `mark` and `measure`
|
||||
*/
|
||||
export function time(name: string): { stop(): void };
|
||||
|
||||
/**
|
||||
* All entries filtered by type and sorted by `startTime`.
|
||||
*/
|
||||
export function getEntries(type: 'mark' | 'measure'): PerformanceEntry[];
|
||||
|
||||
|
||||
type ExportData = any[];
|
||||
export function importEntries(data: ExportData): void;
|
||||
export function exportEntries(): ExportData;
|
||||
@@ -0,0 +1,111 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/*global define*/
|
||||
|
||||
// This module can be loaded in an amd and commonjs-context.
|
||||
// Because we want both instances to use the same perf-data
|
||||
// we store them globally
|
||||
// stores data as 'type','name','startTime','duration'
|
||||
global._performanceEntries = global._performanceEntries || [];
|
||||
|
||||
if (typeof define !== "function" && typeof module === "object" && typeof module.exports === "object") {
|
||||
// this is commonjs, fake amd
|
||||
global.define = function (dep, callback) {
|
||||
module.exports = callback();
|
||||
global.define = undefined;
|
||||
};
|
||||
}
|
||||
|
||||
define([], function () {
|
||||
|
||||
// const _now = global.performance && performance.now ? performance.now : Date.now
|
||||
const _now = Date.now;
|
||||
|
||||
function importEntries(entries) {
|
||||
global._performanceEntries.splice(0, 0, ...entries);
|
||||
}
|
||||
|
||||
function exportEntries() {
|
||||
return global._performanceEntries.splice(0);
|
||||
}
|
||||
|
||||
function getEntries(type) {
|
||||
const result = [];
|
||||
const entries = global._performanceEntries;
|
||||
for (let i = 0; i < entries.length; i += 4) {
|
||||
if (entries[i] === type) {
|
||||
result.push({
|
||||
type: entries[i],
|
||||
name: entries[i + 1],
|
||||
startTime: entries[i + 2],
|
||||
duration: entries[i + 3],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result.sort((a, b) => {
|
||||
return a.startTime - b.startTime;
|
||||
});
|
||||
}
|
||||
|
||||
function mark(name) {
|
||||
global._performanceEntries.push('mark', name, _now(), 0);
|
||||
if (typeof console.timeStamp === 'function') {
|
||||
console.timeStamp(name);
|
||||
}
|
||||
}
|
||||
|
||||
function time(name) {
|
||||
let from = `${name}/start`;
|
||||
mark(from);
|
||||
return { stop() { measure(name, from); } };
|
||||
}
|
||||
|
||||
function measure(name, from, to) {
|
||||
|
||||
let startTime;
|
||||
let duration;
|
||||
let now = _now();
|
||||
|
||||
if (!from) {
|
||||
startTime = now;
|
||||
} else {
|
||||
startTime = _getLastStartTime(from);
|
||||
}
|
||||
|
||||
if (!to) {
|
||||
duration = now - startTime;
|
||||
} else {
|
||||
duration = _getLastStartTime(to) - startTime;
|
||||
}
|
||||
|
||||
global._performanceEntries.push('measure', name, startTime, duration);
|
||||
}
|
||||
|
||||
function _getLastStartTime(name) {
|
||||
const entries = global._performanceEntries;
|
||||
for (let i = entries.length - 1; i >= 0; i -= 4) {
|
||||
if (entries[i - 2] === name) {
|
||||
return entries[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(name + ' not found');
|
||||
}
|
||||
|
||||
var exports = {
|
||||
mark: mark,
|
||||
measure: measure,
|
||||
time: time,
|
||||
getEntries: getEntries,
|
||||
importEntries: importEntries,
|
||||
exportEntries: exportEntries
|
||||
};
|
||||
|
||||
return exports;
|
||||
});
|
||||
@@ -127,9 +127,6 @@ interface IGlobals {
|
||||
const _globals = <IGlobals>(typeof self === 'object' ? self : global);
|
||||
export const globals: any = _globals;
|
||||
|
||||
export function hasWebWorkerSupport(): boolean {
|
||||
return typeof _globals.Worker !== 'undefined';
|
||||
}
|
||||
export const setTimeout = _globals.setTimeout.bind(_globals);
|
||||
export const clearTimeout = _globals.clearTimeout.bind(_globals);
|
||||
|
||||
|
||||
@@ -4,15 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import NLS = require('vs/nls');
|
||||
|
||||
import * as Objects from 'vs/base/common/objects';
|
||||
import * as Platform from 'vs/base/common/platform';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import * as Types from 'vs/base/common/types';
|
||||
|
||||
import { ValidationState, IProblemReporter, Parser } from 'vs/base/common/parsers';
|
||||
|
||||
/**
|
||||
* Options to be passed to the external program or shell.
|
||||
*/
|
||||
@@ -94,159 +85,3 @@ export enum TerminateResponseCode {
|
||||
AccessDenied = 2,
|
||||
ProcessNotFound = 3,
|
||||
}
|
||||
|
||||
export namespace Config {
|
||||
/**
|
||||
* Options to be passed to the external program or shell
|
||||
*/
|
||||
export interface CommandOptions {
|
||||
/**
|
||||
* The current working directory of the executed program or shell.
|
||||
* If omitted VSCode's current workspace root is used.
|
||||
*/
|
||||
cwd?: string;
|
||||
|
||||
/**
|
||||
* The additional environment of the executed program or shell. If omitted
|
||||
* the parent process' environment is used.
|
||||
*/
|
||||
env?: IStringDictionary<string>;
|
||||
|
||||
/**
|
||||
* Index signature
|
||||
*/
|
||||
[key: string]: string | string[] | IStringDictionary<string>;
|
||||
}
|
||||
|
||||
export interface BaseExecutable {
|
||||
/**
|
||||
* The command to be executed. Can be an external program or a shell
|
||||
* command.
|
||||
*/
|
||||
command?: string;
|
||||
|
||||
/**
|
||||
* Specifies whether the command is a shell command and therefore must
|
||||
* be executed in a shell interpreter (e.g. cmd.exe, bash, ...).
|
||||
*
|
||||
* Defaults to false if omitted.
|
||||
*/
|
||||
isShellCommand?: boolean;
|
||||
|
||||
/**
|
||||
* The arguments passed to the command. Can be omitted.
|
||||
*/
|
||||
args?: string[];
|
||||
|
||||
/**
|
||||
* The command options used when the command is executed. Can be omitted.
|
||||
*/
|
||||
options?: CommandOptions;
|
||||
}
|
||||
|
||||
export interface Executable extends BaseExecutable {
|
||||
|
||||
/**
|
||||
* Windows specific executable configuration
|
||||
*/
|
||||
windows?: BaseExecutable;
|
||||
|
||||
/**
|
||||
* Mac specific executable configuration
|
||||
*/
|
||||
osx?: BaseExecutable;
|
||||
|
||||
/**
|
||||
* Linux specific executable configuration
|
||||
*/
|
||||
linux?: BaseExecutable;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export interface ParserOptions {
|
||||
globals?: Executable;
|
||||
emptyCommand?: boolean;
|
||||
noDefaults?: boolean;
|
||||
}
|
||||
|
||||
export class ExecutableParser extends Parser {
|
||||
|
||||
constructor(logger: IProblemReporter) {
|
||||
super(logger);
|
||||
}
|
||||
|
||||
public parse(json: Config.Executable, parserOptions: ParserOptions = { globals: null, emptyCommand: false, noDefaults: false }): Executable {
|
||||
let result = this.parseExecutable(json, parserOptions.globals);
|
||||
if (this.problemReporter.status.isFatal()) {
|
||||
return result;
|
||||
}
|
||||
let osExecutable: Executable;
|
||||
if (json.windows && Platform.platform === Platform.Platform.Windows) {
|
||||
osExecutable = this.parseExecutable(json.windows);
|
||||
} else if (json.osx && Platform.platform === Platform.Platform.Mac) {
|
||||
osExecutable = this.parseExecutable(json.osx);
|
||||
} else if (json.linux && Platform.platform === Platform.Platform.Linux) {
|
||||
osExecutable = this.parseExecutable(json.linux);
|
||||
}
|
||||
if (osExecutable) {
|
||||
result = ExecutableParser.mergeExecutable(result, osExecutable);
|
||||
}
|
||||
if ((!result || !result.command) && !parserOptions.emptyCommand) {
|
||||
this.fatal(NLS.localize('ExecutableParser.commandMissing', 'Error: executable info must define a command of type string.'));
|
||||
return null;
|
||||
}
|
||||
if (!parserOptions.noDefaults) {
|
||||
Parser.merge(result, {
|
||||
command: undefined,
|
||||
isShellCommand: false,
|
||||
args: [],
|
||||
options: {}
|
||||
}, false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public parseExecutable(json: Config.BaseExecutable, globals?: Executable): Executable {
|
||||
let command: string = undefined;
|
||||
let isShellCommand: boolean = undefined;
|
||||
let args: string[] = undefined;
|
||||
let options: CommandOptions = undefined;
|
||||
|
||||
if (this.is(json.command, Types.isString)) {
|
||||
command = json.command;
|
||||
}
|
||||
if (this.is(json.isShellCommand, Types.isBoolean, ValidationState.Warning, NLS.localize('ExecutableParser.isShellCommand', 'Warning: isShellCommand must be of type boolean. Ignoring value {0}.', json.isShellCommand))) {
|
||||
isShellCommand = json.isShellCommand;
|
||||
}
|
||||
if (this.is(json.args, Types.isStringArray, ValidationState.Warning, NLS.localize('ExecutableParser.args', 'Warning: args must be of type string[]. Ignoring value {0}.', json.isShellCommand))) {
|
||||
args = json.args.slice(0);
|
||||
}
|
||||
if (this.is(json.options, Types.isObject)) {
|
||||
options = this.parseCommandOptions(json.options);
|
||||
}
|
||||
return { command, isShellCommand, args, options };
|
||||
}
|
||||
|
||||
private parseCommandOptions(json: Config.CommandOptions): CommandOptions {
|
||||
let result: CommandOptions = {};
|
||||
if (!json) {
|
||||
return result;
|
||||
}
|
||||
if (this.is(json.cwd, Types.isString, ValidationState.Warning, NLS.localize('ExecutableParser.invalidCWD', 'Warning: options.cwd must be of type string. Ignoring value {0}.', json.cwd))) {
|
||||
result.cwd = json.cwd;
|
||||
}
|
||||
if (!Types.isUndefined(json.env)) {
|
||||
result.env = Objects.clone(json.env);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static mergeExecutable(executable: Executable, other: Executable): Executable {
|
||||
if (!executable) {
|
||||
return other;
|
||||
}
|
||||
Parser.merge(executable, other, true);
|
||||
return executable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface ISplice<T> {
|
||||
readonly start: number;
|
||||
readonly deleteCount: number;
|
||||
readonly toInsert: T[];
|
||||
}
|
||||
|
||||
export interface ISpliceable<T> {
|
||||
splice(start: number, deleteCount: number, toInsert: T[]): void;
|
||||
}
|
||||
|
||||
export interface ISequence<T> {
|
||||
readonly elements: T[];
|
||||
readonly onDidSplice: Event<ISplice<T>>;
|
||||
}
|
||||
|
||||
export class Sequence<T> implements ISequence<T>, ISpliceable<T> {
|
||||
|
||||
readonly elements: T[] = [];
|
||||
|
||||
private _onDidSplice = new Emitter<ISplice<T>>();
|
||||
readonly onDidSplice: Event<ISplice<T>> = this._onDidSplice.event;
|
||||
|
||||
splice(start: number, deleteCount: number, toInsert: T[] = []): void {
|
||||
this.elements.splice(start, deleteCount, ...toInsert);
|
||||
this._onDidSplice.fire({ start, deleteCount, toInsert });
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { BoundedMap } from 'vs/base/common/map';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
/**
|
||||
@@ -243,18 +243,18 @@ export function regExpContainsBackreference(regexpValue: string): boolean {
|
||||
*/
|
||||
export const canNormalize = typeof ((<any>'').normalize) === 'function';
|
||||
|
||||
const nfcCache = new BoundedMap<string>(10000); // bounded to 10000 elements
|
||||
const nfcCache = new LRUCache<string, string>(10000); // bounded to 10000 elements
|
||||
export function normalizeNFC(str: string): string {
|
||||
return normalize(str, 'NFC', nfcCache);
|
||||
}
|
||||
|
||||
const nfdCache = new BoundedMap<string>(10000); // bounded to 10000 elements
|
||||
const nfdCache = new LRUCache<string, string>(10000); // bounded to 10000 elements
|
||||
export function normalizeNFD(str: string): string {
|
||||
return normalize(str, 'NFD', nfdCache);
|
||||
}
|
||||
|
||||
const nonAsciiCharactersPattern = /[^\u0000-\u0080]/;
|
||||
function normalize(str: string, form: string, normalizedCache: BoundedMap<string>): string {
|
||||
function normalize(str: string, form: string, normalizedCache: LRUCache<string, string>): string {
|
||||
if (!canNormalize || !str) {
|
||||
return str;
|
||||
}
|
||||
@@ -618,81 +618,27 @@ export function isFullWidthCharacter(charCode: number): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference score for two strings. More similar strings have a higher score.
|
||||
* We use largest common subsequence dynamic programming approach but penalize in the end for length differences.
|
||||
* Strings that have a large length difference will get a bad default score 0.
|
||||
* Complexity - both time and space O(first.length * second.length)
|
||||
* Dynamic programming LCS computation http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
|
||||
*
|
||||
* @param first a string
|
||||
* @param second a string
|
||||
*/
|
||||
export function difference(first: string, second: string, maxLenDelta: number = 4): number {
|
||||
let lengthDifference = Math.abs(first.length - second.length);
|
||||
// We only compute score if length of the currentWord and length of entry.name are similar.
|
||||
if (lengthDifference > maxLenDelta) {
|
||||
return 0;
|
||||
}
|
||||
// Initialize LCS (largest common subsequence) matrix.
|
||||
let LCS: number[][] = [];
|
||||
let zeroArray: number[] = [];
|
||||
let i: number, j: number;
|
||||
for (i = 0; i < second.length + 1; ++i) {
|
||||
zeroArray.push(0);
|
||||
}
|
||||
for (i = 0; i < first.length + 1; ++i) {
|
||||
LCS.push(zeroArray);
|
||||
}
|
||||
for (i = 1; i < first.length + 1; ++i) {
|
||||
for (j = 1; j < second.length + 1; ++j) {
|
||||
if (first[i - 1] === second[j - 1]) {
|
||||
LCS[i][j] = LCS[i - 1][j - 1] + 1;
|
||||
} else {
|
||||
LCS[i][j] = Math.max(LCS[i - 1][j], LCS[i][j - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return LCS[first.length][second.length] - Math.sqrt(lengthDifference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array in which every entry is the offset of a
|
||||
* line. There is always one entry which is zero.
|
||||
*/
|
||||
export function computeLineStarts(text: string): number[] {
|
||||
let regexp = /\r\n|\r|\n/g,
|
||||
ret: number[] = [0],
|
||||
match: RegExpExecArray;
|
||||
while ((match = regexp.exec(text))) {
|
||||
ret.push(regexp.lastIndex);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a string and a max length returns a shorted version. Shorting
|
||||
* happens at favorable positions - such as whitespace or punctuation characters.
|
||||
*/
|
||||
export function lcut(text: string, n: number): string {
|
||||
|
||||
export function lcut(text: string, n: number) {
|
||||
if (text.length < n) {
|
||||
return text;
|
||||
}
|
||||
|
||||
let segments = text.split(/\b/),
|
||||
count = 0;
|
||||
|
||||
for (let i = segments.length - 1; i >= 0; i--) {
|
||||
count += segments[i].length;
|
||||
|
||||
if (count > n) {
|
||||
segments.splice(0, i);
|
||||
const re = /\b/g;
|
||||
let i = 0;
|
||||
while (re.test(text)) {
|
||||
if (text.length - re.lastIndex < n) {
|
||||
break;
|
||||
}
|
||||
|
||||
i = re.lastIndex;
|
||||
re.lastIndex += 1;
|
||||
}
|
||||
|
||||
return segments.join(empty).replace(/^\s/, empty);
|
||||
return text.substring(i).replace(/^\s/, empty);
|
||||
}
|
||||
|
||||
// Escape codes
|
||||
@@ -723,25 +669,6 @@ export function stripUTF8BOM(str: string): string {
|
||||
return startsWithUTF8BOM(str) ? str.substr(1) : str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends two strings. If the appended result is longer than maxLength,
|
||||
* trims the start of the result and replaces it with '...'.
|
||||
*/
|
||||
export function appendWithLimit(first: string, second: string, maxLength: number): string {
|
||||
const newLength = first.length + second.length;
|
||||
if (newLength > maxLength) {
|
||||
first = '...' + first.substr(newLength - maxLength);
|
||||
}
|
||||
if (second.length > maxLength) {
|
||||
first += second.substr(second.length - maxLength);
|
||||
} else {
|
||||
first += second;
|
||||
}
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
|
||||
export function safeBtoa(str: string): string {
|
||||
return btoa(encodeURIComponent(str)); // we use encodeURIComponent because btoa fails for non Latin 1 values
|
||||
}
|
||||
@@ -784,4 +711,4 @@ export function fuzzyContains(target: string, query: string): boolean {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
const _typeof = {
|
||||
number: 'number',
|
||||
string: 'string',
|
||||
@@ -149,7 +147,7 @@ export function validateConstraint(arg: any, constraint: TypeConstraint): void {
|
||||
if (arg instanceof constraint) {
|
||||
return;
|
||||
}
|
||||
if (arg && arg.constructor === constraint) {
|
||||
if (!isUndefinedOrNull(arg) && arg.constructor === constraint) {
|
||||
return;
|
||||
}
|
||||
if (constraint.length === 1 && constraint.call(undefined, arg) === true) {
|
||||
@@ -169,52 +167,3 @@ export function create(ctor: Function, ...args: any[]): any {
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
export interface IFunction0<T> {
|
||||
(): T;
|
||||
}
|
||||
export interface IFunction1<A1, T> {
|
||||
(a1: A1): T;
|
||||
}
|
||||
export interface IFunction2<A1, A2, T> {
|
||||
(a1: A1, a2: A2): T;
|
||||
}
|
||||
export interface IFunction3<A1, A2, A3, T> {
|
||||
(a1: A1, a2: A2, a3: A3): T;
|
||||
}
|
||||
export interface IFunction4<A1, A2, A3, A4, T> {
|
||||
(a1: A1, a2: A2, a3: A3, a4: A4): T;
|
||||
}
|
||||
export interface IFunction5<A1, A2, A3, A4, A5, T> {
|
||||
(a1: A1, a2: A2, a3: A3, a4: A4, a5: A5): T;
|
||||
}
|
||||
export interface IFunction6<A1, A2, A3, A4, A5, A6, T> {
|
||||
(a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6): T;
|
||||
}
|
||||
export interface IFunction7<A1, A2, A3, A4, A5, A6, A7, T> {
|
||||
(a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7): T;
|
||||
}
|
||||
export interface IFunction8<A1, A2, A3, A4, A5, A6, A7, A8, T> {
|
||||
(a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8): T;
|
||||
}
|
||||
|
||||
export interface IAction0 extends IFunction0<void> { }
|
||||
export interface IAction1<A1> extends IFunction1<A1, void> { }
|
||||
export interface IAction2<A1, A2> extends IFunction2<A1, A2, void> { }
|
||||
export interface IAction3<A1, A2, A3> extends IFunction3<A1, A2, A3, void> { }
|
||||
export interface IAction4<A1, A2, A3, A4> extends IFunction4<A1, A2, A3, A4, void> { }
|
||||
export interface IAction5<A1, A2, A3, A4, A5> extends IFunction5<A1, A2, A3, A4, A5, void> { }
|
||||
export interface IAction6<A1, A2, A3, A4, A5, A6> extends IFunction6<A1, A2, A3, A4, A5, A6, void> { }
|
||||
export interface IAction7<A1, A2, A3, A4, A5, A6, A7> extends IFunction7<A1, A2, A3, A4, A5, A6, A7, void> { }
|
||||
export interface IAction8<A1, A2, A3, A4, A5, A6, A7, A8> extends IFunction8<A1, A2, A3, A4, A5, A6, A7, A8, void> { }
|
||||
|
||||
export interface IAsyncFunction0<T> extends IFunction0<TPromise<T>> { }
|
||||
export interface IAsyncFunction1<A1, T> extends IFunction1<A1, TPromise<T>> { }
|
||||
export interface IAsyncFunction2<A1, A2, T> extends IFunction2<A1, A2, TPromise<T>> { }
|
||||
export interface IAsyncFunction3<A1, A2, A3, T> extends IFunction3<A1, A2, A3, TPromise<T>> { }
|
||||
export interface IAsyncFunction4<A1, A2, A3, A4, T> extends IFunction4<A1, A2, A3, A4, TPromise<T>> { }
|
||||
export interface IAsyncFunction5<A1, A2, A3, A4, A5, T> extends IFunction5<A1, A2, A3, A4, A5, TPromise<T>> { }
|
||||
export interface IAsyncFunction6<A1, A2, A3, A4, A5, A6, T> extends IFunction6<A1, A2, A3, A4, A5, A6, TPromise<T>> { }
|
||||
export interface IAsyncFunction7<A1, A2, A3, A4, A5, A6, A7, T> extends IFunction7<A1, A2, A3, A4, A5, A6, A7, TPromise<T>> { }
|
||||
export interface IAsyncFunction8<A1, A2, A3, A4, A5, A6, A7, A8, T> extends IFunction8<A1, A2, A3, A4, A5, A6, A7, A8, TPromise<T>> { }
|
||||
|
||||
|
||||
@@ -450,7 +450,7 @@ function _asFormatted(uri: URI, skipEncoding: boolean): string {
|
||||
}
|
||||
parts.push(encoder(path.substring(lastIdx, idx)), _slash);
|
||||
lastIdx = idx + 1;
|
||||
};
|
||||
}
|
||||
}
|
||||
if (query) {
|
||||
parts.push('?', encoder(query));
|
||||
|
||||
@@ -13,8 +13,6 @@ export interface UUID {
|
||||
* @returns the canonical representation in sets of hexadecimal numbers separated by dashes.
|
||||
*/
|
||||
asHex(): string;
|
||||
|
||||
equals(other: UUID): boolean;
|
||||
}
|
||||
|
||||
class ValueUUID implements UUID {
|
||||
@@ -26,17 +24,13 @@ class ValueUUID implements UUID {
|
||||
public asHex(): string {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public equals(other: UUID): boolean {
|
||||
return this.asHex() === other.asHex();
|
||||
}
|
||||
}
|
||||
|
||||
class V4UUID extends ValueUUID {
|
||||
|
||||
private static _chars = ['0', '1', '2', '3', '4', '5', '6', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
|
||||
private static readonly _chars = ['0', '1', '2', '3', '4', '5', '6', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
|
||||
|
||||
private static _timeHighBits = ['8', '9', 'a', 'b'];
|
||||
private static readonly _timeHighBits = ['8', '9', 'a', 'b'];
|
||||
|
||||
private static _oneOf(array: string[]): string {
|
||||
return array[Math.floor(array.length * Math.random())];
|
||||
|
||||
Vendored
+1
@@ -29,6 +29,7 @@ export declare class Promise<T = any, TProgress = any> {
|
||||
|
||||
public static as(value: null): Promise<null>;
|
||||
public static as(value: undefined): Promise<undefined>;
|
||||
public static as<T>(value: PromiseLike<T>): PromiseLike<T>;
|
||||
public static as<T, SomePromise extends PromiseLike<T>>(value: SomePromise): SomePromise;
|
||||
public static as<T>(value: T): Promise<T>;
|
||||
|
||||
|
||||
@@ -42,14 +42,14 @@ export class PolyfillPromise<T = any> implements Promise<T> {
|
||||
|
||||
constructor(winjsPromise: WinJSPromise);
|
||||
constructor(callback: (resolve: (value?: T) => void, reject: (err?: any) => void) => any);
|
||||
constructor(callback: WinJSPromise | ((resolve: (value?: T) => void, reject: (err?: any) => void) => any)) {
|
||||
constructor(initOrPromise: WinJSPromise | ((resolve: (value?: T) => void, reject: (err?: any) => void) => any)) {
|
||||
|
||||
if (WinJSPromise.is(callback)) {
|
||||
this._winjsPromise = callback;
|
||||
if (WinJSPromise.is(initOrPromise)) {
|
||||
this._winjsPromise = initOrPromise;
|
||||
} else {
|
||||
this._winjsPromise = new WinJSPromise((resolve, reject) => {
|
||||
let initializing = true;
|
||||
callback(function (value) {
|
||||
initOrPromise(function (value) {
|
||||
if (!initializing) {
|
||||
resolve(value);
|
||||
} else {
|
||||
@@ -68,10 +68,28 @@ export class PolyfillPromise<T = any> implements Promise<T> {
|
||||
}
|
||||
|
||||
then(onFulfilled?: any, onRejected?: any): PolyfillPromise {
|
||||
return new PolyfillPromise(this._winjsPromise.then(onFulfilled, onRejected));
|
||||
let sync = true;
|
||||
let promise = new PolyfillPromise(this._winjsPromise.then(
|
||||
onFulfilled && function (value) {
|
||||
if (!sync) {
|
||||
onFulfilled(value);
|
||||
} else {
|
||||
setImmediate(onFulfilled, value);
|
||||
}
|
||||
},
|
||||
onRejected && function (err) {
|
||||
if (!sync) {
|
||||
onFulfilled(err);
|
||||
} else {
|
||||
setImmediate(onFulfilled, err);
|
||||
}
|
||||
}
|
||||
));
|
||||
sync = false;
|
||||
return promise;
|
||||
}
|
||||
|
||||
catch(onRejected?: any): PolyfillPromise {
|
||||
return new PolyfillPromise(this._winjsPromise.then(null, onRejected));
|
||||
return this.then(null, onRejected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,6 @@ export class SimpleWorkerClient<T> extends Disposable {
|
||||
private _onModuleLoaded: TPromise<string[]>;
|
||||
private _protocol: SimpleWorkerProtocol;
|
||||
private _lazyProxy: TPromise<T>;
|
||||
private _lastRequestTimestamp = -1;
|
||||
|
||||
constructor(workerFactory: IWorkerFactory, moduleId: string) {
|
||||
super();
|
||||
@@ -244,7 +243,7 @@ export class SimpleWorkerClient<T> extends Disposable {
|
||||
this._onModuleLoaded.then((availableMethods: string[]) => {
|
||||
let proxy = <T>{};
|
||||
for (let i = 0; i < availableMethods.length; i++) {
|
||||
proxy[availableMethods[i]] = createProxyMethod(availableMethods[i], proxyMethodRequest);
|
||||
(proxy as any)[availableMethods[i]] = createProxyMethod(availableMethods[i], proxyMethodRequest);
|
||||
}
|
||||
lazyProxyFulfill(proxy);
|
||||
}, (e) => {
|
||||
@@ -270,14 +269,9 @@ export class SimpleWorkerClient<T> extends Disposable {
|
||||
return new ShallowCancelThenPromise(this._lazyProxy);
|
||||
}
|
||||
|
||||
public getLastRequestTimestamp(): number {
|
||||
return this._lastRequestTimestamp;
|
||||
}
|
||||
|
||||
private _request(method: string, args: any[]): TPromise<any> {
|
||||
return new TPromise<any>((c, e, p) => {
|
||||
this._onModuleLoaded.then(() => {
|
||||
this._lastRequestTimestamp = Date.now();
|
||||
this._protocol.sendMessage(method, args).then(c, e);
|
||||
}, e);
|
||||
}, () => {
|
||||
@@ -292,7 +286,7 @@ export class SimpleWorkerClient<T> extends Disposable {
|
||||
}
|
||||
|
||||
export interface IRequestHandler {
|
||||
_requestHandlerTrait: any;
|
||||
_requestHandlerBrand: any;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -153,7 +153,7 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
|
||||
|
||||
try {
|
||||
const watcher = extfs.watch(path, (type, file) => this.onConfigFileChange(type, file, isParentFolder));
|
||||
watcher.on('error', (code, signal) => this.options.onError(`Error watching ${path} for configuration changes (${code}, ${signal})`));
|
||||
watcher.on('error', (code: number, signal: string) => this.options.onError(`Error watching ${path} for configuration changes (${code}, ${signal})`));
|
||||
|
||||
this.disposables.push(toDisposable(() => {
|
||||
watcher.removeAllListeners();
|
||||
@@ -209,7 +209,7 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const value = this.cache ? this.cache[key] : void 0;
|
||||
const value = this.cache ? (this.cache as any)[key] : void 0;
|
||||
|
||||
return typeof value !== 'undefined' ? value : fallback;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
import stream = require('vs/base/node/stream');
|
||||
import iconv = require('iconv-lite');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { isLinux, isMacintosh } from 'vs/base/common/platform';
|
||||
import { exec } from 'child_process';
|
||||
|
||||
export const UTF8 = 'utf8';
|
||||
export const UTF8_with_bom = 'utf8bom';
|
||||
@@ -42,10 +44,6 @@ export function decodeStream(encoding: string): NodeJS.ReadWriteStream {
|
||||
return iconv.decodeStream(toNodeEncoding(encoding));
|
||||
}
|
||||
|
||||
export function encodeStream(encoding: string): NodeJS.ReadWriteStream {
|
||||
return iconv.encodeStream(toNodeEncoding(encoding));
|
||||
}
|
||||
|
||||
function toNodeEncoding(enc: string): string {
|
||||
if (enc === UTF8_with_bom) {
|
||||
return UTF8; // iconv does not distinguish UTF 8 with or without BOM, so we need to help it
|
||||
@@ -169,3 +167,88 @@ export function toCanonicalName(enc: string): string {
|
||||
return enc;
|
||||
}
|
||||
}
|
||||
|
||||
// https://ss64.com/nt/chcp.html
|
||||
const windowsTerminalEncodings = {
|
||||
'437': 'cp437', // United States
|
||||
'850': 'cp850', // Multilingual(Latin I)
|
||||
'852': 'cp852', // Slavic(Latin II)
|
||||
'855': 'cp855', // Cyrillic(Russian)
|
||||
'857': 'cp857', // Turkish
|
||||
'860': 'cp860', // Portuguese
|
||||
'861': 'cp861', // Icelandic
|
||||
'863': 'cp863', // Canadian - French
|
||||
'865': 'cp865', // Nordic
|
||||
'866': 'cp866', // Russian
|
||||
'869': 'cp869', // Modern Greek
|
||||
'1252': 'cp1252' // West European Latin
|
||||
};
|
||||
|
||||
export function resolveTerminalEncoding(verbose?: boolean): TPromise<string> {
|
||||
let rawEncodingPromise: TPromise<string>;
|
||||
|
||||
// Support a global environment variable to win over other mechanics
|
||||
const cliEncodingEnv = process.env['VSCODE_CLI_ENCODING'];
|
||||
if (cliEncodingEnv) {
|
||||
if (verbose) {
|
||||
console.log(`Found VSCODE_CLI_ENCODING variable: ${cliEncodingEnv}`);
|
||||
}
|
||||
|
||||
rawEncodingPromise = TPromise.as(cliEncodingEnv);
|
||||
}
|
||||
|
||||
// Linux/Mac: use "locale charmap" command
|
||||
else if (isLinux || isMacintosh) {
|
||||
rawEncodingPromise = new TPromise<string>(c => {
|
||||
if (verbose) {
|
||||
console.log('Running "locale charmap" to detect terminal encoding...');
|
||||
}
|
||||
|
||||
exec('locale charmap', (err, stdout, stderr) => c(stdout));
|
||||
});
|
||||
}
|
||||
|
||||
// Windows: educated guess
|
||||
else {
|
||||
rawEncodingPromise = new TPromise<string>(c => {
|
||||
if (verbose) {
|
||||
console.log('Running "chcp" to detect terminal encoding...');
|
||||
}
|
||||
|
||||
exec('chcp', (err, stdout, stderr) => {
|
||||
if (stdout) {
|
||||
const windowsTerminalEncodingKeys = Object.keys(windowsTerminalEncodings);
|
||||
for (let i = 0; i < windowsTerminalEncodingKeys.length; i++) {
|
||||
const key = windowsTerminalEncodingKeys[i];
|
||||
if (stdout.indexOf(key) >= 0) {
|
||||
return c(windowsTerminalEncodings[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c(void 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return rawEncodingPromise.then(rawEncoding => {
|
||||
if (verbose) {
|
||||
console.log(`Detected raw terminal encoding: ${rawEncoding}`);
|
||||
}
|
||||
|
||||
if (!rawEncoding || rawEncoding.toLowerCase() === 'utf-8' || rawEncoding.toLowerCase() === UTF8) {
|
||||
return UTF8;
|
||||
}
|
||||
|
||||
const iconvEncoding = toIconvLiteEncoding(rawEncoding);
|
||||
if (iconv.encodingExists(iconvEncoding)) {
|
||||
return iconvEncoding;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
console.log('Unsupported terminal encoding, falling back to UTF-8.');
|
||||
}
|
||||
|
||||
return UTF8;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export function fromEventEmitter<T>(emitter: EventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> {
|
||||
const fn = (...args) => 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;
|
||||
};
|
||||
+84
-58
@@ -12,6 +12,8 @@ import * as flow from 'vs/base/node/flow';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as paths from 'path';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { nfcall } from 'vs/base/common/async';
|
||||
|
||||
const loop = flow.loop;
|
||||
|
||||
@@ -41,80 +43,72 @@ export function readdir(path: string, callback: (error: Error, files: string[])
|
||||
return fs.readdir(path, callback);
|
||||
}
|
||||
|
||||
export function mkdirp(path: string, mode: number, callback: (error: Error) => void): void {
|
||||
fs.exists(path, exists => {
|
||||
if (exists) {
|
||||
return isDirectory(path, (err: Error, itIs?: boolean) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!itIs) {
|
||||
return callback(new Error('"' + path + '" is not a directory.'));
|
||||
}
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
mkdirp(paths.dirname(path), mode, (err: Error) => {
|
||||
if (err) { callback(err); return; }
|
||||
|
||||
if (mode) {
|
||||
fs.mkdir(path, mode, error => {
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
fs.chmod(path, mode, callback); // we need to explicitly chmod because of https://github.com/nodejs/node/issues/1104
|
||||
});
|
||||
} else {
|
||||
fs.mkdir(path, null, callback);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isDirectory(path: string, callback: (error: Error, isDirectory?: boolean) => void): void {
|
||||
fs.stat(path, (error, stat) => {
|
||||
if (error) { return callback(error); }
|
||||
|
||||
callback(null, stat.isDirectory());
|
||||
});
|
||||
}
|
||||
|
||||
export function copy(source: string, target: string, callback: (error: Error) => void, copiedSources?: { [path: string]: boolean }): void {
|
||||
if (!copiedSources) {
|
||||
copiedSources = Object.create(null);
|
||||
}
|
||||
|
||||
fs.stat(source, (error, stat) => {
|
||||
if (error) { return callback(error); }
|
||||
if (!stat.isDirectory()) { return pipeFs(source, target, stat.mode & 511, callback); }
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
if (!stat.isDirectory()) {
|
||||
return pipeFs(source, target, stat.mode & 511, callback);
|
||||
}
|
||||
|
||||
if (copiedSources[source]) {
|
||||
return callback(null); // escape when there are cycles (can happen with symlinks)
|
||||
} else {
|
||||
copiedSources[source] = true; // remember as copied
|
||||
}
|
||||
|
||||
mkdirp(target, stat.mode & 511, err => {
|
||||
copiedSources[source] = true; // remember as copied
|
||||
|
||||
const proceed = function () {
|
||||
readdir(source, (err, files) => {
|
||||
loop(files, (file: string, clb: (error: Error, result: string[]) => void) => {
|
||||
copy(paths.join(source, file), paths.join(target, file), (error: Error) => clb(error, void 0), copiedSources);
|
||||
}, callback);
|
||||
});
|
||||
};
|
||||
|
||||
mkdirp(target, stat.mode & 511).done(proceed, proceed);
|
||||
});
|
||||
}
|
||||
|
||||
export function mkdirp(path: string, mode?: number): TPromise<boolean> {
|
||||
const mkdir = () => nfcall(fs.mkdir, path, mode)
|
||||
.then(null, (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === 'EEXIST') {
|
||||
return nfcall(fs.stat, path)
|
||||
.then((stat: fs.Stats) => stat.isDirectory
|
||||
? null
|
||||
: TPromise.wrapError(new Error(`'${path}' exists and is not a directory.`)));
|
||||
}
|
||||
|
||||
return TPromise.wrapError<boolean>(err);
|
||||
});
|
||||
|
||||
// is root?
|
||||
if (path === paths.dirname(path)) {
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
return mkdir().then(null, (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === 'ENOENT') {
|
||||
return mkdirp(paths.dirname(path), mode).then(mkdir);
|
||||
}
|
||||
|
||||
return TPromise.wrapError<boolean>(err);
|
||||
});
|
||||
}
|
||||
|
||||
function pipeFs(source: string, target: string, mode: number, callback: (error: Error) => void): void {
|
||||
let callbackHandled = false;
|
||||
|
||||
let readStream = fs.createReadStream(source);
|
||||
let writeStream = fs.createWriteStream(target, { mode: mode });
|
||||
const readStream = fs.createReadStream(source);
|
||||
const writeStream = fs.createWriteStream(target, { mode: mode });
|
||||
|
||||
let onError = (error: Error) => {
|
||||
const onError = (error: Error) => {
|
||||
if (!callbackHandled) {
|
||||
callbackHandled = true;
|
||||
callback(error);
|
||||
@@ -163,7 +157,7 @@ export function del(path: string, tmpFolder: string, callback: (error: Error) =>
|
||||
return rmRecursive(path, callback);
|
||||
}
|
||||
|
||||
let pathInTemp = paths.join(tmpFolder, uuid.generateUuid());
|
||||
const pathInTemp = paths.join(tmpFolder, uuid.generateUuid());
|
||||
fs.rename(path, pathInTemp, (error: Error) => {
|
||||
if (error) {
|
||||
return rmRecursive(path, callback); // if rename fails, delete without tmp dir
|
||||
@@ -200,7 +194,7 @@ function rmRecursive(path: string, callback: (error: Error) => void): void {
|
||||
if (err || !stat) {
|
||||
callback(err);
|
||||
} else if (!stat.isDirectory() || stat.isSymbolicLink() /* !!! never recurse into links when deleting !!! */) {
|
||||
let mode = stat.mode;
|
||||
const mode = stat.mode;
|
||||
if (!(mode & 128)) { // 128 === 0200
|
||||
fs.chmod(path, mode | 128, (err: Error) => { // 128 === 0200
|
||||
if (err) {
|
||||
@@ -369,6 +363,35 @@ export function writeFileAndFlush(path: string, data: string | NodeBuffer, optio
|
||||
});
|
||||
}
|
||||
|
||||
export function writeFileAndFlushSync(path: string, data: string | NodeBuffer, options?: { mode?: number; flag?: string; }): void {
|
||||
if (!canFlush) {
|
||||
return fs.writeFileSync(path, data, options);
|
||||
}
|
||||
|
||||
if (!options) {
|
||||
options = { mode: 0o666, flag: 'w' };
|
||||
}
|
||||
|
||||
// Open the file with same flags and mode as fs.writeFile()
|
||||
const fd = fs.openSync(path, options.flag, options.mode);
|
||||
|
||||
try {
|
||||
|
||||
// It is valid to pass a fd handle to fs.writeFile() and this will keep the handle open!
|
||||
fs.writeFileSync(fd, data);
|
||||
|
||||
// Flush contents (not metadata) of the file to disk
|
||||
try {
|
||||
fs.fdatasyncSync(fd);
|
||||
} catch (syncError) {
|
||||
console.warn('[node.js fs] fdatasyncSync is now disabled for this session because it failed: ', syncError);
|
||||
canFlush = false;
|
||||
}
|
||||
} finally {
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from: https://github.com/Microsoft/vscode-node-debug/blob/master/src/node/pathUtilities.ts#L83
|
||||
*
|
||||
@@ -384,7 +407,7 @@ export function realcaseSync(path: string): string {
|
||||
return path;
|
||||
}
|
||||
|
||||
const name = paths.basename(path).toLowerCase();
|
||||
const name = (paths.basename(path) /* can be '' for windows drive letters */ || path).toLowerCase();
|
||||
try {
|
||||
const entries = readdirSync(dir);
|
||||
const found = entries.filter(e => e.toLowerCase() === name); // use a case insensitive search
|
||||
@@ -454,11 +477,14 @@ function normalizePath(path: string): string {
|
||||
export function watch(path: string, onChange: (type: string, path: string) => void): fs.FSWatcher {
|
||||
const watcher = fs.watch(path);
|
||||
watcher.on('change', (type, raw) => {
|
||||
let file = raw.toString();
|
||||
if (platform.isMacintosh) {
|
||||
// Mac: uses NFD unicode form on disk, but we want NFC
|
||||
// See also https://github.com/nodejs/node/issues/2165
|
||||
file = strings.normalizeNFC(file);
|
||||
let file: string = null;
|
||||
if (raw) { // https://github.com/Microsoft/vscode/issues/38191
|
||||
file = raw.toString();
|
||||
if (platform.isMacintosh) {
|
||||
// Mac: uses NFD unicode form on disk, but we want NFC
|
||||
// See also https://github.com/nodejs/node/issues/2165
|
||||
file = strings.normalizeNFC(file);
|
||||
}
|
||||
}
|
||||
|
||||
onChange(type, file);
|
||||
|
||||
+15
-12
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as getmac from 'getmac';
|
||||
import * as crypto from 'crypto';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as uuid from 'vs/base/common/uuid';
|
||||
@@ -86,17 +84,22 @@ export function getMachineId(): TPromise<string> {
|
||||
|
||||
function getMacMachineId(): TPromise<string> {
|
||||
return new TPromise<string>(resolve => {
|
||||
try {
|
||||
getmac.getMac((error, macAddress) => {
|
||||
if (!error) {
|
||||
resolve(crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex'));
|
||||
} else {
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
TPromise.join([import('crypto'), import('getmac')]).then(([crypto, getmac]) => {
|
||||
try {
|
||||
getmac.getMac((error, macAddress) => {
|
||||
if (!error) {
|
||||
resolve(crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex'));
|
||||
} else {
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
errors.onUnexpectedError(err);
|
||||
resolve(undefined);
|
||||
}
|
||||
}, err => {
|
||||
errors.onUnexpectedError(err);
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import streams = require('stream');
|
||||
|
||||
import mime = require('vs/base/common/mime');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
@@ -76,18 +74,6 @@ export interface DetectMimesOption {
|
||||
autoGuessEncoding?: boolean;
|
||||
}
|
||||
|
||||
function doDetectMimesFromStream(instream: streams.Readable, option?: DetectMimesOption): TPromise<IMimeAndEncoding> {
|
||||
return stream.readExactlyByStream(instream, maxBufferLen(option)).then((readResult: stream.ReadResult) => {
|
||||
return detectMimeAndEncodingFromBuffer(readResult, option && option.autoGuessEncoding);
|
||||
});
|
||||
}
|
||||
|
||||
function doDetectMimesFromFile(absolutePath: string, option?: DetectMimesOption): TPromise<IMimeAndEncoding> {
|
||||
return stream.readExactlyByFile(absolutePath, maxBufferLen(option)).then((readResult: stream.ReadResult) => {
|
||||
return detectMimeAndEncodingFromBuffer(readResult, option && option.autoGuessEncoding);
|
||||
});
|
||||
}
|
||||
|
||||
export function detectMimeAndEncodingFromBuffer(readResult: stream.ReadResult, autoGuessEncoding?: false): IMimeAndEncoding;
|
||||
export function detectMimeAndEncodingFromBuffer(readResult: stream.ReadResult, autoGuessEncoding?: boolean): TPromise<IMimeAndEncoding>;
|
||||
export function detectMimeAndEncodingFromBuffer({ buffer, bytesRead }: stream.ReadResult, autoGuessEncoding?: boolean): TPromise<IMimeAndEncoding> | IMimeAndEncoding {
|
||||
@@ -117,57 +103,4 @@ export function detectMimeAndEncodingFromBuffer({ buffer, bytesRead }: stream.Re
|
||||
mimes: isText ? [mime.MIME_TEXT] : [mime.MIME_BINARY],
|
||||
encoding: enc
|
||||
};
|
||||
}
|
||||
|
||||
function filterAndSortMimes(detectedMimes: string[], guessedMimes: string[]): string[] {
|
||||
const mimes = detectedMimes;
|
||||
|
||||
// Add extension based mime as first element as this is the desire of whoever created the file.
|
||||
// Never care about application/octet-stream or application/unknown as guessed mime, as this is the fallback of the guess which is never accurate
|
||||
const guessedMime = guessedMimes[0];
|
||||
if (guessedMime !== mime.MIME_BINARY && guessedMime !== mime.MIME_UNKNOWN) {
|
||||
mimes.unshift(guessedMime);
|
||||
}
|
||||
|
||||
// Remove duplicate elements from array and sort unspecific mime to the end
|
||||
const uniqueSortedMimes = mimes.filter((element, position) => {
|
||||
return element && mimes.indexOf(element) === position;
|
||||
}).sort((mimeA, mimeB) => {
|
||||
if (mimeA === mime.MIME_BINARY) { return 1; }
|
||||
if (mimeB === mime.MIME_BINARY) { return -1; }
|
||||
if (mimeA === mime.MIME_TEXT) { return 1; }
|
||||
if (mimeB === mime.MIME_TEXT) { return -1; }
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return uniqueSortedMimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the given stream to detect its mime type. Returns an array of mime types sorted from most specific to unspecific.
|
||||
* @param instream the readable stream to detect the mime types from.
|
||||
* @param nameHint an additional hint that can be used to detect a mime from a file extension.
|
||||
*/
|
||||
export function detectMimesFromStream(instream: streams.Readable, nameHint: string, option?: DetectMimesOption): TPromise<IMimeAndEncoding> {
|
||||
return doDetectMimesFromStream(instream, option).then(encoding =>
|
||||
handleMimeResult(nameHint, encoding)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the given file to detect its mime type. Returns an array of mime types sorted from most specific to unspecific.
|
||||
* @param absolutePath the absolute path of the file.
|
||||
*/
|
||||
export function detectMimesFromFile(absolutePath: string, option?: DetectMimesOption): TPromise<IMimeAndEncoding> {
|
||||
return doDetectMimesFromFile(absolutePath, option).then(encoding =>
|
||||
handleMimeResult(absolutePath, encoding)
|
||||
);
|
||||
}
|
||||
|
||||
function handleMimeResult(nameHint: string, result: IMimeAndEncoding): IMimeAndEncoding {
|
||||
const filterAndSortedMimes = filterAndSortMimes(result.mimes, mime.guessMimeTypes(nameHint));
|
||||
result.mimes = filterAndSortedMimes;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+2
-27
@@ -7,7 +7,7 @@
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { dirname, join } from 'path';
|
||||
import { join } from 'path';
|
||||
import { nfcall, Queue } from 'vs/base/common/async';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
@@ -26,32 +26,7 @@ export function chmod(path: string, mode: number): TPromise<boolean> {
|
||||
return nfcall(fs.chmod, path, mode);
|
||||
}
|
||||
|
||||
export function mkdirp(path: string, mode?: number): TPromise<boolean> {
|
||||
const mkdir = () => nfcall(fs.mkdir, path, mode)
|
||||
.then(null, (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === 'EEXIST') {
|
||||
return nfcall(fs.stat, path)
|
||||
.then((stat: fs.Stats) => stat.isDirectory
|
||||
? null
|
||||
: TPromise.wrapError(new Error(`'${path}' exists and is not a directory.`)));
|
||||
}
|
||||
|
||||
return TPromise.wrapError<boolean>(err);
|
||||
});
|
||||
|
||||
// is root?
|
||||
if (path === dirname(path)) {
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
return mkdir().then(null, (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === 'ENOENT') {
|
||||
return mkdirp(dirname(path), mode).then(mkdir);
|
||||
}
|
||||
|
||||
return TPromise.wrapError<boolean>(err);
|
||||
});
|
||||
}
|
||||
export import mkdirp = extfs.mkdirp;
|
||||
|
||||
export function rimraf(path: string): TPromise<void> {
|
||||
return lstat(path).then(stat => {
|
||||
|
||||
+15
-15
@@ -11,24 +11,24 @@ import net = require('net');
|
||||
* Given a start point and a max number of retries, will find a port that
|
||||
* is openable. Will return 0 in case no free port can be found.
|
||||
*/
|
||||
export function findFreePort(startPort: number, giveUpAfter: number, timeout: number, clb: (port: number) => void): void {
|
||||
export function findFreePort(startPort: number, giveUpAfter: number, timeout: number): Thenable<number> {
|
||||
let done = false;
|
||||
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
return new Promise(resolve => {
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
return resolve(0);
|
||||
}
|
||||
}, timeout);
|
||||
|
||||
return clb(0);
|
||||
}
|
||||
}, timeout);
|
||||
|
||||
doFindFreePort(startPort, giveUpAfter, (port) => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
clearTimeout(timeoutHandle);
|
||||
|
||||
return clb(port);
|
||||
}
|
||||
doFindFreePort(startPort, giveUpAfter, (port) => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
clearTimeout(timeoutHandle);
|
||||
return resolve(port);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import * as cp from 'child_process';
|
||||
import ChildProcess = cp.ChildProcess;
|
||||
import exec = cp.exec;
|
||||
import spawn = cp.spawn;
|
||||
import { PassThrough } from 'stream';
|
||||
import { fork } from 'vs/base/node/stdFork';
|
||||
import nls = require('vs/nls');
|
||||
import { PPromise, TPromise, TValueCallback, TProgressCallback, ErrorCallback } from 'vs/base/common/winjs.base';
|
||||
@@ -28,17 +27,6 @@ export interface LineData {
|
||||
source: Source;
|
||||
}
|
||||
|
||||
export interface BufferData {
|
||||
data: Buffer;
|
||||
source: Source;
|
||||
}
|
||||
|
||||
export interface StreamData {
|
||||
stdin: NodeJS.WritableStream;
|
||||
stdout: NodeJS.ReadableStream;
|
||||
stderr: NodeJS.ReadableStream;
|
||||
}
|
||||
|
||||
function getWindowsCode(status: number): TerminateResponseCode {
|
||||
switch (status) {
|
||||
case 0:
|
||||
@@ -212,7 +200,7 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
cc(result);
|
||||
};
|
||||
if (this.shell && Platform.isWindows) {
|
||||
let options: any = Objects.clone(this.options);
|
||||
let options: any = Objects.deepClone(this.options);
|
||||
options.windowsVerbatimArguments = true;
|
||||
options.detached = false;
|
||||
let quotedCommand: boolean = false;
|
||||
@@ -287,7 +275,7 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
// Default is to do nothing.
|
||||
}
|
||||
|
||||
private static regexp = /^[^"].* .*[^"]/;
|
||||
private static readonly regexp = /^[^"].* .*[^"]/;
|
||||
private ensureQuotes(value: string) {
|
||||
if (AbstractProcess.regexp.test(value)) {
|
||||
return {
|
||||
@@ -302,10 +290,6 @@ export abstract class AbstractProcess<TProgressData> {
|
||||
}
|
||||
}
|
||||
|
||||
public isRunning(): boolean {
|
||||
return this.childProcessPromise !== null;
|
||||
}
|
||||
|
||||
public get pid(): TPromise<number> {
|
||||
return this.childProcessPromise.then(childProcess => childProcess.pid, err => -1);
|
||||
}
|
||||
@@ -391,60 +375,6 @@ export class LineProcess extends AbstractProcess<LineData> {
|
||||
}
|
||||
}
|
||||
|
||||
export class BufferProcess extends AbstractProcess<BufferData> {
|
||||
|
||||
public constructor(executable: Executable);
|
||||
public constructor(cmd: string, args: string[], shell: boolean, options: CommandOptions);
|
||||
public constructor(module: string, args: string[], options: ForkOptions);
|
||||
public constructor(arg1: string | Executable, arg2?: string[], arg3?: boolean | ForkOptions, arg4?: CommandOptions) {
|
||||
super(<any>arg1, arg2, <any>arg3, arg4);
|
||||
}
|
||||
|
||||
protected handleExec(cc: TValueCallback<SuccessData>, pp: TProgressCallback<BufferData>, error: Error, stdout: Buffer, stderr: Buffer): void {
|
||||
pp({ data: stdout, source: Source.stdout });
|
||||
pp({ data: stderr, source: Source.stderr });
|
||||
cc({ terminated: this.terminateRequested, error: error });
|
||||
}
|
||||
|
||||
protected handleSpawn(childProcess: ChildProcess, cc: TValueCallback<SuccessData>, pp: TProgressCallback<BufferData>, ee: ErrorCallback, sync: boolean): void {
|
||||
childProcess.stdout.on('data', (data: Buffer) => {
|
||||
pp({ data: data, source: Source.stdout });
|
||||
});
|
||||
childProcess.stderr.on('data', (data: Buffer) => {
|
||||
pp({ data: data, source: Source.stderr });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class StreamProcess extends AbstractProcess<StreamData> {
|
||||
|
||||
public constructor(executable: Executable);
|
||||
public constructor(cmd: string, args: string[], shell: boolean, options: CommandOptions);
|
||||
public constructor(module: string, args: string[], options: ForkOptions);
|
||||
public constructor(arg1: string | Executable, arg2?: string[], arg3?: boolean | ForkOptions, arg4?: CommandOptions) {
|
||||
super(<any>arg1, arg2, <any>arg3, arg4);
|
||||
}
|
||||
|
||||
protected handleExec(cc: TValueCallback<SuccessData>, pp: TProgressCallback<StreamData>, error: Error, stdout: Buffer, stderr: Buffer): void {
|
||||
let stdoutStream = new PassThrough();
|
||||
stdoutStream.end(stdout);
|
||||
let stderrStream = new PassThrough();
|
||||
stderrStream.end(stderr);
|
||||
pp({ stdin: null, stdout: stdoutStream, stderr: stderrStream });
|
||||
cc({ terminated: this.terminateRequested, error: error });
|
||||
}
|
||||
|
||||
protected handleSpawn(childProcess: ChildProcess, cc: TValueCallback<SuccessData>, pp: TProgressCallback<StreamData>, ee: ErrorCallback, sync: boolean): void {
|
||||
if (sync) {
|
||||
process.nextTick(() => {
|
||||
pp({ stdin: childProcess.stdin, stdout: childProcess.stdout, stderr: childProcess.stderr });
|
||||
});
|
||||
} else {
|
||||
pp({ stdin: childProcess.stdin, stdout: childProcess.stdout, stderr: childProcess.stderr });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface IQueuedSender {
|
||||
send: (msg: any) => void;
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { join, basename } from 'path';
|
||||
import { writeFile } from 'vs/base/node/pfs';
|
||||
|
||||
export function startProfiling(name: string): TPromise<boolean> {
|
||||
return lazyV8Profiler.value.then(profiler => {
|
||||
profiler.startProfiling(name);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
const _isRunningOutOfDev = process.env['VSCODE_DEV'];
|
||||
|
||||
export function stopProfiling(dir: string, prefix: string): TPromise<string> {
|
||||
return lazyV8Profiler.value.then(profiler => {
|
||||
return profiler.stopProfiling();
|
||||
}).then(profile => {
|
||||
return new TPromise<any>((resolve, reject) => {
|
||||
|
||||
// remove pii paths
|
||||
if (!_isRunningOutOfDev) {
|
||||
removePiiPaths(profile); // remove pii from our users
|
||||
}
|
||||
|
||||
profile.export(function (error, result) {
|
||||
profile.delete();
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
let filepath = join(dir, `${prefix}_${profile.title}.cpuprofile`);
|
||||
if (!_isRunningOutOfDev) {
|
||||
filepath += '.txt'; // github issues must be: txt, zip, png, gif
|
||||
}
|
||||
writeFile(filepath, result).then(() => resolve(filepath), reject);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function removePiiPaths(profile: Profile) {
|
||||
const stack = [profile.head];
|
||||
while (stack.length > 0) {
|
||||
const element = stack.pop();
|
||||
if (element.url) {
|
||||
const shortUrl = basename(element.url);
|
||||
if (element.url !== shortUrl) {
|
||||
element.url = `pii_removed/${shortUrl}`;
|
||||
}
|
||||
}
|
||||
if (element.children) {
|
||||
stack.push(...element.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare interface Profiler {
|
||||
startProfiling(name: string): void;
|
||||
stopProfiling(): Profile;
|
||||
}
|
||||
|
||||
export declare interface Profile {
|
||||
title: string;
|
||||
export(callback: (err, data) => void): void;
|
||||
delete(): void;
|
||||
head: ProfileSample;
|
||||
}
|
||||
|
||||
export declare interface ProfileSample {
|
||||
// bailoutReason:""
|
||||
// callUID:2333
|
||||
// children:Array[39]
|
||||
// functionName:"(root)"
|
||||
// hitCount:0
|
||||
// id:1
|
||||
// lineNumber:0
|
||||
// scriptId:0
|
||||
// url:""
|
||||
url: string;
|
||||
children: ProfileSample[];
|
||||
}
|
||||
|
||||
const lazyV8Profiler = new class {
|
||||
private _value: TPromise<Profiler>;
|
||||
get value() {
|
||||
if (!this._value) {
|
||||
this._value = new TPromise<Profiler>((resolve, reject) => {
|
||||
require(['v8-profiler'], resolve, reject);
|
||||
});
|
||||
}
|
||||
return this._value;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,183 @@
|
||||
################################################################################################
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
################################################################################################
|
||||
|
||||
Param(
|
||||
[string]$ProcessName = "code.exe",
|
||||
[int]$MaxSamples = 10
|
||||
)
|
||||
|
||||
$processLength = "process(".Length
|
||||
|
||||
function Get-MachineInfo {
|
||||
$model = (Get-WmiObject -Class Win32_Processor).Name
|
||||
$memory = (Get-WmiObject -Class Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum).Sum / 1MB
|
||||
$wmi_cs = Get-WmiObject -Class Win32_ComputerSystem
|
||||
return @{
|
||||
"type" = "machineInfo"
|
||||
"model" = $model
|
||||
"processors" = $wmi_cs.NumberOfProcessors
|
||||
"logicalProcessors" = $wmi_cs.NumberOfLogicalProcessors
|
||||
"totalMemory" = $memory
|
||||
|
||||
}
|
||||
}
|
||||
$machineInfo = Get-MachineInfo
|
||||
|
||||
function Get-MachineState {
|
||||
$proc = Get-WmiObject Win32_Processor
|
||||
$os = Get-WmiObject win32_OperatingSystem
|
||||
return @{
|
||||
"type" = 'machineState'
|
||||
"cpuLoad" = $proc.LoadPercentage
|
||||
"handles" = (Get-Process | Measure-Object Handles -Sum).Sum
|
||||
"memory" = @{
|
||||
"total" = $os.TotalVisibleMemorySize
|
||||
"free" = $os.FreePhysicalMemory
|
||||
"swapTotal" = $os.TotalVirtualMemorySize
|
||||
"swapFree" = $os.FreeVirtualMemory
|
||||
}
|
||||
}
|
||||
}
|
||||
$machineState = Get-MachineState
|
||||
|
||||
$processId2CpuLoad = @{}
|
||||
function Get-PerformanceCounters ($logicalProcessors) {
|
||||
$counterError
|
||||
# In a first round we get the performance counters and the process ids.
|
||||
$counters = (Get-Counter ("\Process(*)\% Processor Time", "\Process(*)\ID Process") -ErrorAction SilentlyContinue).CounterSamples
|
||||
$processKey2Id = @{}
|
||||
foreach ($counter in $counters) {
|
||||
if ($counter.Status -ne 0) {
|
||||
continue
|
||||
}
|
||||
$path = $counter.path;
|
||||
$segments = $path.Split("\");
|
||||
$kind = $segments[4];
|
||||
$processKey = $segments[3].Substring($processLength, $segments[3].Length - $processLength - 1)
|
||||
if ($kind -eq "id process") {
|
||||
$processKey2Id[$processKey] = [uint32]$counter.CookedValue
|
||||
}
|
||||
}
|
||||
foreach ($counter in $counters) {
|
||||
if ($counter.Status -ne 0) {
|
||||
continue
|
||||
}
|
||||
$path = $counter.path;
|
||||
$segments = $path.Split("\");
|
||||
$kind = $segments[4];
|
||||
$processKey = $segments[3].Substring($processLength, $segments[3].Length - $processLength - 1)
|
||||
if ($kind -eq "% processor time") {
|
||||
$array = New-Object double[] ($MaxSamples + 1)
|
||||
$array[0] = ($counter.CookedValue / $logicalProcessors)
|
||||
$processId = $processKey2Id[$processKey]
|
||||
if ($processId) {
|
||||
$processId2CpuLoad[$processId] = $array
|
||||
}
|
||||
}
|
||||
}
|
||||
# Now lets sample another 10 times but only the processor time
|
||||
$samples = Get-Counter "\Process(*)\% Processor Time" -SampleInterval 1 -MaxSamples $MaxSamples -ErrorAction SilentlyContinue
|
||||
for ($s = 0; $s -lt $samples.Count; $s++) {
|
||||
$counters = $samples[$s].CounterSamples;
|
||||
foreach ($counter in $counters) {
|
||||
if ($counter.Status -ne 0) {
|
||||
continue
|
||||
}
|
||||
$path = $counter.path;
|
||||
$segments = $path.Split("\");
|
||||
$processKey = $segments[3].Substring($processLength, $segments[3].Length - $processLength - 1)
|
||||
$processKey = $processKey2Id[$processKey];
|
||||
if ($processKey) {
|
||||
$processId2CpuLoad[$processKey][$s + 1] = ($counter.CookedValue / $logicalProcessors)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Get-PerformanceCounters -logicalProcessors $machineInfo.logicalProcessors
|
||||
|
||||
$topElements = New-Object PSObject[] $processId2CpuLoad.Keys.Count;
|
||||
$index = 0;
|
||||
foreach ($key in $processId2CpuLoad.Keys) {
|
||||
$obj = [PSCustomObject]@{
|
||||
ProcessId = $key
|
||||
Load = ($processId2CpuLoad[$key] | Measure-Object -Sum).Sum / ($MaxSamples + 1)
|
||||
}
|
||||
$topElements[$index] = $obj
|
||||
$index++
|
||||
}
|
||||
$topElements = $topElements | Sort-Object Load -Descending
|
||||
|
||||
# Get all code processes
|
||||
$codeProcesses = @{}
|
||||
foreach ($item in Get-WmiObject Win32_Process -Filter "name = '$ProcessName'") {
|
||||
$codeProcesses[$item.ProcessId] = $item
|
||||
}
|
||||
foreach ($item in Get-WmiObject Win32_Process -Filter "name = 'codeHelper.exe'") {
|
||||
$codeProcesses[$item.ProcessId] = $item
|
||||
}
|
||||
$otherProcesses = @{}
|
||||
foreach ($item in Get-WmiObject Win32_Process -Filter "name Like '%'") {
|
||||
if (!($codeProcesses.Contains($item.ProcessId))) {
|
||||
$otherProcesses[$item.ProcessId] = $item
|
||||
}
|
||||
}
|
||||
$modified = $false
|
||||
do {
|
||||
$toDelete = @()
|
||||
$modified = $false
|
||||
foreach ($item in $otherProcesses.Values) {
|
||||
if ($codeProcesses.Contains([uint32]$item.ParentProcessId)) {
|
||||
$codeProcesses[$item.ProcessId] = $item;
|
||||
$toDelete += $item
|
||||
}
|
||||
}
|
||||
foreach ($item in $toDelete) {
|
||||
$otherProcesses.Remove([uint32]$item.ProcessId)
|
||||
$modified = $true
|
||||
}
|
||||
} while ($modified)
|
||||
|
||||
$result = New-Object PSObject[] (2 + [math]::Min(5, $topElements.Count) + $codeProcesses.Count)
|
||||
$result[0] = $machineInfo
|
||||
$result[1] = $machineState
|
||||
$index = 2;
|
||||
for($i = 0; $i -lt 5 -and $i -lt $topElements.Count; $i++) {
|
||||
$element = $topElements[$i]
|
||||
$item = $codeProcesses[[uint32]$element.ProcessId]
|
||||
if (!$item) {
|
||||
$item = $otherProcesses[[uint32]$element.ProcessId]
|
||||
}
|
||||
if ($item) {
|
||||
$cpuLoad = $processId2CpuLoad[[uint32]$item.ProcessId] | % { [pscustomobject] $_ }
|
||||
$result[$index] = [pscustomobject]@{
|
||||
"type" = "topProcess"
|
||||
"name" = $item.Name
|
||||
"processId" = $item.ProcessId
|
||||
"parentProcessId" = $item.ParentProcessId
|
||||
"commandLine" = $item.CommandLine
|
||||
"handles" = $item.HandleCount
|
||||
"cpuLoad" = $cpuLoad
|
||||
"workingSetSize" = $item.WorkingSetSize
|
||||
}
|
||||
$index++
|
||||
}
|
||||
}
|
||||
foreach ($item in $codeProcesses.Values) {
|
||||
# we need to convert this otherwise to JSON with create a value, count object and not an inline array
|
||||
$cpuLoad = $processId2CpuLoad[[uint32]$item.ProcessId] | % { [pscustomobject] $_ }
|
||||
$result[$index] = [pscustomobject]@{
|
||||
"type" = "processInfo"
|
||||
"name" = $item.Name
|
||||
"processId" = $item.ProcessId
|
||||
"parentProcessId" = $item.ParentProcessId
|
||||
"commandLine" = $item.CommandLine
|
||||
"handles" = $item.HandleCount
|
||||
"cpuLoad" = $cpuLoad
|
||||
"workingSetSize" = $item.WorkingSetSize
|
||||
}
|
||||
$index++
|
||||
}
|
||||
|
||||
$result | ConvertTo-Json -Depth 99
|
||||
@@ -0,0 +1,235 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { spawn, exec } from 'child_process';
|
||||
import * as path from 'path';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export interface ProcessItem {
|
||||
name: string;
|
||||
cmd: string;
|
||||
pid: number;
|
||||
ppid: number;
|
||||
load: number;
|
||||
mem: number;
|
||||
|
||||
children?: ProcessItem[];
|
||||
}
|
||||
|
||||
export function listProcesses(rootPid: number): Promise<ProcessItem> {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
let rootItem: ProcessItem;
|
||||
const map = new Map<number, ProcessItem>();
|
||||
|
||||
function addToTree(pid: number, ppid: number, cmd: string, load: number, mem: number) {
|
||||
|
||||
const parent = map.get(ppid);
|
||||
if (pid === rootPid || parent) {
|
||||
|
||||
const item: ProcessItem = {
|
||||
name: findName(cmd),
|
||||
cmd,
|
||||
pid,
|
||||
ppid,
|
||||
load,
|
||||
mem
|
||||
};
|
||||
map.set(pid, item);
|
||||
|
||||
if (pid === rootPid) {
|
||||
rootItem = item;
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
parent.children.push(item);
|
||||
if (parent.children.length > 1) {
|
||||
parent.children = parent.children.sort((a, b) => a.pid - b.pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findName(cmd: string): string {
|
||||
|
||||
const RENDERER_PROCESS_HINT = /--disable-blink-features=Auxclick/;
|
||||
const WINDOWS_WATCHER_HINT = /\\watcher\\win32\\CodeHelper.exe/;
|
||||
const TYPE = /--type=([a-zA-Z-]+)/;
|
||||
|
||||
// find windows file watcher
|
||||
if (WINDOWS_WATCHER_HINT.exec(cmd)) {
|
||||
return 'watcherService';
|
||||
}
|
||||
|
||||
// find "--type=xxxx"
|
||||
let matches = TYPE.exec(cmd);
|
||||
if (matches && matches.length === 2) {
|
||||
if (matches[1] === 'renderer') {
|
||||
if (!RENDERER_PROCESS_HINT.exec(cmd)) {
|
||||
return 'shared-process';
|
||||
}
|
||||
|
||||
return `window`;
|
||||
}
|
||||
return matches[1];
|
||||
}
|
||||
|
||||
// find all xxxx.js
|
||||
const JS = /[a-zA-Z-]+\.js/g;
|
||||
let result = '';
|
||||
do {
|
||||
matches = JS.exec(cmd);
|
||||
if (matches) {
|
||||
result += matches + ' ';
|
||||
}
|
||||
} while (matches);
|
||||
|
||||
if (result) {
|
||||
if (cmd.indexOf('node ') !== 0) {
|
||||
return `electron_node ${result}`;
|
||||
}
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
|
||||
interface ProcessInfo {
|
||||
type: 'processInfo';
|
||||
name: string;
|
||||
processId: number;
|
||||
parentProcessId: number;
|
||||
commandLine: string;
|
||||
handles: number;
|
||||
cpuLoad: number[];
|
||||
workingSetSize: number;
|
||||
}
|
||||
|
||||
interface TopProcess {
|
||||
type: 'topProcess';
|
||||
name: string;
|
||||
processId: number;
|
||||
parentProcessId: number;
|
||||
commandLine: string;
|
||||
handles: number;
|
||||
cpuLoad: number[];
|
||||
workingSetSize: number;
|
||||
}
|
||||
|
||||
type Item = ProcessInfo | TopProcess;
|
||||
|
||||
const cleanUNCPrefix = (value: string): string => {
|
||||
if (value.indexOf('\\\\?\\') === 0) {
|
||||
return value.substr(4);
|
||||
} else if (value.indexOf('\\??\\') === 0) {
|
||||
return value.substr(4);
|
||||
} else if (value.indexOf('"\\\\?\\') === 0) {
|
||||
return '"' + value.substr(5);
|
||||
} else if (value.indexOf('"\\??\\') === 0) {
|
||||
return '"' + value.substr(5);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
const execMain = path.basename(process.execPath);
|
||||
const script = URI.parse(require.toUrl('vs/base/node/ps-win.ps1')).fsPath;
|
||||
const commandLine = `& {& '${script}' -ProcessName '${execMain}' -MaxSamples 3}`;
|
||||
const cmd = spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', commandLine]);
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
cmd.stdout.on('data', data => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
cmd.stderr.on('data', data => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
cmd.on('exit', () => {
|
||||
if (stderr.length > 0) {
|
||||
reject(stderr);
|
||||
}
|
||||
let processItems: Map<number, ProcessItem> = new Map();
|
||||
try {
|
||||
const items: Item[] = JSON.parse(stdout);
|
||||
for (const item of items) {
|
||||
if (item.type === 'processInfo') {
|
||||
let load = 0;
|
||||
if (item.cpuLoad) {
|
||||
for (let value of item.cpuLoad) {
|
||||
load += value;
|
||||
}
|
||||
load = load / item.cpuLoad.length;
|
||||
} else {
|
||||
load = -1;
|
||||
}
|
||||
let commandLine = cleanUNCPrefix(item.commandLine);
|
||||
processItems.set(item.processId, {
|
||||
name: findName(commandLine),
|
||||
cmd: commandLine,
|
||||
pid: item.processId,
|
||||
ppid: item.parentProcessId,
|
||||
load: load,
|
||||
mem: item.workingSetSize
|
||||
});
|
||||
}
|
||||
}
|
||||
rootItem = processItems.get(rootPid);
|
||||
if (rootItem) {
|
||||
processItems.forEach(item => {
|
||||
let parent = processItems.get(item.ppid);
|
||||
if (parent) {
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
parent.children.push(item);
|
||||
}
|
||||
});
|
||||
processItems.forEach(item => {
|
||||
if (item.children) {
|
||||
item.children = item.children.sort((a, b) => a.pid - b.pid);
|
||||
}
|
||||
});
|
||||
resolve(rootItem);
|
||||
} else {
|
||||
reject(new Error(`Root process ${rootPid} not found`));
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
} else { // OS X & Linux
|
||||
|
||||
const CMD = 'ps -ax -o pid=,ppid=,pcpu=,pmem=,command=';
|
||||
const PID_CMD = /^\s*([0-9]+)\s+([0-9]+)\s+([0-9]+\.[0-9]+)\s+([0-9]+\.[0-9]+)\s+(.+)$/;
|
||||
|
||||
exec(CMD, { maxBuffer: 1000 * 1024 }, (err, stdout, stderr) => {
|
||||
|
||||
if (err || stderr) {
|
||||
reject(err || stderr.toString());
|
||||
} else {
|
||||
|
||||
const lines = stdout.toString().split('\n');
|
||||
for (const line of lines) {
|
||||
let matches = PID_CMD.exec(line.trim());
|
||||
if (matches && matches.length === 6) {
|
||||
addToTree(parseInt(matches[1]), parseInt(matches[2]), matches[5], parseFloat(matches[3]), parseFloat(matches[4]));
|
||||
}
|
||||
}
|
||||
|
||||
resolve(rootItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
Vendored
-39
@@ -1,39 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Profile } from './profiler';
|
||||
|
||||
declare interface TickStart {
|
||||
name: string;
|
||||
started: number;
|
||||
}
|
||||
|
||||
export declare class Tick {
|
||||
|
||||
readonly duration: number;
|
||||
readonly name: string;
|
||||
readonly started: number;
|
||||
readonly stopped: number;
|
||||
readonly profile: Profile;
|
||||
|
||||
static compareByStart(a: Tick, b: Tick): number;
|
||||
}
|
||||
|
||||
declare interface TickController {
|
||||
while<T extends Thenable<any>>(t: T): T;
|
||||
stop(stopped?: number): void;
|
||||
}
|
||||
|
||||
export function startTimer(name: string): TickController;
|
||||
|
||||
export function stopTimer(name: string): void;
|
||||
|
||||
export function ticks(): Tick[];
|
||||
|
||||
export function tick(name: string): Tick;
|
||||
|
||||
export function setProfileList(names: string[]): void;
|
||||
|
||||
export function disable(): void;
|
||||
@@ -1,128 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/*global define*/
|
||||
|
||||
var requireProfiler;
|
||||
|
||||
if (typeof define !== "function" && typeof module === "object" && typeof module.exports === "object") {
|
||||
// this is commonjs, fake amd
|
||||
global.define = function (dep, callback) {
|
||||
module.exports = callback();
|
||||
global.define = undefined;
|
||||
};
|
||||
requireProfiler = function () {
|
||||
return require('v8-profiler');
|
||||
};
|
||||
} else {
|
||||
// this is amd
|
||||
requireProfiler = function () {
|
||||
return require.__$__nodeRequire('v8-profiler');
|
||||
};
|
||||
}
|
||||
|
||||
define([], function () {
|
||||
|
||||
function Tick(name, started, stopped, profile) {
|
||||
this.name = name;
|
||||
this.started = started;
|
||||
this.stopped = stopped;
|
||||
this.duration = Math.round(((stopped[0] * 1.e9 + stopped[1]) - (started[0] * 1e9 + started[1])) / 1.e6);
|
||||
this.profile = profile;
|
||||
}
|
||||
Tick.compareByStart = function (a, b) {
|
||||
if (a.started < b.started) {
|
||||
return -1;
|
||||
} else if (a.started > b.started) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// This module can be loaded in an amd and commonjs-context.
|
||||
// Because we want both instances to use the same tick-data
|
||||
// we store them globally
|
||||
global._perfStarts = global._perfStarts || new Map();
|
||||
global._perfTicks = global._perfTicks || new Map();
|
||||
global._perfToBeProfiled = global._perfToBeProfiled || new Set();
|
||||
|
||||
var _starts = global._perfStarts;
|
||||
var _ticks = global._perfTicks;
|
||||
var _toBeProfiled = global._perfToBeProfiled;
|
||||
|
||||
function startTimer(name) {
|
||||
if (_starts.has(name)) {
|
||||
throw new Error("${name}" + " already exists");
|
||||
}
|
||||
if (_toBeProfiled.has(name)) {
|
||||
requireProfiler().startProfiling(name, true);
|
||||
}
|
||||
_starts.set(name, { name: name, started: process.hrtime() });
|
||||
var stop = stopTimer.bind(undefined, name);
|
||||
return {
|
||||
stop: stop,
|
||||
while: function (thenable) {
|
||||
thenable.then(function () { stop(); }, function () { stop(); });
|
||||
return thenable;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function stopTimer(name) {
|
||||
var profile = _toBeProfiled.has(name) ? requireProfiler().stopProfiling(name) : undefined;
|
||||
var start = _starts.get(name);
|
||||
if (start !== undefined) {
|
||||
var tick = new Tick(start.name, start.started, process.hrtime(), profile);
|
||||
_ticks.set(name, tick);
|
||||
_starts.delete(name);
|
||||
}
|
||||
}
|
||||
|
||||
function ticks() {
|
||||
var ret = [];
|
||||
_ticks.forEach(function (value) { ret.push(value); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
function tick(name) {
|
||||
var ret = _ticks.get(name);
|
||||
if (!ret) {
|
||||
var now = Date.now();
|
||||
ret = new Tick(name, now, now);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function setProfileList(names) {
|
||||
_toBeProfiled.clear();
|
||||
names.forEach(function (name) { _toBeProfiled.add(name); });
|
||||
}
|
||||
|
||||
var exports = {
|
||||
Tick: Tick,
|
||||
startTimer: startTimer,
|
||||
stopTimer: stopTimer,
|
||||
ticks: ticks,
|
||||
tick: tick,
|
||||
setProfileList: setProfileList,
|
||||
disable: disable,
|
||||
};
|
||||
|
||||
function disable() {
|
||||
var emptyController = Object.freeze({ while: function (t) { return t; }, stop: function () { } });
|
||||
var emptyTicks = Object.create([]);
|
||||
exports.startTimer = function () { return emptyController; };
|
||||
exports.stopTimer = function () { };
|
||||
exports.ticks = function () { return emptyTicks; };
|
||||
|
||||
delete global._perfStarts;
|
||||
delete global._perfTicks;
|
||||
}
|
||||
|
||||
return exports;
|
||||
});
|
||||
@@ -0,0 +1,151 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { readdirSync, statSync, existsSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
export interface WorkspaceStatItem {
|
||||
name: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface WorkspaceStats {
|
||||
fileTypes: WorkspaceStatItem[];
|
||||
configFiles: WorkspaceStatItem[];
|
||||
fileCount: number;
|
||||
maxFilesReached: boolean;
|
||||
}
|
||||
|
||||
function asSortedItems(map: Map<string, number>): WorkspaceStatItem[] {
|
||||
let a: WorkspaceStatItem[] = [];
|
||||
map.forEach((value, index) => a.push({ name: index, count: value }));
|
||||
return a.sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
export function collectLaunchConfigs(folder: string): WorkspaceStatItem[] {
|
||||
let launchConfigs = new Map<string, number>();
|
||||
|
||||
let launchConfig = join(folder, '.vscode', 'launch.json');
|
||||
if (existsSync(launchConfig)) {
|
||||
try {
|
||||
const contents = readFileSync(launchConfig).toString();
|
||||
const json = JSON.parse(contents);
|
||||
if (json['configurations']) {
|
||||
for (const each of json['configurations']) {
|
||||
const type = each['type'];
|
||||
if (type) {
|
||||
if (launchConfigs.has(type)) {
|
||||
launchConfigs.set(type, launchConfigs.get(type) + 1);
|
||||
}
|
||||
else {
|
||||
launchConfigs.set(type, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
return asSortedItems(launchConfigs);
|
||||
}
|
||||
|
||||
export function collectWorkspaceStats(folder: string, filter: string[]): WorkspaceStats {
|
||||
const configFilePatterns = [
|
||||
{ 'tag': 'grunt.js', 'pattern': /^gruntfile\.js$/i },
|
||||
{ 'tag': 'gulp.js', 'pattern': /^gulpfile\.js$/i },
|
||||
{ 'tag': 'tsconfig.json', 'pattern': /^tsconfig\.json$/i },
|
||||
{ 'tag': 'package.json', 'pattern': /^package\.json$/i },
|
||||
{ 'tag': 'jsconfig.json', 'pattern': /^jsconfig\.json$/i },
|
||||
{ 'tag': 'tslint.json', 'pattern': /^tslint\.json$/i },
|
||||
{ 'tag': 'eslint.json', 'pattern': /^eslint\.json$/i },
|
||||
{ 'tag': 'tasks.json', 'pattern': /^tasks\.json$/i },
|
||||
{ 'tag': 'launch.json', 'pattern': /^launch\.json$/i },
|
||||
{ 'tag': 'settings.json', 'pattern': /^settings\.json$/i },
|
||||
{ 'tag': 'webpack.config.js', 'pattern': /^webpack\.config\.js$/i },
|
||||
{ 'tag': 'project.json', 'pattern': /^project\.json$/i },
|
||||
{ 'tag': 'makefile', 'pattern': /^makefile$/i },
|
||||
{ 'tag': 'sln', 'pattern': /^.+\.sln$/i },
|
||||
{ 'tag': 'csproj', 'pattern': /^.+\.csproj$/i },
|
||||
{ 'tag': 'cmake', 'pattern': /^.+\.cmake$/i }
|
||||
];
|
||||
|
||||
let fileTypes = new Map<string, number>();
|
||||
let configFiles = new Map<string, number>();
|
||||
|
||||
const MAX_FILES = 20000;
|
||||
|
||||
let walkSync = (dir: string, acceptFile: (fileName: string) => void, filter: string[], token) => {
|
||||
if (token.maxReached) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let files = readdirSync(dir);
|
||||
for (const file of files) {
|
||||
try {
|
||||
if (statSync(join(dir, file)).isDirectory()) {
|
||||
if (filter.indexOf(file) === -1) {
|
||||
walkSync(join(dir, file), acceptFile, filter, token);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (token.count++ >= MAX_FILES) {
|
||||
token.maxReached = true;
|
||||
return;
|
||||
}
|
||||
acceptFile(file);
|
||||
}
|
||||
} catch {
|
||||
// skip over files for which stat fails
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// skip over folders that cannot be read
|
||||
}
|
||||
};
|
||||
|
||||
let addFileType = (fileType: string) => {
|
||||
if (fileTypes.has(fileType)) {
|
||||
fileTypes.set(fileType, fileTypes.get(fileType) + 1);
|
||||
}
|
||||
else {
|
||||
fileTypes.set(fileType, 1);
|
||||
}
|
||||
};
|
||||
|
||||
let addConfigFiles = (fileName: string) => {
|
||||
for (const each of configFilePatterns) {
|
||||
if (each.pattern.test(fileName)) {
|
||||
if (configFiles.has(each.tag)) {
|
||||
configFiles.set(each.tag, configFiles.get(each.tag) + 1);
|
||||
} else {
|
||||
configFiles.set(each.tag, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let acceptFile = (name: string) => {
|
||||
if (name.lastIndexOf('.') >= 0) {
|
||||
let suffix: string | undefined = name.split('.').pop();
|
||||
if (suffix) {
|
||||
addFileType(suffix);
|
||||
}
|
||||
}
|
||||
addConfigFiles(name);
|
||||
};
|
||||
|
||||
let token: { count: number, maxReached: boolean } = { count: 0, maxReached: false };
|
||||
walkSync(folder, acceptFile, filter, token);
|
||||
|
||||
return {
|
||||
configFiles: asSortedItems(configFiles),
|
||||
fileTypes: asSortedItems(fileTypes),
|
||||
fileCount: token.count,
|
||||
maxFilesReached: token.maxReached
|
||||
|
||||
};
|
||||
}
|
||||
+24
-72
@@ -6,7 +6,6 @@
|
||||
'use strict';
|
||||
|
||||
import fs = require('fs');
|
||||
import stream = require('stream');
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
@@ -15,43 +14,6 @@ export interface ReadResult {
|
||||
bytesRead: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up to total bytes from the provided stream.
|
||||
*/
|
||||
export function readExactlyByStream(stream: stream.Readable, totalBytes: number): TPromise<ReadResult> {
|
||||
return new TPromise<ReadResult>((complete, error) => {
|
||||
let done = false;
|
||||
let buffer = new Buffer(totalBytes);
|
||||
let bytesRead = 0;
|
||||
|
||||
stream.on('data', (data: NodeBuffer) => {
|
||||
let bytesToRead = Math.min(totalBytes - bytesRead, data.length);
|
||||
data.copy(buffer, bytesRead, 0, bytesToRead);
|
||||
bytesRead += bytesToRead;
|
||||
|
||||
if (bytesRead === totalBytes) {
|
||||
(stream as any).destroy(); // Will trigger the close event eventually
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('error', (e: Error) => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
error(e);
|
||||
}
|
||||
});
|
||||
|
||||
let onSuccess = () => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
complete({ buffer, bytesRead });
|
||||
}
|
||||
};
|
||||
|
||||
stream.on('close', onSuccess);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads totalBytes from the provided file.
|
||||
*/
|
||||
@@ -63,7 +25,7 @@ export function readExactlyByFile(file: string, totalBytes: number): TPromise<Re
|
||||
}
|
||||
|
||||
function end(err: Error, resultBuffer: NodeBuffer, bytesRead: number): void {
|
||||
fs.close(fd, (closeError: Error) => {
|
||||
fs.close(fd, closeError => {
|
||||
if (closeError) {
|
||||
return error(closeError);
|
||||
}
|
||||
@@ -76,35 +38,30 @@ export function readExactlyByFile(file: string, totalBytes: number): TPromise<Re
|
||||
});
|
||||
}
|
||||
|
||||
let buffer = new Buffer(totalBytes);
|
||||
let bytesRead = 0;
|
||||
let zeroAttempts = 0;
|
||||
function loop(): void {
|
||||
fs.read(fd, buffer, bytesRead, totalBytes - bytesRead, null, (err, moreBytesRead) => {
|
||||
const buffer = new Buffer(totalBytes);
|
||||
let offset = 0;
|
||||
|
||||
function readChunk(): void {
|
||||
fs.read(fd, buffer, offset, totalBytes - offset, null, (err, bytesRead) => {
|
||||
if (err) {
|
||||
return end(err, null, 0);
|
||||
}
|
||||
|
||||
// Retry up to N times in case 0 bytes where read
|
||||
if (moreBytesRead === 0) {
|
||||
if (++zeroAttempts === 10) {
|
||||
return end(null, buffer, bytesRead);
|
||||
}
|
||||
|
||||
return loop();
|
||||
if (bytesRead === 0) {
|
||||
return end(null, buffer, offset);
|
||||
}
|
||||
|
||||
bytesRead += moreBytesRead;
|
||||
offset += bytesRead;
|
||||
|
||||
if (bytesRead === totalBytes) {
|
||||
return end(null, buffer, bytesRead);
|
||||
if (offset === totalBytes) {
|
||||
return end(null, buffer, offset);
|
||||
}
|
||||
|
||||
return loop();
|
||||
return readChunk();
|
||||
});
|
||||
}
|
||||
|
||||
loop();
|
||||
readChunk();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -126,7 +83,7 @@ export function readToMatchingString(file: string, matchingString: string, chunk
|
||||
}
|
||||
|
||||
function end(err: Error, result: string): void {
|
||||
fs.close(fd, (closeError: Error) => {
|
||||
fs.close(fd, closeError => {
|
||||
if (closeError) {
|
||||
return error(closeError);
|
||||
}
|
||||
@@ -140,39 +97,34 @@ export function readToMatchingString(file: string, matchingString: string, chunk
|
||||
}
|
||||
|
||||
let buffer = new Buffer(maximumBytesToRead);
|
||||
let bytesRead = 0;
|
||||
let zeroAttempts = 0;
|
||||
function loop(): void {
|
||||
fs.read(fd, buffer, bytesRead, chunkBytes, null, (err, moreBytesRead) => {
|
||||
let offset = 0;
|
||||
|
||||
function readChunk(): void {
|
||||
fs.read(fd, buffer, offset, chunkBytes, null, (err, bytesRead) => {
|
||||
if (err) {
|
||||
return end(err, null);
|
||||
}
|
||||
|
||||
// Retry up to N times in case 0 bytes where read
|
||||
if (moreBytesRead === 0) {
|
||||
if (++zeroAttempts === 10) {
|
||||
return end(null, null);
|
||||
}
|
||||
|
||||
return loop();
|
||||
if (bytesRead === 0) {
|
||||
return end(null, null);
|
||||
}
|
||||
|
||||
bytesRead += moreBytesRead;
|
||||
offset += bytesRead;
|
||||
|
||||
const newLineIndex = buffer.indexOf(matchingString);
|
||||
if (newLineIndex >= 0) {
|
||||
return end(null, buffer.toString('utf8').substr(0, newLineIndex));
|
||||
}
|
||||
|
||||
if (bytesRead >= maximumBytesToRead) {
|
||||
if (offset >= maximumBytesToRead) {
|
||||
return end(new Error(`Could not find ${matchingString} in first ${maximumBytesToRead} bytes of ${file}`), null);
|
||||
}
|
||||
|
||||
return loop();
|
||||
return readChunk();
|
||||
});
|
||||
}
|
||||
|
||||
loop();
|
||||
readChunk();
|
||||
})
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user